Commit 888bc1b5 by Яков

update

parent 7549a85b
...@@ -9,12 +9,12 @@ const App = () => { ...@@ -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={`<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={"<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={"<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)=>{ onChange={(value)=>{
console.log(value); console.log(value);
}} }}
uploadOptions={{ uploadOptions={{
url: '/upload', url: 'https://cdn.atmaguru.online/upload/?sid=demo&md5=HwgSY1eMX_zlzfmKsBRHEg&expires=1755535222',
errorMessage: 'Загрузка временно невозможна' errorMessage: 'Загрузка временно невозможна'
}} }}
style={{ style={{
......
...@@ -7,7 +7,7 @@ module.exports = function(app) { ...@@ -7,7 +7,7 @@ module.exports = function(app) {
target: 'https://cdn.atmaguru.online', target: 'https://cdn.atmaguru.online',
changeOrigin: true, changeOrigin: true,
pathRewrite: { 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) => { onProxyReq: (proxyReq) => {
// Добавляем необходимые заголовки // Добавляем необходимые заголовки
......
{ {
"name": "react-ag-qeditor", "name": "react-ag-qeditor",
"version": "1.1.16", "version": "1.1.17",
"description": "WYSIWYG html editor", "description": "WYSIWYG html editor",
"author": "atma", "author": "atma",
"license": "MIT", "license": "MIT",
......
...@@ -50,6 +50,24 @@ export const DragAndDrop = Extension.create({ ...@@ -50,6 +50,24 @@ export const DragAndDrop = Extension.create({
return null; 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) => { const handleFileUpload = async (file, view, position) => {
if (!extension.options.uploadUrl && !extension.options.uploadHandler) { if (!extension.options.uploadUrl && !extension.options.uploadHandler) {
console.error('No upload URL or handler provided'); console.error('No upload URL or handler provided');
...@@ -74,30 +92,29 @@ export const DragAndDrop = Extension.create({ ...@@ -74,30 +92,29 @@ export const DragAndDrop = Extension.create({
if (!result?.file_path) throw new Error('Invalid response from server'); if (!result?.file_path) throw new Error('Invalid response from server');
const id = `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const id = `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
if (nodeType === 'image') { if (nodeType === 'image') {
const id = `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const img = new Image(); const img = new Image();
img.src = result.file_path; img.src = result.file_path;
img.onload = () => { 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 fullEditorWidth = editorContent?.clientWidth || 1000;
const editorStyles = editorContent ? window.getComputedStyle(editorContent) : {}; const editorStyles = editorContent ? window.getComputedStyle(editorContent) : {};
const paddingLeft = parseFloat(editorStyles.paddingLeft) || 0; const paddingLeft = parseFloat(editorStyles.paddingLeft) || 0;
const paddingRight = parseFloat(editorStyles.paddingRight) || 0; const paddingRight = parseFloat(editorStyles.paddingRight) || 0;
const availableEditorWidth = fullEditorWidth - paddingLeft - paddingRight; const availableEditorWidth = fullEditorWidth - paddingLeft - paddingRight;
let container = editorContent; const container = editorContent;
const containerStyles = container ? window.getComputedStyle(container) : {}; const containerStyles = container ? window.getComputedStyle(container) : {};
const containerPaddingLeft = parseFloat(containerStyles.paddingLeft) || 0; const containerPaddingLeft = parseFloat(containerStyles.paddingLeft) || 0;
const containerPaddingRight = parseFloat(containerStyles.paddingRight) || 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); const maxWidth = Math.min(containerWidth, availableEditorWidth);
// === КОНЕЦ: имитация getEditorDimensions === // === конец блока расчёта ===
let width = img.naturalWidth; let width = img.naturalWidth;
let height = img.naturalHeight; let height = img.naturalHeight;
...@@ -147,7 +164,8 @@ export const DragAndDrop = Extension.create({ ...@@ -147,7 +164,8 @@ export const DragAndDrop = Extension.create({
return false; 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; if (!file) return false;
event.preventDefault(); event.preventDefault();
...@@ -157,7 +175,7 @@ export const DragAndDrop = Extension.create({ ...@@ -157,7 +175,7 @@ export const DragAndDrop = Extension.create({
const handleDragStart = (event) => { const handleDragStart = (event) => {
const target = event.target; 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 view = extension.editor.view;
const pos = view.posAtDOM(target, 0); const pos = view.posAtDOM(target, 0);
...@@ -182,6 +200,7 @@ export const DragAndDrop = Extension.create({ ...@@ -182,6 +200,7 @@ export const DragAndDrop = Extension.create({
}; };
const handleDrop = (event) => { const handleDrop = (event) => {
// ВАЖНО: drop сработает только если во время dragover был preventDefault
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
...@@ -193,15 +212,24 @@ export const DragAndDrop = Extension.create({ ...@@ -193,15 +212,24 @@ export const DragAndDrop = Extension.create({
event.dataTransfer.clearData?.(); event.dataTransfer.clearData?.();
} }
// ВНЕШНИЙ ДРОП ФАЙЛА
if (!dragState.active) { if (!dragState.active) {
const files = Array.from(event.dataTransfer?.files || []) const files = Array.from(event.dataTransfer?.files || [])
.filter(file => isRealFile(file)); .filter(file => isRealFile(file));
if (files.length === 0) return; 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; return;
} }
// ВНУТРЕННЕЕ ПЕРЕТАСКИВАНИЕ
if (dropPos === undefined || dropPos === null) { if (dropPos === undefined || dropPos === null) {
resetDragState(); resetDragState();
return; return;
...@@ -229,8 +257,14 @@ export const DragAndDrop = Extension.create({ ...@@ -229,8 +257,14 @@ export const DragAndDrop = Extension.create({
return; 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 tr = state.tr.delete(actualPos, actualPos + actualNode.nodeSize);
const insertPos = dropPos > actualPos ? dropPos - actualNode.nodeSize : dropPos;
tr.insert(insertPos, actualNode); tr.insert(insertPos, actualNode);
view.dispatch(tr); view.dispatch(tr);
...@@ -252,13 +286,42 @@ export const DragAndDrop = Extension.create({ ...@@ -252,13 +286,42 @@ export const DragAndDrop = Extension.create({
dragState.nodeId = null; 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 = () => { const setupGlobalListeners = () => {
document.addEventListener('dragstart', handleDragStart, true); document.addEventListener('dragstart', handleDragStart, true);
// >>> NEW
document.addEventListener('dragover', globalDragOver, true);
// <<< NEW
document.addEventListener('drop', handleDrop, true); document.addEventListener('drop', handleDrop, true);
document.addEventListener('dragend', resetDragState, true); document.addEventListener('dragend', resetDragState, true);
return () => { return () => {
document.removeEventListener('dragstart', handleDragStart, true); document.removeEventListener('dragstart', handleDragStart, true);
document.removeEventListener('dragover', globalDragOver, true);
document.removeEventListener('drop', handleDrop, true); document.removeEventListener('drop', handleDrop, true);
document.removeEventListener('dragend', resetDragState, true); document.removeEventListener('dragend', resetDragState, true);
}; };
...@@ -271,22 +334,40 @@ export const DragAndDrop = Extension.create({ ...@@ -271,22 +334,40 @@ export const DragAndDrop = Extension.create({
handlePaste, handlePaste,
handleDOMEvents: { handleDOMEvents: {
dragstart: () => true, dragstart: () => true,
// не трогаем встроенный drop редактора — глобальный ловит в capture
drop: () => true, drop: () => true,
dragover: (view, event) => { dragover: (view, event) => {
// Внутренний перетаскиваемый узел — оставляем как было, чтобы курсор был "move"
if (dragState.active) { if (dragState.active) {
event.preventDefault(); event.preventDefault();
event.dataTransfer.dropEffect = 'move'; if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
return true; // ВАЖНО: возвращаем FALSE, чтобы Dropcursor тоже смог обновиться
return false;
} }
// Внешние файлы — не перехватываем (Dropcursor посчитает позицию сам)
const items = event.dataTransfer?.items; const items = event.dataTransfer?.items;
if (items && Array.from(items).some(item => isRealFile(item.getAsFile()))) { const hasFile = items && Array.from(items).some(item => isRealFile(item.getAsFile()));
event.preventDefault(); if (hasFile) {
event.dataTransfer.dropEffect = 'copy'; // globalDragOver уже сделал preventDefault; просто не блокируем событие
return true; return false;
} }
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) => { dragenter: (view, event) => {
if ( if (
dragState.active || 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