react-lite-rich-text-editor 1.1.4 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +361 -111
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +361 -111
- package/dist/index.esm.js.map +1 -1
- package/package.json +8 -1
package/dist/index.esm.js
CHANGED
|
@@ -388,7 +388,7 @@ function styleInject(css, ref) {
|
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
var css_248z = ".rte-container{background-color:#fff;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,.05);overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1)}.rte-container:focus-within{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-toolbar{align-items:center;background-color:#f9fafb;border-bottom:1px solid #f3f4f6;display:flex;flex-wrap:wrap;gap:4px;padding:8px}.rte-toolbar-button{align-items:center;background:transparent;border:none;border-radius:6px;color:#4b5563;cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;transition:all .15s ease;width:32px}.rte-toolbar-button:hover{background-color:#f3f4f6;color:#111827}.rte-toolbar-button.active{background-color:#eff6ff;color:#2563eb}.rte-toolbar-button:disabled{cursor:not-allowed;opacity:.5}.rte-toolbar-button-danger:hover{background-color:#fef2f2!important;color:#dc2626!important}.rte-toolbar-select{background-color:#fff;border:1px solid #e5e7eb;border-radius:6px;color:#374151;cursor:pointer;font-size:14px;height:32px;outline:none;padding:0 8px;transition:border-color .15s ease}.rte-toolbar-select:hover{border-color:#d1d5db}.rte-toolbar-select:focus{border-color:#3b82f6}.rte-color-picker-label{align-items:center;border-radius:6px;cursor:pointer;display:flex;height:32px;justify-content:center;position:relative;transition:background-color .15s ease;width:32px}.rte-color-picker-label:hover{background-color:#f3f4f6}.rte-color-input{cursor:pointer;height:100%;inset:0;opacity:0;position:absolute;width:100%}.rte-content{color:#1f2937;font-family:inherit;font-size:16px;line-height:1.6;min-height:150px;outline:none;overflow-y:auto;padding:12px;word-break:break-word}.rte-content ul{list-style-type:disc;margin-left:1.5rem}.rte-content ol{list-style-type:decimal;margin-left:1.5rem}.rte-content img{border-radius:8px;display:block;height:auto;max-width:100%}.rte-content table{border-collapse:collapse;margin:16px 0;width:100%}.rte-content td,.rte-content th{border:1px solid #e5e7eb;min-width:40px;padding:12px;word-break:break-word}.video-container{border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1);height:0;margin:16px 0;overflow:hidden;padding-bottom:56.25%;position:relative}.video-container iframe{height:100%;left:0;position:absolute;top:0;width:100%}.rte-modal-overlay{align-items:center;animation:rte-fade-in .2s ease-out;backdrop-filter:blur(4px);background-color:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;position:fixed;z-index:9999}.rte-modal{animation:rte-zoom-in .2s ease-out;background-color:#fff;border:1px solid #f3f4f6;border-radius:16px;box-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 10px 10px -5px rgba(0,0,0,.04);display:flex;flex-direction:column;gap:16px;max-width:400px;padding:0;width:100%}.rte-modal-title{color:#111827;flex:1;font-size:18px;font-weight:600;margin:0;text-align:center}.rte-modal-header{align-items:center;border-bottom:1px solid #f3f4f6;display:flex;justify-content:space-between;padding:20px 24px 16px}.rte-form-group{display:flex;flex-direction:column;gap:8px;padding:16px 24px}.rte-label{color:#374151;font-size:14px;font-weight:600}.rte-input{border:1px solid #d1d5db;border-radius:8px;outline:none;padding:8px 12px;transition:all .15s ease;width:93%}.rte-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-modal-actions{border-top:1px solid #f3f4f6;display:flex;gap:12px;justify-content:flex-end;padding:16px 24px 20px}.rte-button{border:none;border-radius:8px;cursor:pointer;font-weight:500;padding:8px 16px;transition:all .15s ease}.rte-button-secondary{background-color:#f3f4f6;color:#4b5563}.rte-button-secondary:hover{background-color:#e5e7eb}.rte-button-primary{background-color:#2563eb;box-shadow:0 1px 2px rgba(0,0,0,.05);color:#fff}.rte-button-primary:hover{background-color:#1d4ed8}.rte-button-primary:disabled{cursor:not-allowed;opacity:.5}.rte-spinner-container{align-items:center;display:flex;justify-content:center;padding:16px}.rte-spinner{animation:rte-spin .8s linear infinite;border:3px solid #eff6ff;border-radius:50%;border-top-color:#3b82f6;height:32px;width:32px}@keyframes rte-spin{to{transform:rotate(1turn)}}@keyframes rte-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rte-zoom-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.image-container{display:
|
|
391
|
+
var css_248z = ".rte-container{background-color:#fff;border:1px solid #e5e7eb;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,.05);overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1)}.rte-container:focus-within{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-toolbar{align-items:center;background-color:#f9fafb;border-bottom:1px solid #f3f4f6;display:flex;flex-wrap:wrap;gap:4px;padding:8px}.rte-toolbar-button{align-items:center;background:transparent;border:none;border-radius:6px;color:#4b5563;cursor:pointer;display:flex;height:32px;justify-content:center;padding:0;transition:all .15s ease;width:32px}.rte-toolbar-button:hover{background-color:#f3f4f6;color:#111827}.rte-toolbar-button.active{background-color:#eff6ff;color:#2563eb}.rte-toolbar-button:disabled{cursor:not-allowed;opacity:.5}.rte-toolbar-button-danger:hover{background-color:#fef2f2!important;color:#dc2626!important}.rte-toolbar-select{background-color:#fff;border:1px solid #e5e7eb;border-radius:6px;color:#374151;cursor:pointer;font-size:14px;height:32px;outline:none;padding:0 8px;transition:border-color .15s ease}.rte-toolbar-select:hover{border-color:#d1d5db}.rte-toolbar-select:focus{border-color:#3b82f6}.rte-color-picker-label{align-items:center;border-radius:6px;cursor:pointer;display:flex;height:32px;justify-content:center;position:relative;transition:background-color .15s ease;width:32px}.rte-color-picker-label:hover{background-color:#f3f4f6}.rte-color-input{cursor:pointer;height:100%;inset:0;opacity:0;position:absolute;width:100%}.rte-content{color:#1f2937;font-family:inherit;font-size:16px;line-height:1.6;min-height:150px;outline:none;overflow-y:auto;padding:12px;word-break:break-word}.rte-content ul{list-style-type:disc;margin-left:1.5rem}.rte-content ol{list-style-type:decimal;margin-left:1.5rem}.rte-content img{border-radius:8px;display:block;height:auto;max-width:100%}.rte-content table{border-collapse:collapse;margin:16px 0;width:100%}.rte-content td,.rte-content th{border:1px solid #e5e7eb;min-width:40px;padding:12px;word-break:break-word}.video-container{border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1);height:0;margin:16px 0;max-width:100%;overflow:hidden;padding-bottom:56.25%;position:relative}.video-container iframe{height:100%;left:0;position:absolute;top:0;width:100%}.rte-modal-overlay{align-items:center;animation:rte-fade-in .2s ease-out;backdrop-filter:blur(4px);background-color:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;position:fixed;z-index:9999}.rte-modal{animation:rte-zoom-in .2s ease-out;background-color:#fff;border:1px solid #f3f4f6;border-radius:16px;box-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 10px 10px -5px rgba(0,0,0,.04);display:flex;flex-direction:column;gap:16px;max-width:400px;padding:0;width:100%}.rte-modal-title{color:#111827;flex:1;font-size:18px;font-weight:600;margin:0;text-align:center}.rte-modal-header{align-items:center;border-bottom:1px solid #f3f4f6;display:flex;justify-content:space-between;padding:20px 24px 16px}.rte-form-group{display:flex;flex-direction:column;gap:8px;padding:16px 24px}.rte-label{color:#374151;font-size:14px;font-weight:600}.rte-input{border:1px solid #d1d5db;border-radius:8px;outline:none;padding:8px 12px;transition:all .15s ease;width:93%}.rte-input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.rte-modal-actions{border-top:1px solid #f3f4f6;display:flex;gap:12px;justify-content:flex-end;padding:16px 24px 20px}.rte-button{border:none;border-radius:8px;cursor:pointer;font-weight:500;padding:8px 16px;transition:all .15s ease}.rte-button-secondary{background-color:#f3f4f6;color:#4b5563}.rte-button-secondary:hover{background-color:#e5e7eb}.rte-button-primary{background-color:#2563eb;box-shadow:0 1px 2px rgba(0,0,0,.05);color:#fff}.rte-button-primary:hover{background-color:#1d4ed8}.rte-button-primary:disabled{cursor:not-allowed;opacity:.5}.rte-spinner-container{align-items:center;display:flex;justify-content:center;padding:16px}.rte-spinner{animation:rte-spin .8s linear infinite;border:3px solid #eff6ff;border-radius:50%;border-top-color:#3b82f6;height:32px;width:32px}@keyframes rte-spin{to{transform:rotate(1turn)}}@keyframes rte-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rte-zoom-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.image-container{display:block;line-height:0;margin:16px 0;max-width:100%;width:fit-content}.image-container.image-align-center{margin-left:auto;margin-right:auto}.image-container.image-align-left{margin-left:0;margin-right:auto}.image-container.image-align-right{margin-left:auto;margin-right:0}.image-media-frame{display:block;line-height:0;max-width:100%;position:relative;width:fit-content}.image-media-frame img{border-radius:12px;display:block;height:auto;margin:0;max-width:100%;width:auto}.image-media-frame[style*=width] img{width:100%}.image-media-frame[data-explicit-height=true] img,.image-media-frame[style*=height] img{height:100%;object-fit:contain}.image-container.image-small .image-media-frame img{width:50%!important}.image-delete-button{align-items:center;background:#ef4444;border:3px solid #fff;border-radius:9999px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#fff;cursor:pointer;display:none;font-size:18px;font-weight:700;height:26px;justify-content:center;line-height:1;padding:0;pointer-events:none;position:absolute;right:0;top:0;transform:translate(50%,-50%);transition:all .2s cubic-bezier(.4,0,.2,1);width:26px;z-index:50}.rte-content.rte-is-editable.rte-is-focused .image-delete-button,.rte-content.rte-is-editable.rte-is-focused .video-delete-button{display:flex;pointer-events:auto}.image-delete-button:hover{background:#b91c1c;box-shadow:0 10px 15px -3px rgba(0,0,0,.1);transform:translate(50%,-50%) scale(1.1)}.media-resize-handles{inset:0;pointer-events:none;position:absolute;z-index:55}.media-resize-handle{background:#dbeafe;border:2px solid #fff;border-radius:4px;box-shadow:0 1px 3px rgba(15,23,42,.12);pointer-events:auto;position:absolute;z-index:60}.media-resize-handle:hover{background:#bfdbfe}.media-resize-handle-left,.media-resize-handle-right{cursor:ew-resize;height:28px;top:50%;transform:translateY(-50%);width:10px}.media-resize-handle-right{right:-5px}.media-resize-handle-left{left:-5px}.media-resize-handle-bottom,.media-resize-handle-top{cursor:ns-resize;height:10px;left:50%;transform:translateX(-50%);width:28px}.media-resize-handle-top{top:-5px}.media-resize-handle-bottom{bottom:-5px}.video-container .media-resize-handle-right{right:4px}.video-container .media-resize-handle-left{left:4px}.video-container .media-resize-handle-top{top:4px}.video-container .media-resize-handle-bottom{bottom:4px}.video-container .video-delete-button{z-index:70}.rte-table-delete-btn,.rte-table-delete-hover{align-items:center;display:flex;justify-content:center}.rte-table-delete-btn{background:#fff;border:1px solid #ef4444;border-radius:6px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);color:#ef4444;cursor:pointer;height:28px;padding:0;transition:all .2s ease;width:28px}.rte-table-delete-btn:hover{background:#ef4444;color:#fff;transform:scale(1.1)}.rte-table-delete-btn:active{transform:scale(.95)}.rte-image-toolbar{background:#fff!important;border:1px solid #e5e7eb!important;border-radius:8px!important;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)!important;display:flex;gap:4px!important;padding:4px!important;pointer-events:auto!important}.rte-image-toolbar button{align-items:center;background:transparent;border:none;border-radius:4px;color:#4b5563;cursor:pointer;display:flex;font-size:11px;font-weight:600;height:28px;justify-content:center;min-width:32px;padding:0 4px;transition:all .15s ease}.rte-image-toolbar button:hover{background-color:#f3f4f6;color:#111827}.rte-image-toolbar button.danger{color:#ef4444!important}.rte-image-toolbar button.danger:hover{background-color:#fef2f2!important}.image-container:after{clear:both;content:\"\";display:table}.rte-footer{background-color:#fcfcfd;border-top:1px solid #f3f4f6;display:flex;justify-content:flex-end;padding:6px 16px;user-select:none}.rte-footer-content{align-items:center;color:#9ca3af;display:flex;font-size:11px;gap:10px;letter-spacing:.025em}.rte-footer-separator{color:#e5e7eb;font-size:14px;line-height:1}.rte-footer-item b{color:#6b7280;font-weight:600}";
|
|
392
392
|
styleInject(css_248z);
|
|
393
393
|
|
|
394
394
|
// Helper functions for HTML escaping
|
|
@@ -416,9 +416,6 @@ function RichTextEditor({
|
|
|
416
416
|
maxHeight,
|
|
417
417
|
onImageUpload
|
|
418
418
|
}) {
|
|
419
|
-
if (isLoading) {
|
|
420
|
-
return /*#__PURE__*/React.createElement(Spinner, null);
|
|
421
|
-
}
|
|
422
419
|
const editorRef = useRef(null);
|
|
423
420
|
const fileInputRef = useRef(null);
|
|
424
421
|
const scrollTopRef = useRef(0);
|
|
@@ -428,6 +425,7 @@ function RichTextEditor({
|
|
|
428
425
|
const [linkText, setLinkText] = useState("");
|
|
429
426
|
const selectionRangeRef = useRef(null);
|
|
430
427
|
const [editable, setEditable] = useState(initialEditable);
|
|
428
|
+
const [editorFocused, setEditorFocused] = useState(false);
|
|
431
429
|
const lastSynchronizedHtmlRef = useRef("");
|
|
432
430
|
useEffect(() => {
|
|
433
431
|
setEditable(initialEditable);
|
|
@@ -533,40 +531,25 @@ function RichTextEditor({
|
|
|
533
531
|
window.removeEventListener('wheel', handleWheel);
|
|
534
532
|
};
|
|
535
533
|
}, [imageModalOpen]);
|
|
536
|
-
useEffect(() => {
|
|
537
|
-
const handleClick = e => {
|
|
538
|
-
// Trigger selection update for toolbar reactivity
|
|
539
|
-
setSelectionVersion(v => v + 1);
|
|
540
|
-
const deleteBtn = e.target.closest('button[title="Remove image"]');
|
|
541
|
-
if (deleteBtn && editable) {
|
|
542
|
-
e.preventDefault();
|
|
543
|
-
e.stopPropagation();
|
|
544
|
-
const wrapper = deleteBtn.closest('.image-container');
|
|
545
|
-
if (wrapper && wrapper.parentNode) {
|
|
546
|
-
wrapper.parentNode.removeChild(wrapper);
|
|
547
|
-
triggerChange && triggerChange();
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
const editor = editorRef.current;
|
|
552
|
-
if (editor) {
|
|
553
|
-
editor.addEventListener('click', handleClick);
|
|
554
|
-
return () => {
|
|
555
|
-
editor.removeEventListener('click', handleClick);
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
// Removed dependency on editable to minimize listener churn
|
|
559
|
-
}, []);
|
|
560
534
|
useEffect(() => {
|
|
561
535
|
if (editorRef.current && value && value !== lastSynchronizedHtmlRef.current) {
|
|
562
|
-
requestAnimationFrame(() =>
|
|
536
|
+
requestAnimationFrame(() => processExistingMedia(editorRef.current));
|
|
563
537
|
}
|
|
564
538
|
}, [value]);
|
|
565
539
|
|
|
566
540
|
// Runs whenever editable changes (toggles delete icon visibility)
|
|
567
541
|
useEffect(() => {
|
|
568
|
-
|
|
542
|
+
if (!editable) {
|
|
543
|
+
setEditorFocused(false);
|
|
544
|
+
}
|
|
545
|
+
processExistingMedia(editorRef.current);
|
|
569
546
|
}, [editable]);
|
|
547
|
+
useEffect(() => {
|
|
548
|
+
if (!editorRef.current) return;
|
|
549
|
+
editorRef.current.querySelectorAll(".image-container, .video-container").forEach(container => {
|
|
550
|
+
updateMediaControlVisibility(container);
|
|
551
|
+
});
|
|
552
|
+
}, [editorFocused, editable]);
|
|
570
553
|
useEffect(() => {
|
|
571
554
|
// Only update if value is different from our last known synced state
|
|
572
555
|
if (value && value !== lastSynchronizedHtmlRef.current) {
|
|
@@ -590,6 +573,7 @@ function RichTextEditor({
|
|
|
590
573
|
if (editorRef.current && editorRef.current.innerHTML !== newContent) {
|
|
591
574
|
editorRef.current.innerHTML = newContent;
|
|
592
575
|
}
|
|
576
|
+
requestAnimationFrame(() => processExistingMedia(editorRef.current));
|
|
593
577
|
updateMetrics();
|
|
594
578
|
}
|
|
595
579
|
} catch (e) {
|
|
@@ -604,6 +588,17 @@ function RichTextEditor({
|
|
|
604
588
|
}
|
|
605
589
|
}
|
|
606
590
|
}, [value, initialEditable, updateMetrics]);
|
|
591
|
+
const processExistingMedia = container => {
|
|
592
|
+
if (!container) return;
|
|
593
|
+
processExistingImages(container);
|
|
594
|
+
processExistingVideos(container);
|
|
595
|
+
};
|
|
596
|
+
const getCleanHtml = () => {
|
|
597
|
+
if (!editorRef.current) return "";
|
|
598
|
+
const clone = editorRef.current.cloneNode(true);
|
|
599
|
+
stripEditorChrome(clone);
|
|
600
|
+
return clone.innerHTML;
|
|
601
|
+
};
|
|
607
602
|
|
|
608
603
|
// Trigger change manually
|
|
609
604
|
const triggerChange = useCallback(() => {
|
|
@@ -647,14 +642,220 @@ function RichTextEditor({
|
|
|
647
642
|
return hex.length === 1 ? "0" + hex : hex;
|
|
648
643
|
}).join("");
|
|
649
644
|
}
|
|
645
|
+
const getColorAtCursor = () => {
|
|
646
|
+
const sel = window.getSelection();
|
|
647
|
+
if (!sel || !sel.rangeCount || !editorRef.current) return null;
|
|
648
|
+
let node = sel.anchorNode;
|
|
649
|
+
if (node.nodeType === 3) node = node.parentNode;
|
|
650
|
+
while (node && node !== editorRef.current) {
|
|
651
|
+
if (node.nodeType === 1) {
|
|
652
|
+
if (node.style && node.style.color) {
|
|
653
|
+
return rgbToHex(node.style.color);
|
|
654
|
+
}
|
|
655
|
+
if (node.tagName === "FONT" && node.getAttribute("color")) {
|
|
656
|
+
return node.getAttribute("color");
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
node = node.parentNode;
|
|
660
|
+
}
|
|
661
|
+
const computedColor = window.getComputedStyle(sel.anchorNode.nodeType === 3 ? sel.anchorNode.parentNode : sel.anchorNode).color;
|
|
662
|
+
return rgbToHex(computedColor);
|
|
663
|
+
};
|
|
664
|
+
const stripEditorChrome = root => {
|
|
665
|
+
root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handles, .media-resize-handle").forEach(element => element.remove());
|
|
666
|
+
return root;
|
|
667
|
+
};
|
|
668
|
+
const getMediaSizeLimits = () => {
|
|
669
|
+
const maxWidth = editorRef.current ? editorRef.current.getBoundingClientRect().width - 24 : 800;
|
|
670
|
+
return {
|
|
671
|
+
minWidth: 120,
|
|
672
|
+
minHeight: 80,
|
|
673
|
+
maxWidth,
|
|
674
|
+
maxHeight: 720
|
|
675
|
+
};
|
|
676
|
+
};
|
|
677
|
+
const ensureImageMediaFrame = imageContainer => {
|
|
678
|
+
if (!imageContainer) return null;
|
|
679
|
+
let frame = imageContainer.querySelector(":scope > .image-media-frame");
|
|
680
|
+
if (frame) return frame;
|
|
681
|
+
frame = document.createElement("div");
|
|
682
|
+
frame.className = "image-media-frame";
|
|
683
|
+
["width", "height", "marginLeft", "marginTop", "maxWidth"].forEach(prop => {
|
|
684
|
+
if (imageContainer.style[prop]) {
|
|
685
|
+
frame.style[prop] = imageContainer.style[prop];
|
|
686
|
+
imageContainer.style[prop] = "";
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
if (imageContainer.dataset.explicitHeight) {
|
|
690
|
+
frame.dataset.explicitHeight = imageContainer.dataset.explicitHeight;
|
|
691
|
+
delete imageContainer.dataset.explicitHeight;
|
|
692
|
+
}
|
|
693
|
+
const children = Array.from(imageContainer.children);
|
|
694
|
+
imageContainer.appendChild(frame);
|
|
695
|
+
children.forEach(child => frame.appendChild(child));
|
|
696
|
+
return frame;
|
|
697
|
+
};
|
|
698
|
+
const getImageMediaTarget = imageContainer => ensureImageMediaFrame(imageContainer) || imageContainer;
|
|
699
|
+
const applyImageMediaSize = (frame, width, height, edge) => {
|
|
700
|
+
const img = frame.querySelector("img");
|
|
701
|
+
const isVertical = edge === "top" || edge === "bottom";
|
|
702
|
+
frame.style.width = `${Math.round(width)}px`;
|
|
703
|
+
frame.style.maxWidth = "100%";
|
|
704
|
+
if (isVertical) {
|
|
705
|
+
frame.style.height = `${Math.round(height)}px`;
|
|
706
|
+
frame.dataset.explicitHeight = "true";
|
|
707
|
+
if (img) {
|
|
708
|
+
img.style.width = "100%";
|
|
709
|
+
img.style.height = "100%";
|
|
710
|
+
img.style.objectFit = "contain";
|
|
711
|
+
}
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (!frame.dataset.explicitHeight) {
|
|
715
|
+
frame.style.height = "";
|
|
716
|
+
}
|
|
717
|
+
if (img) {
|
|
718
|
+
img.style.width = "100%";
|
|
719
|
+
if (frame.dataset.explicitHeight) {
|
|
720
|
+
img.style.height = "100%";
|
|
721
|
+
img.style.objectFit = "contain";
|
|
722
|
+
} else {
|
|
723
|
+
img.style.height = "auto";
|
|
724
|
+
img.style.objectFit = "";
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const applyVideoMediaSize = (container, width, height) => {
|
|
729
|
+
container.style.paddingBottom = "0";
|
|
730
|
+
container.style.width = `${Math.round(width)}px`;
|
|
731
|
+
container.style.maxWidth = "100%";
|
|
732
|
+
container.style.height = `${Math.round(height)}px`;
|
|
733
|
+
};
|
|
734
|
+
const attachMediaResizeHandle = container => {
|
|
735
|
+
if (!container || container.querySelector(".media-resize-handles")) return;
|
|
736
|
+
const isVideo = container.classList.contains("video-container");
|
|
737
|
+
const resizeTarget = isVideo ? container : getImageMediaTarget(container);
|
|
738
|
+
if (!resizeTarget) return;
|
|
739
|
+
const handlesWrapper = document.createElement("div");
|
|
740
|
+
handlesWrapper.className = "media-resize-handles";
|
|
741
|
+
handlesWrapper.setAttribute("contenteditable", "false");
|
|
742
|
+
const edges = [{
|
|
743
|
+
edge: "left",
|
|
744
|
+
title: "Drag to resize width"
|
|
745
|
+
}, {
|
|
746
|
+
edge: "right",
|
|
747
|
+
title: "Drag to resize width"
|
|
748
|
+
}, {
|
|
749
|
+
edge: "top",
|
|
750
|
+
title: "Drag to resize height"
|
|
751
|
+
}, {
|
|
752
|
+
edge: "bottom",
|
|
753
|
+
title: "Drag to resize height"
|
|
754
|
+
}];
|
|
755
|
+
edges.forEach(({
|
|
756
|
+
edge,
|
|
757
|
+
title
|
|
758
|
+
}) => {
|
|
759
|
+
const handle = document.createElement("div");
|
|
760
|
+
handle.className = `media-resize-handle media-resize-handle-${edge}`;
|
|
761
|
+
handle.title = title;
|
|
762
|
+
handle.setAttribute("contenteditable", "false");
|
|
763
|
+
handle.dataset.edge = edge;
|
|
764
|
+
handle.addEventListener("mousedown", event => {
|
|
765
|
+
if (!editable) return;
|
|
766
|
+
event.preventDefault();
|
|
767
|
+
event.stopPropagation();
|
|
768
|
+
const limits = getMediaSizeLimits();
|
|
769
|
+
const rect = resizeTarget.getBoundingClientRect();
|
|
770
|
+
const startX = event.clientX;
|
|
771
|
+
const startY = event.clientY;
|
|
772
|
+
const startWidth = rect.width;
|
|
773
|
+
const startHeight = rect.height;
|
|
774
|
+
const startMarginLeft = Number.parseFloat(resizeTarget.style.marginLeft) || 0;
|
|
775
|
+
const startMarginTop = Number.parseFloat(resizeTarget.style.marginTop) || 0;
|
|
776
|
+
if (isVideo) {
|
|
777
|
+
resizeTarget.style.paddingBottom = "0";
|
|
778
|
+
}
|
|
779
|
+
const onMouseMove = moveEvent => {
|
|
780
|
+
const deltaX = moveEvent.clientX - startX;
|
|
781
|
+
const deltaY = moveEvent.clientY - startY;
|
|
782
|
+
let nextWidth = startWidth;
|
|
783
|
+
let nextHeight = startHeight;
|
|
784
|
+
if (edge === "right") {
|
|
785
|
+
nextWidth = startWidth + deltaX;
|
|
786
|
+
} else if (edge === "left") {
|
|
787
|
+
nextWidth = startWidth - deltaX;
|
|
788
|
+
} else if (edge === "bottom") {
|
|
789
|
+
nextHeight = startHeight + deltaY;
|
|
790
|
+
} else if (edge === "top") {
|
|
791
|
+
nextHeight = startHeight - deltaY;
|
|
792
|
+
}
|
|
793
|
+
nextWidth = Math.max(limits.minWidth, Math.min(nextWidth, limits.maxWidth));
|
|
794
|
+
nextHeight = Math.max(limits.minHeight, Math.min(nextHeight, limits.maxHeight));
|
|
795
|
+
if (edge === "left") {
|
|
796
|
+
resizeTarget.style.marginLeft = `${Math.round(startMarginLeft + (startWidth - nextWidth))}px`;
|
|
797
|
+
}
|
|
798
|
+
if (edge === "top") {
|
|
799
|
+
resizeTarget.style.marginTop = `${Math.round(startMarginTop + (startHeight - nextHeight))}px`;
|
|
800
|
+
}
|
|
801
|
+
if (isVideo) {
|
|
802
|
+
applyVideoMediaSize(resizeTarget, nextWidth, nextHeight);
|
|
803
|
+
} else {
|
|
804
|
+
applyImageMediaSize(resizeTarget, nextWidth, nextHeight, edge);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
const onMouseUp = () => {
|
|
808
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
809
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
810
|
+
triggerChange();
|
|
811
|
+
};
|
|
812
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
813
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
814
|
+
});
|
|
815
|
+
handlesWrapper.appendChild(handle);
|
|
816
|
+
});
|
|
817
|
+
resizeTarget.appendChild(handlesWrapper);
|
|
818
|
+
};
|
|
819
|
+
const handleEditorFocus = () => {
|
|
820
|
+
setEditorFocused(true);
|
|
821
|
+
};
|
|
822
|
+
const handleEditorBlur = () => {
|
|
823
|
+
requestAnimationFrame(() => {
|
|
824
|
+
var _editorRef$current;
|
|
825
|
+
if (!((_editorRef$current = editorRef.current) !== null && _editorRef$current !== void 0 && _editorRef$current.contains(document.activeElement))) {
|
|
826
|
+
setEditorFocused(false);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
};
|
|
830
|
+
const updateMediaControlVisibility = container => {
|
|
831
|
+
const handles = container.querySelector(".media-resize-handles");
|
|
832
|
+
if (handles instanceof HTMLElement) {
|
|
833
|
+
handles.style.display = editable ? "block" : "none";
|
|
834
|
+
handles.style.pointerEvents = editable ? "auto" : "none";
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
const createMediaDeleteButton = (title, className, onRemove) => {
|
|
838
|
+
const deleteBtn = document.createElement("button");
|
|
839
|
+
deleteBtn.type = "button";
|
|
840
|
+
deleteBtn.innerHTML = "×";
|
|
841
|
+
deleteBtn.className = className;
|
|
842
|
+
deleteBtn.title = title;
|
|
843
|
+
deleteBtn.setAttribute("contenteditable", "false");
|
|
844
|
+
deleteBtn.onclick = event => {
|
|
845
|
+
event.preventDefault();
|
|
846
|
+
event.stopPropagation();
|
|
847
|
+
onRemove();
|
|
848
|
+
};
|
|
849
|
+
return deleteBtn;
|
|
850
|
+
};
|
|
650
851
|
|
|
651
852
|
// Listen for selection changes globally to update styles and list type in one pass
|
|
652
853
|
useEffect(() => {
|
|
653
854
|
const handleGlobalSelectionSync = () => {
|
|
654
|
-
var _editorRef$
|
|
855
|
+
var _editorRef$current2;
|
|
655
856
|
// Only sync if the editor has focus
|
|
656
857
|
const sel = window.getSelection();
|
|
657
|
-
if (!sel || !sel.rangeCount || !((_editorRef$
|
|
858
|
+
if (!sel || !sel.rangeCount || !((_editorRef$current2 = editorRef.current) !== null && _editorRef$current2 !== void 0 && _editorRef$current2.contains(sel.anchorNode))) {
|
|
658
859
|
return;
|
|
659
860
|
}
|
|
660
861
|
|
|
@@ -715,6 +916,7 @@ function RichTextEditor({
|
|
|
715
916
|
focus();
|
|
716
917
|
};
|
|
717
918
|
const [fontColor, setFontColor] = useState("#000000");
|
|
919
|
+
const getActiveTextColor = () => getColorAtCursor() || fontColor;
|
|
718
920
|
const handleColorChange = color => {
|
|
719
921
|
setFontColor(color);
|
|
720
922
|
exec("foreColor", color);
|
|
@@ -925,58 +1127,82 @@ function RichTextEditor({
|
|
|
925
1127
|
setVideoModalOpen(false);
|
|
926
1128
|
setVideoUrl("");
|
|
927
1129
|
triggerChange && triggerChange();
|
|
1130
|
+
requestAnimationFrame(() => processExistingMedia(editorRef.current));
|
|
928
1131
|
} else {
|
|
929
1132
|
console.warn("Invalid Video URL or Platform not supported");
|
|
930
1133
|
}
|
|
931
1134
|
};
|
|
1135
|
+
const processExistingVideos = container => {
|
|
1136
|
+
if (!container) return;
|
|
1137
|
+
container.querySelectorAll(".video-container").forEach(videoContainer => {
|
|
1138
|
+
if (!videoContainer.querySelector(".video-delete-button")) {
|
|
1139
|
+
const deleteBtn = createMediaDeleteButton("Remove video", "video-delete-button image-delete-button", () => {
|
|
1140
|
+
videoContainer.remove();
|
|
1141
|
+
triggerChange && triggerChange();
|
|
1142
|
+
});
|
|
1143
|
+
videoContainer.appendChild(deleteBtn);
|
|
1144
|
+
}
|
|
1145
|
+
attachMediaResizeHandle(videoContainer);
|
|
1146
|
+
updateMediaControlVisibility(videoContainer);
|
|
1147
|
+
});
|
|
1148
|
+
};
|
|
932
1149
|
const processExistingImages = container => {
|
|
933
1150
|
if (!container) return;
|
|
934
1151
|
container.querySelectorAll("img").forEach(img => {
|
|
935
|
-
|
|
1152
|
+
var _img$closest;
|
|
1153
|
+
if (img.closest(".rte-modal")) return;
|
|
936
1154
|
const existingWrapper = img.closest(".image-container");
|
|
937
1155
|
if (existingWrapper) {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1156
|
+
existingWrapper.style.cursor = editable ? "pointer" : "default";
|
|
1157
|
+
const frame = ensureImageMediaFrame(existingWrapper);
|
|
1158
|
+
if (frame && !frame.querySelector(".image-delete-button")) {
|
|
1159
|
+
frame.appendChild(createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1160
|
+
existingWrapper.remove();
|
|
1161
|
+
triggerChange && triggerChange();
|
|
1162
|
+
}));
|
|
943
1163
|
}
|
|
1164
|
+
attachMediaResizeHandle(existingWrapper);
|
|
1165
|
+
updateMediaControlVisibility(existingWrapper);
|
|
944
1166
|
return;
|
|
945
1167
|
}
|
|
946
1168
|
const wrapper = document.createElement("div");
|
|
947
|
-
const align = img.getAttribute(
|
|
1169
|
+
const align = img.getAttribute("data-align") || ((_img$closest = img.closest("[data-align]")) === null || _img$closest === void 0 ? void 0 : _img$closest.getAttribute("data-align")) || "left";
|
|
948
1170
|
wrapper.className = `image-container image-align-${align}`;
|
|
949
|
-
wrapper.style.cursor = editable ?
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
img.
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
if (
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1171
|
+
wrapper.style.cursor = editable ? "pointer" : "default";
|
|
1172
|
+
const frame = document.createElement("div");
|
|
1173
|
+
frame.className = "image-media-frame";
|
|
1174
|
+
if (img.getAttribute("width") && !frame.style.width) {
|
|
1175
|
+
frame.style.width = `${img.getAttribute("width")}px`;
|
|
1176
|
+
} else if (img.style.width && img.style.width.endsWith("px")) {
|
|
1177
|
+
frame.style.width = img.style.width;
|
|
1178
|
+
}
|
|
1179
|
+
img.classList.add("rte-image");
|
|
1180
|
+
img.setAttribute("data-align", align);
|
|
1181
|
+
if (frame.style.width) {
|
|
1182
|
+
img.style.width = "100%";
|
|
1183
|
+
img.style.height = frame.dataset.explicitHeight ? "100%" : "auto";
|
|
1184
|
+
} else {
|
|
1185
|
+
img.style.width = "";
|
|
1186
|
+
img.style.height = "auto";
|
|
1187
|
+
}
|
|
1188
|
+
img.addEventListener("click", event => {
|
|
1189
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1190
|
+
openImageModal(img.src);
|
|
1191
|
+
});
|
|
1192
|
+
const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1193
|
+
wrapper.remove();
|
|
1194
|
+
triggerChange && triggerChange();
|
|
1195
|
+
});
|
|
972
1196
|
const {
|
|
973
1197
|
parentNode,
|
|
974
1198
|
nextSibling
|
|
975
1199
|
} = img;
|
|
976
1200
|
if (parentNode) {
|
|
977
1201
|
parentNode.removeChild(img);
|
|
978
|
-
|
|
979
|
-
|
|
1202
|
+
frame.appendChild(img);
|
|
1203
|
+
frame.appendChild(deleteBtn);
|
|
1204
|
+
wrapper.appendChild(frame);
|
|
1205
|
+
attachMediaResizeHandle(wrapper);
|
|
980
1206
|
if (nextSibling) {
|
|
981
1207
|
parentNode.insertBefore(wrapper, nextSibling);
|
|
982
1208
|
} else {
|
|
@@ -985,11 +1211,6 @@ function RichTextEditor({
|
|
|
985
1211
|
}
|
|
986
1212
|
});
|
|
987
1213
|
};
|
|
988
|
-
useEffect(() => {
|
|
989
|
-
if (editorRef.current && value) {
|
|
990
|
-
requestAnimationFrame(() => processExistingImages(editorRef.current));
|
|
991
|
-
}
|
|
992
|
-
}, [value]);
|
|
993
1214
|
|
|
994
1215
|
/*
|
|
995
1216
|
Advanced Tip: Use the 'onImageUpload' prop to handle file uploads to a server
|
|
@@ -1000,24 +1221,23 @@ function RichTextEditor({
|
|
|
1000
1221
|
if (editable) {
|
|
1001
1222
|
// Create container for the image
|
|
1002
1223
|
const container = document.createElement('div');
|
|
1003
|
-
container.className = 'image-container';
|
|
1224
|
+
container.className = 'image-container image-align-left';
|
|
1225
|
+
const frame = document.createElement('div');
|
|
1226
|
+
frame.className = 'image-media-frame';
|
|
1004
1227
|
|
|
1005
1228
|
// Create image element
|
|
1006
1229
|
const img = document.createElement('img');
|
|
1007
1230
|
img.src = dataUrl;
|
|
1008
1231
|
img.alt = fileName || "image";
|
|
1009
1232
|
img.addEventListener("click", () => openImageModal(dataUrl));
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
container.appendChild(img);
|
|
1233
|
+
frame.appendChild(img);
|
|
1234
|
+
container.appendChild(frame);
|
|
1013
1235
|
|
|
1014
1236
|
// Insert at cursor position
|
|
1015
1237
|
insertNodeAtCursor(container);
|
|
1016
|
-
triggerChange();
|
|
1017
|
-
|
|
1018
|
-
// Immediately process newly inserted image so delete button appears
|
|
1019
1238
|
requestAnimationFrame(() => {
|
|
1020
|
-
|
|
1239
|
+
processExistingMedia(editorRef.current);
|
|
1240
|
+
triggerChange();
|
|
1021
1241
|
});
|
|
1022
1242
|
}
|
|
1023
1243
|
} catch (err) {
|
|
@@ -1146,10 +1366,6 @@ function RichTextEditor({
|
|
|
1146
1366
|
}
|
|
1147
1367
|
}
|
|
1148
1368
|
};
|
|
1149
|
-
const getCleanHtml = () => {
|
|
1150
|
-
if (!editorRef.current) return "";
|
|
1151
|
-
return editorRef.current.innerHTML;
|
|
1152
|
-
};
|
|
1153
1369
|
|
|
1154
1370
|
// Helper function to unescape HTML entities
|
|
1155
1371
|
const unescapeHtml = html => {
|
|
@@ -1158,6 +1374,29 @@ function RichTextEditor({
|
|
|
1158
1374
|
txt.innerHTML = html;
|
|
1159
1375
|
return txt.value;
|
|
1160
1376
|
};
|
|
1377
|
+
const isCursorAtEndOfListItem = (range, listItem) => {
|
|
1378
|
+
const suffixRange = document.createRange();
|
|
1379
|
+
suffixRange.setStart(range.startContainer, range.startOffset);
|
|
1380
|
+
suffixRange.setEnd(listItem, listItem.childNodes.length);
|
|
1381
|
+
return suffixRange.toString().replace(/\u200B/g, "").length === 0;
|
|
1382
|
+
};
|
|
1383
|
+
const prepareListItemForTyping = (listItem, selection) => {
|
|
1384
|
+
const activeColor = getActiveTextColor();
|
|
1385
|
+
const newRange = document.createRange();
|
|
1386
|
+
if (activeColor && activeColor.toLowerCase() !== "#000000") {
|
|
1387
|
+
const span = document.createElement("span");
|
|
1388
|
+
span.style.color = activeColor;
|
|
1389
|
+
span.appendChild(document.createTextNode("\u200B"));
|
|
1390
|
+
listItem.appendChild(span);
|
|
1391
|
+
newRange.setStart(span.firstChild, 1);
|
|
1392
|
+
} else {
|
|
1393
|
+
listItem.appendChild(document.createTextNode("\u200B"));
|
|
1394
|
+
newRange.setStart(listItem.firstChild, 1);
|
|
1395
|
+
}
|
|
1396
|
+
newRange.collapse(true);
|
|
1397
|
+
selection.removeAllRanges();
|
|
1398
|
+
selection.addRange(newRange);
|
|
1399
|
+
};
|
|
1161
1400
|
const handleKeyDown = useCallback(e => {
|
|
1162
1401
|
// Handle Enter key
|
|
1163
1402
|
if (e.key === 'Enter') {
|
|
@@ -1172,15 +1411,14 @@ function RichTextEditor({
|
|
|
1172
1411
|
const listItem = parent.closest('li');
|
|
1173
1412
|
if (listItem) {
|
|
1174
1413
|
const list = listItem.parentNode;
|
|
1175
|
-
list.tagName === 'OL';
|
|
1176
1414
|
|
|
1177
1415
|
// Create a new list item
|
|
1178
1416
|
const newItem = document.createElement('li');
|
|
1179
1417
|
|
|
1180
1418
|
// If we're at the end of a list item, add a new one
|
|
1181
|
-
if (range.collapsed && range
|
|
1419
|
+
if (range.collapsed && isCursorAtEndOfListItem(range, listItem)) {
|
|
1182
1420
|
// If it's empty, create a regular paragraph instead
|
|
1183
|
-
if (listItem.textContent.trim() === '') {
|
|
1421
|
+
if (listItem.textContent.replace(/\u200B/g, '').trim() === '') {
|
|
1184
1422
|
document.execCommand('insertHTML', false, '<div><br></div>');
|
|
1185
1423
|
// Move the cursor to the new line
|
|
1186
1424
|
const newRange = document.createRange();
|
|
@@ -1189,6 +1427,7 @@ function RichTextEditor({
|
|
|
1189
1427
|
newRange.collapse(true);
|
|
1190
1428
|
selection.removeAllRanges();
|
|
1191
1429
|
selection.addRange(newRange);
|
|
1430
|
+
triggerChange();
|
|
1192
1431
|
return;
|
|
1193
1432
|
}
|
|
1194
1433
|
|
|
@@ -1198,35 +1437,29 @@ function RichTextEditor({
|
|
|
1198
1437
|
} else {
|
|
1199
1438
|
list.appendChild(newItem);
|
|
1200
1439
|
}
|
|
1201
|
-
|
|
1202
|
-
// Move cursor to the new list item
|
|
1203
|
-
const newRange = document.createRange();
|
|
1204
|
-
newRange.setStart(newItem, 0);
|
|
1205
|
-
newRange.collapse(true);
|
|
1206
|
-
selection.removeAllRanges();
|
|
1207
|
-
selection.addRange(newRange);
|
|
1440
|
+
prepareListItemForTyping(newItem, selection);
|
|
1208
1441
|
} else {
|
|
1209
|
-
// If we're in the middle of text, split the list item
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
// Insert new item after current one
|
|
1217
|
-
newItem.textContent = textAfter;
|
|
1442
|
+
// If we're in the middle of text, split the list item while preserving formatting
|
|
1443
|
+
const afterRange = document.createRange();
|
|
1444
|
+
afterRange.setStart(range.startContainer, range.startOffset);
|
|
1445
|
+
afterRange.setEnd(listItem, listItem.childNodes.length);
|
|
1446
|
+
const movedFragment = afterRange.extractContents();
|
|
1447
|
+
newItem.appendChild(movedFragment);
|
|
1218
1448
|
if (listItem.nextSibling) {
|
|
1219
1449
|
list.insertBefore(newItem, listItem.nextSibling);
|
|
1220
1450
|
} else {
|
|
1221
1451
|
list.appendChild(newItem);
|
|
1222
1452
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1453
|
+
if (!newItem.textContent.replace(/\u200B/g, '').trim()) {
|
|
1454
|
+
newItem.textContent = "";
|
|
1455
|
+
prepareListItemForTyping(newItem, selection);
|
|
1456
|
+
} else {
|
|
1457
|
+
const newRange = document.createRange();
|
|
1458
|
+
newRange.setStart(newItem, 0);
|
|
1459
|
+
newRange.collapse(true);
|
|
1460
|
+
selection.removeAllRanges();
|
|
1461
|
+
selection.addRange(newRange);
|
|
1462
|
+
}
|
|
1230
1463
|
}
|
|
1231
1464
|
} else {
|
|
1232
1465
|
// Regular text, insert a new paragraph
|
|
@@ -1247,7 +1480,7 @@ function RichTextEditor({
|
|
|
1247
1480
|
e.preventDefault();
|
|
1248
1481
|
exec("underline");
|
|
1249
1482
|
}
|
|
1250
|
-
}, [exec, triggerChange]);
|
|
1483
|
+
}, [exec, triggerChange, fontColor]);
|
|
1251
1484
|
const confirmLink = () => {
|
|
1252
1485
|
// Add protocol if missing
|
|
1253
1486
|
let url = linkUrl.trim();
|
|
@@ -1420,7 +1653,7 @@ function RichTextEditor({
|
|
|
1420
1653
|
};
|
|
1421
1654
|
const handleInput = useCallback(() => {
|
|
1422
1655
|
if (editorRef.current) {
|
|
1423
|
-
const next =
|
|
1656
|
+
const next = getCleanHtml();
|
|
1424
1657
|
setHtml(next);
|
|
1425
1658
|
lastSynchronizedHtmlRef.current = next;
|
|
1426
1659
|
onChange && onChange(next);
|
|
@@ -1457,6 +1690,18 @@ function RichTextEditor({
|
|
|
1457
1690
|
}, [disabled]);
|
|
1458
1691
|
const handleEditorClick = useCallback(e => {
|
|
1459
1692
|
setSelectionVersion(v => v + 1);
|
|
1693
|
+
const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
|
|
1694
|
+
if (deleteBtn && editable && editorFocused) {
|
|
1695
|
+
e.preventDefault();
|
|
1696
|
+
e.stopPropagation();
|
|
1697
|
+
const wrapper = deleteBtn.closest('.image-container, .video-container');
|
|
1698
|
+
if (wrapper) {
|
|
1699
|
+
wrapper.remove();
|
|
1700
|
+
triggerChange();
|
|
1701
|
+
}
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1460
1705
|
// Check if the click is on a link
|
|
1461
1706
|
const clickedLink = e.target.closest('a');
|
|
1462
1707
|
if (clickedLink) {
|
|
@@ -1489,7 +1734,7 @@ function RichTextEditor({
|
|
|
1489
1734
|
}
|
|
1490
1735
|
}, 0);
|
|
1491
1736
|
}
|
|
1492
|
-
}, [editable, disabled]);
|
|
1737
|
+
}, [editable, disabled, editorFocused, triggerChange]);
|
|
1493
1738
|
const renderImageToolbar = () => {
|
|
1494
1739
|
if (!selectedImage || !editorRef.current || !editable) return null;
|
|
1495
1740
|
const editorRect = editorRef.current.getBoundingClientRect();
|
|
@@ -1559,6 +1804,9 @@ function RichTextEditor({
|
|
|
1559
1804
|
title: "Remove Image"
|
|
1560
1805
|
}, "\xD7"));
|
|
1561
1806
|
};
|
|
1807
|
+
if (isLoading) {
|
|
1808
|
+
return /*#__PURE__*/React.createElement(Spinner, null);
|
|
1809
|
+
}
|
|
1562
1810
|
return /*#__PURE__*/React.createElement("div", {
|
|
1563
1811
|
className: "rte-main-wrapper",
|
|
1564
1812
|
style: {
|
|
@@ -1984,12 +2232,14 @@ function RichTextEditor({
|
|
|
1984
2232
|
onDragOver: e => e.preventDefault(),
|
|
1985
2233
|
onKeyDown: handleKeyDown,
|
|
1986
2234
|
onClick: handleEditorClick,
|
|
2235
|
+
onFocus: handleEditorFocus,
|
|
2236
|
+
onBlur: handleEditorBlur,
|
|
1987
2237
|
style: {
|
|
1988
2238
|
minHeight: minHeight || '150px',
|
|
1989
2239
|
maxHeight: maxHeight || '500px',
|
|
1990
2240
|
paddingLeft: paddingLeft || '12px'
|
|
1991
2241
|
},
|
|
1992
|
-
className:
|
|
2242
|
+
className: `rte-content${editable ? " rte-is-editable" : ""}${editorFocused ? " rte-is-focused" : ""}`
|
|
1993
2243
|
}), renderImageToolbar(), /*#__PURE__*/React.createElement("div", {
|
|
1994
2244
|
className: "rte-footer"
|
|
1995
2245
|
}, /*#__PURE__*/React.createElement("div", {
|