Commit 8d6f07f5 by Яков

add align text

parent f34c4c37
{
"name": "react-ag-qeditor",
"version": "1.0.92",
"version": "1.0.93",
"description": "WYSIWYG html editor",
"author": "atma",
"license": "MIT",
......
......@@ -4,7 +4,7 @@ import TipTapImage from "@tiptap/extension-image";
const MIN_WIDTH = 60;
const BORDER_COLOR = '#0096fd';
const ALIGN_OPTIONS = ['left', 'center', 'right'];
const ALIGN_OPTIONS = ['left', 'center', 'right', 'text'];
const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
const imgRef = useRef(null);
......@@ -20,7 +20,6 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
aspectRatio: 1
});
// Добавляем прозрачный нулевой пробел после изображения (исправленная версия)
useEffect(() => {
if (!editor?.isEditable || typeof getPos !== 'function') return;
......@@ -30,16 +29,13 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
if (typeof pos !== 'number' || pos < 0) return;
const doc = editor.state.doc;
const insertPos = pos + 1; // Позиция после изображения
const insertPos = pos + 1;
// Проверяем, что позиция существует в документе
if (insertPos >= doc.content.size) return;
// Проверяем, не добавлен ли уже нулевой пробел
const nextNode = doc.nodeAt(insertPos);
if (nextNode?.textContent === '\u200B') return;
// Вставляем пробел с небольшой задержкой для стабильности
setTimeout(() => {
if (editor.isDestroyed) return;
editor.commands.insertContentAt(insertPos, {
......@@ -56,7 +52,6 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
return () => clearTimeout(timer);
}, [editor, getPos]);
// Инициализация размеров (исправленная версия)
useEffect(() => {
if (!imgRef.current || isInitialized.current) return;
......@@ -65,7 +60,6 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
const width = node.attrs.width || imgRef.current.naturalWidth;
const height = node.attrs.height || imgRef.current.naturalHeight;
// Проверяем валидность размеров перед обновлением
if (width > 0 && height > 0) {
updateAttributes({
width: Math.round(width),
......@@ -78,16 +72,13 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
}
};
// Если изображение уже загружено
if (imgRef.current.complete) {
initImageSize();
} else {
// Если еще загружается - ждем события onLoad
imgRef.current.onload = initImageSize;
}
return () => {
// Очищаем обработчик при размонтировании
if (imgRef.current) {
imgRef.current.onload = null;
}
......@@ -98,6 +89,7 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
e.preventDefault();
e.stopPropagation();
const nodePos = typeof getPos === 'function' ? getPos() : null;
const currentWidth = node.attrs.width || imgRef.current.naturalWidth;
const currentHeight = node.attrs.height || imgRef.current.naturalHeight;
......@@ -107,11 +99,22 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
startX: e.clientX,
startY: e.clientY,
aspectRatio: currentWidth / currentHeight,
direction
direction,
nodePos
};
const onMouseMove = (e) => {
const { startWidth, startHeight, startX, startY, aspectRatio, direction } = resizeData.current;
const { startWidth, startHeight, startX, startY, aspectRatio, direction, nodePos } = resizeData.current;
// Проверяем, что узел все еще существует
if (typeof nodePos === 'number') {
try {
const resolvedPos = editor.view.state.doc.resolve(nodePos);
if (!resolvedPos?.nodeAfter) return;
} catch {
return;
}
}
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
......@@ -119,20 +122,16 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
let newWidth, newHeight;
if (node.attrs.align === 'center') {
// Особый случай для центрированного изображения
if (direction.includes('n') || direction.includes('s')) {
// Только вертикальный ресайз с сохранением пропорций
const scale = direction.includes('s') ? 1 : -1;
newHeight = Math.max(startHeight + deltaY * scale, MIN_WIDTH);
newWidth = Math.round(newHeight * aspectRatio);
} else {
// Горизонтальный ресайз с сохранением пропорций
const scale = direction.includes('e') ? 1 : -1;
newWidth = Math.max(startWidth + deltaX * scale, MIN_WIDTH);
newHeight = Math.round(newWidth / aspectRatio);
}
} else {
// Обычный ресайз для других выравниваний
if (direction.includes('e') || direction.includes('w')) {
const scale = direction.includes('e') ? 1 : -1;
newWidth = Math.max(startWidth + deltaX * scale, MIN_WIDTH);
......@@ -144,16 +143,39 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
}
}
updateAttributes({
// Принудительное обновление с новым transaction
const { state, dispatch } = editor.view;
const tr = state.tr.setNodeMarkup(nodePos, undefined, {
...node.attrs,
width: newWidth,
height: newHeight
});
dispatch(tr);
};
const onMouseUp = () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
editor.commands.focus();
// Обновляем атрибуты в конце ресайза для синхронизации
if (typeof resizeData.current.nodePos === 'number') {
updateAttributes({
width: node.attrs.width,
height: node.attrs.height
});
}
if (!editor.isDestroyed && editor.view) {
setTimeout(() => {
try {
editor.commands.focus();
// Принудительное обновление представления
editor.view.dispatch(editor.view.state.tr.setMeta('forceUpdate', true));
} catch (error) {
console.warn('Focus error after resize:', error);
}
}, 50);
}
};
window.addEventListener('mousemove', onMouseMove);
......@@ -189,6 +211,14 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
marginRight: 'auto',
textAlign: 'center'
};
case 'text':
return {
...baseStyle,
display: 'inline-block',
float: 'none',
margin: '0 0.2rem',
verticalAlign: 'middle'
};
case 'wrap-left':
return { ...baseStyle, float: 'left', margin: '0 1rem 1rem 0', shapeOutside: 'margin-box' };
case 'wrap-right':
......@@ -205,7 +235,8 @@ const ResizableImageTemplate = ({ node, updateAttributes, editor, getPos }) => {
display: 'block',
cursor: 'default',
userSelect: 'none',
margin: node.attrs.align === 'center' ? '0 auto' : '0'
margin: node.attrs.align === 'center' ? '0 auto' : '0',
verticalAlign: node.attrs.align === 'text' ? 'middle' : 'top'
});
return (
......@@ -356,17 +387,14 @@ const ResizableImageExtension = TipTapImage.extend({
},
renderHTML({ HTMLAttributes }) {
// Получаем align из атрибутов, учитывая data-align как fallback
const align = HTMLAttributes.align ||
HTMLAttributes['data-align'] ||
'left';
// Определяем, нужно ли применять float
const floatValue = align.startsWith('wrap-') ? align.split('-')[1] :
['left', 'right'].includes(align) ? align :
'none';
// Определяем margin в зависимости от выравнивания
let marginValue;
switch(align) {
case 'left':
......@@ -380,6 +408,9 @@ const ResizableImageExtension = TipTapImage.extend({
case 'center':
marginValue = '0.5rem auto';
break;
case 'text':
marginValue = '0 0.2rem';
break;
default:
marginValue = '0';
}
......@@ -387,13 +418,13 @@ const ResizableImageExtension = TipTapImage.extend({
return ['span', {
'data-type': 'resizable-image',
'data-image-wrapper': true,
'data-align': align, // Сохраняем значение align в data-атрибуте
'data-align': align,
style: `
display: ${align === 'center' ? 'block' : 'inline-block'};
float: ${floatValue};
margin: ${marginValue};
shape-outside: ${align.startsWith('wrap-') ? 'margin-box' : 'none'};
vertical-align: top;
vertical-align: ${align === 'text' ? 'middle' : 'top'};
position: relative;
${align === 'center' ? 'width: 100%; text-align: center;' : ''}
`
......@@ -405,8 +436,8 @@ const ResizableImageExtension = TipTapImage.extend({
margin: ${align === 'center' ? '0 auto' : '0'};
max-width: 100%;
height: auto;
vertical-align: ${align === 'text' ? 'middle' : 'top'};
`,
// Убедимся, что align также передаётся в img
'data-align': align
}]];
},
......@@ -418,7 +449,7 @@ const ResizableImageExtension = TipTapImage.extend({
inline: true,
group: 'inline',
draggable: true,
selectable: false // Важно отключить выделение изображения
selectable: false
});
export default ResizableImageExtension;
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