This commit is contained in:
Tomas Dvorak
2025-10-29 21:20:16 +01:00
parent 823fabee02
commit 16e4533202
61 changed files with 2308 additions and 942 deletions
@@ -78,6 +78,7 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
const onChangeRef = useRef(onChange);
const selectedImageIdRef = useRef<string | null>(null);
const selectImageByIdRef = useRef<(id: string) => void>(() => {});
const toolbarDragRef = useRef<{ active: boolean; startX: number; startY: number; startLeft: number; startTop: number }>({ active: false, startX: 0, startY: 0, startLeft: 0, startTop: 0 });
const [isMounted, setIsMounted] = useState(false);
// Ensure component is mounted before rendering Quill
@@ -401,6 +402,7 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
let startX = 0;
let startY = 0;
let startWidth = 0;
let rafId = 0;
const createResizeHandle = (img: HTMLImageElement) => {
removeResizeHandle();
@@ -672,23 +674,13 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
const scrollTop = editor.root.scrollTop;
const scrollLeft = editor.root.scrollLeft;
// Position toolbar to the right of the image, or left if not enough space
// Place toolbar close to the image (top-right corner inside the image area when possible)
const toolbarWidth = 380;
const spaceOnRight = window.innerWidth - rect.right;
const positionRight = spaceOnRight > toolbarWidth + 20;
let leftPos = positionRight
? rect.right - editorRect.left + scrollLeft + 10
: rect.left - editorRect.left + scrollLeft - toolbarWidth - 10;
// Ensure toolbar is visible horizontally
leftPos = Math.max(10, Math.min(leftPos, editorRect.width - toolbarWidth - 10));
// Position vertically aligned with top of image
let topPos = rect.top - editorRect.top + scrollTop;
// Ensure toolbar is visible vertically
topPos = Math.max(10, topPos);
const margin = 8;
let leftPos = rect.left - editorRect.left + scrollLeft + (rect.width > toolbarWidth + margin ? (rect.width - toolbarWidth - margin) : margin);
leftPos = Math.max(margin, Math.min(leftPos, editorRect.width - toolbarWidth - margin));
let topPos = rect.top - editorRect.top + scrollTop + margin;
topPos = Math.max(margin, topPos);
setToolbarPosition({
top: topPos,
@@ -895,16 +887,18 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
// Handle scroll to update resize handle position
const handleScroll = () => {
if (selectedImage && resizeHandle) {
const rect = selectedImage.getBoundingClientRect();
if (!selectedImage || !resizeHandle) return;
if (rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => {
const rect = selectedImage!.getBoundingClientRect();
const editorRect = editor.root.getBoundingClientRect();
const scrollTop = editor.root.scrollTop;
const scrollLeft = editor.root.scrollLeft;
resizeHandle.style.left = `${rect.left - editorRect.left + scrollLeft}px`;
resizeHandle.style.top = `${rect.top - editorRect.top + scrollTop}px`;
resizeHandle.style.width = `${rect.width}px`;
resizeHandle.style.height = `${rect.height}px`;
}
resizeHandle!.style.left = `${rect.left - editorRect.left + scrollLeft}px`;
resizeHandle!.style.top = `${rect.top - editorRect.top + scrollTop}px`;
resizeHandle!.style.width = `${rect.width}px`;
resizeHandle!.style.height = `${rect.height}px`;
});
};
// Prevent default drag behavior on images
@@ -934,6 +928,7 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
document.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('resize', handleScroll);
document.removeEventListener('scroll', handleScroll, true);
if (rafId) cancelAnimationFrame(rafId);
removeResizeHandle();
deselectImage();
};
@@ -1047,8 +1042,6 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
const editor = quillRef.current?.getEditor();
if (editor) {
onChangeRef.current(editor.root.innerHTML);
// Force overlay reposition
try { editor.root.dispatchEvent(new Event('scroll')); } catch {}
}
reselectAfterContentUpdate();
@@ -1111,7 +1104,6 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
setManualWidth(finalWidth.toString());
if (editor) {
onChangeRef.current(editor.root.innerHTML);
try { editor.root.dispatchEvent(new Event('scroll')); } catch {}
}
// Keep selection active for subsequent operations (e.g., 50% → 75%)
reselectAfterContentUpdate();
@@ -1132,7 +1124,6 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
setManualWidth('');
if (editor) {
onChangeRef.current(editor.root.innerHTML);
try { editor.root.dispatchEvent(new Event('scroll')); } catch {}
}
reselectAfterContentUpdate();
toast({ title: 'Šířka resetována', status: 'info', duration: 1200 });
@@ -1190,7 +1181,7 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
let cleaned = DOMPurify.sanitize(content, {
USE_PROFILES: { html: true },
ADD_TAGS: ['iframe'],
ADD_ATTR: ['target', 'rel', 'allow', 'allowfullscreen', 'style', 'data-filters'],
ADD_ATTR: ['target', 'rel', 'allow', 'allowfullscreen', 'style', 'data-filters', 'data-img-id'],
});
// Replace white and very light colors with dark colors for visibility
@@ -1236,7 +1227,7 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
borderWidth="1px"
borderColor={borderColor}
borderRadius="md"
overflow="hidden"
overflow="visible"
bg={bgColor}
sx={{
'.ql-toolbar': {
@@ -1477,7 +1468,33 @@ const CustomRichEditor: React.FC<CustomRichEditorProps> = ({
>
<VStack align="stretch" spacing={3}>
{/* Toolbar Header */}
<HStack justify="space-between">
<HStack
justify="space-between"
onMouseDown={(e) => {
if (e.button !== 0) return;
e.preventDefault();
e.stopPropagation();
toolbarDragRef.current.active = true;
toolbarDragRef.current.startX = e.clientX;
toolbarDragRef.current.startY = e.clientY;
toolbarDragRef.current.startLeft = toolbarPosition.left;
toolbarDragRef.current.startTop = toolbarPosition.top;
const onMove = (ev: MouseEvent) => {
if (!toolbarDragRef.current.active) return;
const dx = ev.clientX - toolbarDragRef.current.startX;
const dy = ev.clientY - toolbarDragRef.current.startY;
setToolbarPosition((pos) => ({ top: Math.max(0, toolbarDragRef.current.startTop + dy), left: Math.max(0, toolbarDragRef.current.startLeft + dx) }));
};
const onUp = () => {
toolbarDragRef.current.active = false;
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}}
cursor="move"
>
<HStack spacing={2}>
<Settings size={16} />
<Text fontWeight="bold" fontSize="sm">Úprava obrázku</Text>