Commit 888bc1b5 by Яков

update

parent 7549a85b
......@@ -9,12 +9,12 @@ const App = () => {
// value={`<iframe src="https://cdn.atmaguru.online/2/atmacompany/8/b/8bTfGoWtAuv5waVabQRTtWaNOrve5uv8UBXFbGOH9cowQ1K56dYi7TFz6h5jUfzr.pdf" width="100%" /><iframe src="https://www.youtube.com/embed/YmZGP7YP8c4" frameborder="0" allowfullscreen="true"></iframe><video src="https://cdn.atmaguru.online/2/demo/V/k/VkrEXjkxnutXLgcJPt5CLXNgEj4RaL9Zk4SQhIMUjOeIRpu0dSKtQCIMl49pJM6N.webm" controls="true"></video><p>Так исторически сложилось, что взрослым людям стараются дать максимум материалов: часовые лекции, объемные массивы текста и должностных инструкций. Сотрудник изучает огромный объем информации. Пытается его запомнить, а потом в конце курса сдать большой аттестационный экзамен. Вы не учитывете при этом, что мозг взрослого человека перегружен, ему нужно выполнять обязанности по работе, думать о домашних делах, его постоянно отвлекают менеджеры и коллеги по работе… Единственный правильный способ — это давать информацию небольшими кусочками и после каждой порции проверять усвоена она или нет.</p><p></p><p>что-то новое о компании<br><a href="https://cdn.atmaguru.online/1/demo/T/G/TGvSAoLawONkteJ47yyNfmsC8zNe3ZRG4iO0ZfAjmvOIZkm20BWp8KdWCH5p1Rrx.gif" target="_blank" download="Редактор.gif" data-size="37 Мб">РСкачать книгу</a> <br></p>`}
// value={"<p style=\"text-align: left\"><a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://telemost.yandex.ru/j/5911922929\">дшдлодлод</a></p><table data-bordered=\"true\" class=\"\" style=\"min-width: 75px\"><colgroup><col style=\"min-width: 25px\"><col style=\"min-width: 25px\"><col style=\"min-width: 25px\"></colgroup><tbody><tr><th colspan=\"1\" rowspan=\"1\"><p style=\"text-align: left\">sdfsdf</p></th><th colspan=\"1\" rowspan=\"1\"><p style=\"text-align: left\"></p></th><th colspan=\"1\" rowspan=\"1\"><p style=\"text-align: left\">sdfsdf</p></th></tr><tr><td colspan=\"1\" rowspan=\"1\"><p style=\"text-align: left\">sdfsdf</p></td><td colspan=\"1\" rowspan=\"1\"><p style=\"text-align: left\"></p></td><td colspan=\"1\" rowspan=\"1\"><p style=\"text-align: left\">sdfsdf</p></td></tr></tbody></table><p style=\"text-align: left\"></p>"}
// value={"<h2 style=\"text-align: center\">В данном разделе Вы сможете изучить профильные системы KRAUSS и их предназначения.</h2><h2 style=\"text-align: center\">Профильные системы с которыми мы работаем</h2><p style=\"text-align: left\"></p><p style=\"text-align: left\"><img src=\"https://cdn.atmaguru.online/1/galereya-okon/t/J/tJkxP0341Yu58FNTDNtan20IxGVVegvv4Ns8kQvn1SdiWbRBTKj009cPUNpYfD4S.png\" alt=\"fgdgdfdg&amp;#10;sdfsdf&amp;#10;SDfsdf&amp;#10;sdfsdf\" width=\"798\" height=\"449\" data-align=\"center\" style=\"display: block; margin-left: auto; margin-right: auto; width: 798px; height: 449px\" data-node-id=\"img-1752479121918-zsmot20zn\">​</p><p style=\"text-align: left\"><img src=\"https://cdn.atmaguru.online/1/galereya-okon/6/D/6DPwk96e0ja5vPaXFSDzz3SVmeJgWvMLeysYxxYXu5YgOjr57CxS21vzsMThgMgz.png\" alt=\"\" width=\"798\" height=\"431\" data-align=\"center\" style=\"display: block; margin-left: auto; margin-right: auto; width: 798px; height: 431px\" data-node-id=\"img-1752479121912-lk1uabgxv\">​</p><p style=\"text-align: left\"><span style=\"color: rgb(243, 76, 55)\"><strong>Примечание: профильные системы KRAUSS мы закупаем у компании \"Реалит\". Офис данной компании находится в г. Уфа.</strong></span></p><h2 style=\"text-align: left\"><a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://disk.yandex.ru/d/KvB3VMNFKu4ULg\">Ссылка для скачивания видео-курсов от компании \"Реалит\" https://disk.yandex.ru/d/KvB3VMNFKu4ULg</a></h2><h2 style=\"text-align: left\">Файл для скачивания: <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://cdn.atmaguru.online/1/galereya-okon/4/N/4N30p3UR8gb4jph1m1u8KijlQmDp6H9DgHKucRjvxOPUnMprnJyBaK2S1dGZPqaD.pdf\">Профильные системы</a></h2><p style=\"text-align: left\"></p>"}
value={"<p style=\"text-align: left\"></p><p style=\"text-align: left\"><audio controls=\"true\" src=\"https://cdn.atmaguru.online/1/demo/Z/k/ZkGyYplRHYOO2YgIhkmHgPOpeYKSrET2qmHkgBZ5WV0sbaQZc7DS3aPm4OEJHnfR.mp3\"><source src=\"https://cdn.atmaguru.online/1/demo/Z/k/ZkGyYplRHYOO2YgIhkmHgPOpeYKSrET2qmHkgBZ5WV0sbaQZc7DS3aPm4OEJHnfR.mp3\"></audio></p>"}
value={"<p style=\"text-align: left\"></p><p style=\"text-align: left\"></p><p style=\"text-align: left\"><img src=\"https://cdn.atmaguru.online/1/demo/S/M/SMAsLhTwaDcljY7Vo5FpmGP5uMtyxuY3H4TDLnltS2VFDapjyBLo2xa5Dw2bTBGB.png\" alt=\"\" title=\"Снимок экрана 2025-07-29 в 00.54.55.png\" width=\"733\" height=\"473\" data-align=\"left\" style=\"float: left; margin-right: 1rem; width: 733px; height: 473px\" data-node-id=\"img-1755513803778-58y4piy2r\">​​​</p>"}
onChange={(value)=>{
console.log(value);
}}
uploadOptions={{
url: '/upload',
url: 'https://cdn.atmaguru.online/upload/?sid=demo&md5=HwgSY1eMX_zlzfmKsBRHEg&expires=1755535222',
errorMessage: 'Загрузка временно невозможна'
}}
style={{
......
......@@ -7,7 +7,7 @@ module.exports = function(app) {
target: 'https://cdn.atmaguru.online',
changeOrigin: true,
pathRewrite: {
'^/upload': 'https://cdn.atmaguru.online/upload/?sid=demo&md5=G04JoFyFRn5TOviY9R0Nag&expires=1752528156',
'^/upload': 'https://cdn.atmaguru.online/upload/?sid=demo&md5=HwgSY1eMX_zlzfmKsBRHEg&expires=1755535222',
},
onProxyReq: (proxyReq) => {
// Добавляем необходимые заголовки
......
{
"name": "react-ag-qeditor",
"version": "1.1.16",
"version": "1.1.17",
"description": "WYSIWYG html editor",
"author": "atma",
"license": "MIT",
......
......@@ -50,6 +50,24 @@ export const DragAndDrop = Extension.create({
return null;
};
// >>> NEW: позиция СРАЗУ ПОСЛЕ медиа-узла, на который уронили
const getInsertPosAfterTargetMedia = (view, event) => {
const t = event.target && typeof event.target.closest === 'function' ? event.target : null;
if (!t) return null;
const mediaEl = t.closest('.ProseMirror img, .ProseMirror video, .ProseMirror audio, .ProseMirror figure[data-type="image"]');
if (!mediaEl) return null;
try {
const pos = view.posAtDOM(mediaEl, 0);
if (pos == null) return null;
const node = view.state.doc.nodeAt(pos);
if (!node) return null;
return pos + node.nodeSize; // строго "после"
} catch {
return null;
}
};
// <<< NEW
const handleFileUpload = async (file, view, position) => {
if (!extension.options.uploadUrl && !extension.options.uploadHandler) {
console.error('No upload URL or handler provided');
......@@ -74,30 +92,29 @@ export const DragAndDrop = Extension.create({
if (!result?.file_path) throw new Error('Invalid response from server');
const id = `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
if (nodeType === 'image') {
const id = `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const img = new Image();
img.src = result.file_path;
img.onload = () => {
// === НАЧАЛО: имитация getEditorDimensions ===
const editorContent = view.dom.closest('.atma-editor-content');
// === твой расчёт размеров — без изменений ===
const viewDom = view.dom;
const editorContent = viewDom.closest('.atma-editor-content');
const fullEditorWidth = editorContent?.clientWidth || 1000;
const editorStyles = editorContent ? window.getComputedStyle(editorContent) : {};
const paddingLeft = parseFloat(editorStyles.paddingLeft) || 0;
const paddingRight = parseFloat(editorStyles.paddingRight) || 0;
const availableEditorWidth = fullEditorWidth - paddingLeft - paddingRight;
let container = editorContent;
const container = editorContent;
const containerStyles = container ? window.getComputedStyle(container) : {};
const containerPaddingLeft = parseFloat(containerStyles.paddingLeft) || 0;
const containerPaddingRight = parseFloat(containerStyles.paddingRight) || 0;
const containerWidth = container.clientWidth - containerPaddingLeft - containerPaddingRight;
const containerWidth = container ? (container.clientWidth - containerPaddingLeft - containerPaddingRight) : availableEditorWidth;
const maxWidth = Math.min(containerWidth, availableEditorWidth);
// === КОНЕЦ: имитация getEditorDimensions ===
// === конец блока расчёта ===
let width = img.naturalWidth;
let height = img.naturalHeight;
......@@ -147,7 +164,8 @@ export const DragAndDrop = Extension.create({
return false;
}
const file = items.find(item => isRealFile(item.getAsFile()))?.getAsFile();
const maybeFile = items.find(item => isRealFile(item.getAsFile()));
const file = maybeFile?.getAsFile();
if (!file) return false;
event.preventDefault();
......@@ -157,7 +175,7 @@ export const DragAndDrop = Extension.create({
const handleDragStart = (event) => {
const target = event.target;
if (!target.matches('.ProseMirror img, .ProseMirror video, .ProseMirror audio')) return;
if (!target || !target.matches || !target.matches('.ProseMirror img, .ProseMirror video, .ProseMirror audio')) return;
const view = extension.editor.view;
const pos = view.posAtDOM(target, 0);
......@@ -182,6 +200,7 @@ export const DragAndDrop = Extension.create({
};
const handleDrop = (event) => {
// ВАЖНО: drop сработает только если во время dragover был preventDefault
event.preventDefault();
event.stopPropagation();
......@@ -193,15 +212,24 @@ export const DragAndDrop = Extension.create({
event.dataTransfer.clearData?.();
}
// ВНЕШНИЙ ДРОП ФАЙЛА
if (!dragState.active) {
const files = Array.from(event.dataTransfer?.files || [])
.filter(file => isRealFile(file));
if (files.length === 0) return;
handleFileUpload(files[0], view, dropPos || view.state.selection.from);
// >>> NEW: если бросили НА медиа — вставляем СТРОГО ПОСЛЕ него
const afterPos = getInsertPosAfterTargetMedia(view, event);
const insertPos = (afterPos != null)
? afterPos
: (dropPos || view.state.selection.from);
// <<< NEW
handleFileUpload(files[0], view, insertPos);
return;
}
// ВНУТРЕННЕЕ ПЕРЕТАСКИВАНИЕ
if (dropPos === undefined || dropPos === null) {
resetDragState();
return;
......@@ -229,8 +257,14 @@ export const DragAndDrop = Extension.create({
return;
}
let insertPos = dropPos > actualPos ? dropPos - actualNode.nodeSize : dropPos;
// >>> NEW: если бросили НА медиа — вставим ПОСЛЕ неё
const afterPos2 = getInsertPosAfterTargetMedia(view, event);
if (afterPos2 != null) insertPos = afterPos2;
// <<< NEW
const tr = state.tr.delete(actualPos, actualPos + actualNode.nodeSize);
const insertPos = dropPos > actualPos ? dropPos - actualNode.nodeSize : dropPos;
tr.insert(insertPos, actualNode);
view.dispatch(tr);
......@@ -252,13 +286,42 @@ export const DragAndDrop = Extension.create({
dragState.nodeId = null;
};
// >>> NEW: глобальный dragover (capture) — гасит дефолт, чтобы drop вообще пришёл
const globalDragOver = (event) => {
const target = event.target && typeof event.target.closest === 'function' ? event.target : null;
if (!target) return;
const inEditor = target.closest('.ProseMirror');
if (!inEditor) return;
const dt = event.dataTransfer;
if (!dt) return;
// В Chrome/Edge/Safari список типов хранится в .types
const types = Array.from(dt.types || []);
const hasFiles = types.includes('Files') || types.includes('application/x-moz-file');
if (hasFiles || dragState.active) {
event.preventDefault();
// event.stopPropagation();
try {
dt.dropEffect = dragState.active ? 'move' : 'copy';
} catch {}
}
};
// <<< NEW
const setupGlobalListeners = () => {
document.addEventListener('dragstart', handleDragStart, true);
// >>> NEW
document.addEventListener('dragover', globalDragOver, true);
// <<< NEW
document.addEventListener('drop', handleDrop, true);
document.addEventListener('dragend', resetDragState, true);
return () => {
document.removeEventListener('dragstart', handleDragStart, true);
document.removeEventListener('dragover', globalDragOver, true);
document.removeEventListener('drop', handleDrop, true);
document.removeEventListener('dragend', resetDragState, true);
};
......@@ -271,22 +334,40 @@ export const DragAndDrop = Extension.create({
handlePaste,
handleDOMEvents: {
dragstart: () => true,
// не трогаем встроенный drop редактора — глобальный ловит в capture
drop: () => true,
dragover: (view, event) => {
// Внутренний перетаскиваемый узел — оставляем как было, чтобы курсор был "move"
if (dragState.active) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
return true;
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
// ВАЖНО: возвращаем FALSE, чтобы Dropcursor тоже смог обновиться
return false;
}
// Внешние файлы — не перехватываем (Dropcursor посчитает позицию сам)
const items = event.dataTransfer?.items;
if (items && Array.from(items).some(item => isRealFile(item.getAsFile()))) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
return true;
const hasFile = items && Array.from(items).some(item => isRealFile(item.getAsFile()));
if (hasFile) {
// globalDragOver уже сделал preventDefault; просто не блокируем событие
return false;
}
return false;
},
// dragover: (view, event) => {
// if (dragState.active) {
// event.preventDefault();
// if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
// return true;
// }
//
// const items = event.dataTransfer?.items;
// if (items && Array.from(items).some(item => isRealFile(item.getAsFile()))) {
// event.preventDefault();
// if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy';
// return true;
// }
// return false;
// },
dragenter: (view, event) => {
if (
dragState.active ||
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment