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.cjs.js
CHANGED
|
@@ -390,7 +390,7 @@ function styleInject(css, ref) {
|
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
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:
|
|
393
|
+
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}";
|
|
394
394
|
styleInject(css_248z);
|
|
395
395
|
|
|
396
396
|
// Helper functions for HTML escaping
|
|
@@ -418,9 +418,6 @@ function RichTextEditor({
|
|
|
418
418
|
maxHeight,
|
|
419
419
|
onImageUpload
|
|
420
420
|
}) {
|
|
421
|
-
if (isLoading) {
|
|
422
|
-
return /*#__PURE__*/React.createElement(Spinner, null);
|
|
423
|
-
}
|
|
424
421
|
const editorRef = React.useRef(null);
|
|
425
422
|
const fileInputRef = React.useRef(null);
|
|
426
423
|
const scrollTopRef = React.useRef(0);
|
|
@@ -430,6 +427,7 @@ function RichTextEditor({
|
|
|
430
427
|
const [linkText, setLinkText] = React.useState("");
|
|
431
428
|
const selectionRangeRef = React.useRef(null);
|
|
432
429
|
const [editable, setEditable] = React.useState(initialEditable);
|
|
430
|
+
const [editorFocused, setEditorFocused] = React.useState(false);
|
|
433
431
|
const lastSynchronizedHtmlRef = React.useRef("");
|
|
434
432
|
React.useEffect(() => {
|
|
435
433
|
setEditable(initialEditable);
|
|
@@ -535,40 +533,25 @@ function RichTextEditor({
|
|
|
535
533
|
window.removeEventListener('wheel', handleWheel);
|
|
536
534
|
};
|
|
537
535
|
}, [imageModalOpen]);
|
|
538
|
-
React.useEffect(() => {
|
|
539
|
-
const handleClick = e => {
|
|
540
|
-
// Trigger selection update for toolbar reactivity
|
|
541
|
-
setSelectionVersion(v => v + 1);
|
|
542
|
-
const deleteBtn = e.target.closest('button[title="Remove image"]');
|
|
543
|
-
if (deleteBtn && editable) {
|
|
544
|
-
e.preventDefault();
|
|
545
|
-
e.stopPropagation();
|
|
546
|
-
const wrapper = deleteBtn.closest('.image-container');
|
|
547
|
-
if (wrapper && wrapper.parentNode) {
|
|
548
|
-
wrapper.parentNode.removeChild(wrapper);
|
|
549
|
-
triggerChange && triggerChange();
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
const editor = editorRef.current;
|
|
554
|
-
if (editor) {
|
|
555
|
-
editor.addEventListener('click', handleClick);
|
|
556
|
-
return () => {
|
|
557
|
-
editor.removeEventListener('click', handleClick);
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
// Removed dependency on editable to minimize listener churn
|
|
561
|
-
}, []);
|
|
562
536
|
React.useEffect(() => {
|
|
563
537
|
if (editorRef.current && value && value !== lastSynchronizedHtmlRef.current) {
|
|
564
|
-
requestAnimationFrame(() =>
|
|
538
|
+
requestAnimationFrame(() => processExistingMedia(editorRef.current));
|
|
565
539
|
}
|
|
566
540
|
}, [value]);
|
|
567
541
|
|
|
568
542
|
// Runs whenever editable changes (toggles delete icon visibility)
|
|
569
543
|
React.useEffect(() => {
|
|
570
|
-
|
|
544
|
+
if (!editable) {
|
|
545
|
+
setEditorFocused(false);
|
|
546
|
+
}
|
|
547
|
+
processExistingMedia(editorRef.current);
|
|
571
548
|
}, [editable]);
|
|
549
|
+
React.useEffect(() => {
|
|
550
|
+
if (!editorRef.current) return;
|
|
551
|
+
editorRef.current.querySelectorAll(".image-container, .video-container").forEach(container => {
|
|
552
|
+
updateMediaControlVisibility(container);
|
|
553
|
+
});
|
|
554
|
+
}, [editorFocused, editable]);
|
|
572
555
|
React.useEffect(() => {
|
|
573
556
|
// Only update if value is different from our last known synced state
|
|
574
557
|
if (value && value !== lastSynchronizedHtmlRef.current) {
|
|
@@ -592,6 +575,7 @@ function RichTextEditor({
|
|
|
592
575
|
if (editorRef.current && editorRef.current.innerHTML !== newContent) {
|
|
593
576
|
editorRef.current.innerHTML = newContent;
|
|
594
577
|
}
|
|
578
|
+
requestAnimationFrame(() => processExistingMedia(editorRef.current));
|
|
595
579
|
updateMetrics();
|
|
596
580
|
}
|
|
597
581
|
} catch (e) {
|
|
@@ -606,6 +590,17 @@ function RichTextEditor({
|
|
|
606
590
|
}
|
|
607
591
|
}
|
|
608
592
|
}, [value, initialEditable, updateMetrics]);
|
|
593
|
+
const processExistingMedia = container => {
|
|
594
|
+
if (!container) return;
|
|
595
|
+
processExistingImages(container);
|
|
596
|
+
processExistingVideos(container);
|
|
597
|
+
};
|
|
598
|
+
const getCleanHtml = () => {
|
|
599
|
+
if (!editorRef.current) return "";
|
|
600
|
+
const clone = editorRef.current.cloneNode(true);
|
|
601
|
+
stripEditorChrome(clone);
|
|
602
|
+
return clone.innerHTML;
|
|
603
|
+
};
|
|
609
604
|
|
|
610
605
|
// Trigger change manually
|
|
611
606
|
const triggerChange = React.useCallback(() => {
|
|
@@ -649,14 +644,220 @@ function RichTextEditor({
|
|
|
649
644
|
return hex.length === 1 ? "0" + hex : hex;
|
|
650
645
|
}).join("");
|
|
651
646
|
}
|
|
647
|
+
const getColorAtCursor = () => {
|
|
648
|
+
const sel = window.getSelection();
|
|
649
|
+
if (!sel || !sel.rangeCount || !editorRef.current) return null;
|
|
650
|
+
let node = sel.anchorNode;
|
|
651
|
+
if (node.nodeType === 3) node = node.parentNode;
|
|
652
|
+
while (node && node !== editorRef.current) {
|
|
653
|
+
if (node.nodeType === 1) {
|
|
654
|
+
if (node.style && node.style.color) {
|
|
655
|
+
return rgbToHex(node.style.color);
|
|
656
|
+
}
|
|
657
|
+
if (node.tagName === "FONT" && node.getAttribute("color")) {
|
|
658
|
+
return node.getAttribute("color");
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
node = node.parentNode;
|
|
662
|
+
}
|
|
663
|
+
const computedColor = window.getComputedStyle(sel.anchorNode.nodeType === 3 ? sel.anchorNode.parentNode : sel.anchorNode).color;
|
|
664
|
+
return rgbToHex(computedColor);
|
|
665
|
+
};
|
|
666
|
+
const stripEditorChrome = root => {
|
|
667
|
+
root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handles, .media-resize-handle").forEach(element => element.remove());
|
|
668
|
+
return root;
|
|
669
|
+
};
|
|
670
|
+
const getMediaSizeLimits = () => {
|
|
671
|
+
const maxWidth = editorRef.current ? editorRef.current.getBoundingClientRect().width - 24 : 800;
|
|
672
|
+
return {
|
|
673
|
+
minWidth: 120,
|
|
674
|
+
minHeight: 80,
|
|
675
|
+
maxWidth,
|
|
676
|
+
maxHeight: 720
|
|
677
|
+
};
|
|
678
|
+
};
|
|
679
|
+
const ensureImageMediaFrame = imageContainer => {
|
|
680
|
+
if (!imageContainer) return null;
|
|
681
|
+
let frame = imageContainer.querySelector(":scope > .image-media-frame");
|
|
682
|
+
if (frame) return frame;
|
|
683
|
+
frame = document.createElement("div");
|
|
684
|
+
frame.className = "image-media-frame";
|
|
685
|
+
["width", "height", "marginLeft", "marginTop", "maxWidth"].forEach(prop => {
|
|
686
|
+
if (imageContainer.style[prop]) {
|
|
687
|
+
frame.style[prop] = imageContainer.style[prop];
|
|
688
|
+
imageContainer.style[prop] = "";
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
if (imageContainer.dataset.explicitHeight) {
|
|
692
|
+
frame.dataset.explicitHeight = imageContainer.dataset.explicitHeight;
|
|
693
|
+
delete imageContainer.dataset.explicitHeight;
|
|
694
|
+
}
|
|
695
|
+
const children = Array.from(imageContainer.children);
|
|
696
|
+
imageContainer.appendChild(frame);
|
|
697
|
+
children.forEach(child => frame.appendChild(child));
|
|
698
|
+
return frame;
|
|
699
|
+
};
|
|
700
|
+
const getImageMediaTarget = imageContainer => ensureImageMediaFrame(imageContainer) || imageContainer;
|
|
701
|
+
const applyImageMediaSize = (frame, width, height, edge) => {
|
|
702
|
+
const img = frame.querySelector("img");
|
|
703
|
+
const isVertical = edge === "top" || edge === "bottom";
|
|
704
|
+
frame.style.width = `${Math.round(width)}px`;
|
|
705
|
+
frame.style.maxWidth = "100%";
|
|
706
|
+
if (isVertical) {
|
|
707
|
+
frame.style.height = `${Math.round(height)}px`;
|
|
708
|
+
frame.dataset.explicitHeight = "true";
|
|
709
|
+
if (img) {
|
|
710
|
+
img.style.width = "100%";
|
|
711
|
+
img.style.height = "100%";
|
|
712
|
+
img.style.objectFit = "contain";
|
|
713
|
+
}
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (!frame.dataset.explicitHeight) {
|
|
717
|
+
frame.style.height = "";
|
|
718
|
+
}
|
|
719
|
+
if (img) {
|
|
720
|
+
img.style.width = "100%";
|
|
721
|
+
if (frame.dataset.explicitHeight) {
|
|
722
|
+
img.style.height = "100%";
|
|
723
|
+
img.style.objectFit = "contain";
|
|
724
|
+
} else {
|
|
725
|
+
img.style.height = "auto";
|
|
726
|
+
img.style.objectFit = "";
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
const applyVideoMediaSize = (container, width, height) => {
|
|
731
|
+
container.style.paddingBottom = "0";
|
|
732
|
+
container.style.width = `${Math.round(width)}px`;
|
|
733
|
+
container.style.maxWidth = "100%";
|
|
734
|
+
container.style.height = `${Math.round(height)}px`;
|
|
735
|
+
};
|
|
736
|
+
const attachMediaResizeHandle = container => {
|
|
737
|
+
if (!container || container.querySelector(".media-resize-handles")) return;
|
|
738
|
+
const isVideo = container.classList.contains("video-container");
|
|
739
|
+
const resizeTarget = isVideo ? container : getImageMediaTarget(container);
|
|
740
|
+
if (!resizeTarget) return;
|
|
741
|
+
const handlesWrapper = document.createElement("div");
|
|
742
|
+
handlesWrapper.className = "media-resize-handles";
|
|
743
|
+
handlesWrapper.setAttribute("contenteditable", "false");
|
|
744
|
+
const edges = [{
|
|
745
|
+
edge: "left",
|
|
746
|
+
title: "Drag to resize width"
|
|
747
|
+
}, {
|
|
748
|
+
edge: "right",
|
|
749
|
+
title: "Drag to resize width"
|
|
750
|
+
}, {
|
|
751
|
+
edge: "top",
|
|
752
|
+
title: "Drag to resize height"
|
|
753
|
+
}, {
|
|
754
|
+
edge: "bottom",
|
|
755
|
+
title: "Drag to resize height"
|
|
756
|
+
}];
|
|
757
|
+
edges.forEach(({
|
|
758
|
+
edge,
|
|
759
|
+
title
|
|
760
|
+
}) => {
|
|
761
|
+
const handle = document.createElement("div");
|
|
762
|
+
handle.className = `media-resize-handle media-resize-handle-${edge}`;
|
|
763
|
+
handle.title = title;
|
|
764
|
+
handle.setAttribute("contenteditable", "false");
|
|
765
|
+
handle.dataset.edge = edge;
|
|
766
|
+
handle.addEventListener("mousedown", event => {
|
|
767
|
+
if (!editable) return;
|
|
768
|
+
event.preventDefault();
|
|
769
|
+
event.stopPropagation();
|
|
770
|
+
const limits = getMediaSizeLimits();
|
|
771
|
+
const rect = resizeTarget.getBoundingClientRect();
|
|
772
|
+
const startX = event.clientX;
|
|
773
|
+
const startY = event.clientY;
|
|
774
|
+
const startWidth = rect.width;
|
|
775
|
+
const startHeight = rect.height;
|
|
776
|
+
const startMarginLeft = Number.parseFloat(resizeTarget.style.marginLeft) || 0;
|
|
777
|
+
const startMarginTop = Number.parseFloat(resizeTarget.style.marginTop) || 0;
|
|
778
|
+
if (isVideo) {
|
|
779
|
+
resizeTarget.style.paddingBottom = "0";
|
|
780
|
+
}
|
|
781
|
+
const onMouseMove = moveEvent => {
|
|
782
|
+
const deltaX = moveEvent.clientX - startX;
|
|
783
|
+
const deltaY = moveEvent.clientY - startY;
|
|
784
|
+
let nextWidth = startWidth;
|
|
785
|
+
let nextHeight = startHeight;
|
|
786
|
+
if (edge === "right") {
|
|
787
|
+
nextWidth = startWidth + deltaX;
|
|
788
|
+
} else if (edge === "left") {
|
|
789
|
+
nextWidth = startWidth - deltaX;
|
|
790
|
+
} else if (edge === "bottom") {
|
|
791
|
+
nextHeight = startHeight + deltaY;
|
|
792
|
+
} else if (edge === "top") {
|
|
793
|
+
nextHeight = startHeight - deltaY;
|
|
794
|
+
}
|
|
795
|
+
nextWidth = Math.max(limits.minWidth, Math.min(nextWidth, limits.maxWidth));
|
|
796
|
+
nextHeight = Math.max(limits.minHeight, Math.min(nextHeight, limits.maxHeight));
|
|
797
|
+
if (edge === "left") {
|
|
798
|
+
resizeTarget.style.marginLeft = `${Math.round(startMarginLeft + (startWidth - nextWidth))}px`;
|
|
799
|
+
}
|
|
800
|
+
if (edge === "top") {
|
|
801
|
+
resizeTarget.style.marginTop = `${Math.round(startMarginTop + (startHeight - nextHeight))}px`;
|
|
802
|
+
}
|
|
803
|
+
if (isVideo) {
|
|
804
|
+
applyVideoMediaSize(resizeTarget, nextWidth, nextHeight);
|
|
805
|
+
} else {
|
|
806
|
+
applyImageMediaSize(resizeTarget, nextWidth, nextHeight, edge);
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
const onMouseUp = () => {
|
|
810
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
811
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
812
|
+
triggerChange();
|
|
813
|
+
};
|
|
814
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
815
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
816
|
+
});
|
|
817
|
+
handlesWrapper.appendChild(handle);
|
|
818
|
+
});
|
|
819
|
+
resizeTarget.appendChild(handlesWrapper);
|
|
820
|
+
};
|
|
821
|
+
const handleEditorFocus = () => {
|
|
822
|
+
setEditorFocused(true);
|
|
823
|
+
};
|
|
824
|
+
const handleEditorBlur = () => {
|
|
825
|
+
requestAnimationFrame(() => {
|
|
826
|
+
var _editorRef$current;
|
|
827
|
+
if (!((_editorRef$current = editorRef.current) !== null && _editorRef$current !== void 0 && _editorRef$current.contains(document.activeElement))) {
|
|
828
|
+
setEditorFocused(false);
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
};
|
|
832
|
+
const updateMediaControlVisibility = container => {
|
|
833
|
+
const handles = container.querySelector(".media-resize-handles");
|
|
834
|
+
if (handles instanceof HTMLElement) {
|
|
835
|
+
handles.style.display = editable ? "block" : "none";
|
|
836
|
+
handles.style.pointerEvents = editable ? "auto" : "none";
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
const createMediaDeleteButton = (title, className, onRemove) => {
|
|
840
|
+
const deleteBtn = document.createElement("button");
|
|
841
|
+
deleteBtn.type = "button";
|
|
842
|
+
deleteBtn.innerHTML = "×";
|
|
843
|
+
deleteBtn.className = className;
|
|
844
|
+
deleteBtn.title = title;
|
|
845
|
+
deleteBtn.setAttribute("contenteditable", "false");
|
|
846
|
+
deleteBtn.onclick = event => {
|
|
847
|
+
event.preventDefault();
|
|
848
|
+
event.stopPropagation();
|
|
849
|
+
onRemove();
|
|
850
|
+
};
|
|
851
|
+
return deleteBtn;
|
|
852
|
+
};
|
|
652
853
|
|
|
653
854
|
// Listen for selection changes globally to update styles and list type in one pass
|
|
654
855
|
React.useEffect(() => {
|
|
655
856
|
const handleGlobalSelectionSync = () => {
|
|
656
|
-
var _editorRef$
|
|
857
|
+
var _editorRef$current2;
|
|
657
858
|
// Only sync if the editor has focus
|
|
658
859
|
const sel = window.getSelection();
|
|
659
|
-
if (!sel || !sel.rangeCount || !((_editorRef$
|
|
860
|
+
if (!sel || !sel.rangeCount || !((_editorRef$current2 = editorRef.current) !== null && _editorRef$current2 !== void 0 && _editorRef$current2.contains(sel.anchorNode))) {
|
|
660
861
|
return;
|
|
661
862
|
}
|
|
662
863
|
|
|
@@ -717,6 +918,7 @@ function RichTextEditor({
|
|
|
717
918
|
focus();
|
|
718
919
|
};
|
|
719
920
|
const [fontColor, setFontColor] = React.useState("#000000");
|
|
921
|
+
const getActiveTextColor = () => getColorAtCursor() || fontColor;
|
|
720
922
|
const handleColorChange = color => {
|
|
721
923
|
setFontColor(color);
|
|
722
924
|
exec("foreColor", color);
|
|
@@ -927,58 +1129,82 @@ function RichTextEditor({
|
|
|
927
1129
|
setVideoModalOpen(false);
|
|
928
1130
|
setVideoUrl("");
|
|
929
1131
|
triggerChange && triggerChange();
|
|
1132
|
+
requestAnimationFrame(() => processExistingMedia(editorRef.current));
|
|
930
1133
|
} else {
|
|
931
1134
|
console.warn("Invalid Video URL or Platform not supported");
|
|
932
1135
|
}
|
|
933
1136
|
};
|
|
1137
|
+
const processExistingVideos = container => {
|
|
1138
|
+
if (!container) return;
|
|
1139
|
+
container.querySelectorAll(".video-container").forEach(videoContainer => {
|
|
1140
|
+
if (!videoContainer.querySelector(".video-delete-button")) {
|
|
1141
|
+
const deleteBtn = createMediaDeleteButton("Remove video", "video-delete-button image-delete-button", () => {
|
|
1142
|
+
videoContainer.remove();
|
|
1143
|
+
triggerChange && triggerChange();
|
|
1144
|
+
});
|
|
1145
|
+
videoContainer.appendChild(deleteBtn);
|
|
1146
|
+
}
|
|
1147
|
+
attachMediaResizeHandle(videoContainer);
|
|
1148
|
+
updateMediaControlVisibility(videoContainer);
|
|
1149
|
+
});
|
|
1150
|
+
};
|
|
934
1151
|
const processExistingImages = container => {
|
|
935
1152
|
if (!container) return;
|
|
936
1153
|
container.querySelectorAll("img").forEach(img => {
|
|
937
|
-
|
|
1154
|
+
var _img$closest;
|
|
1155
|
+
if (img.closest(".rte-modal")) return;
|
|
938
1156
|
const existingWrapper = img.closest(".image-container");
|
|
939
1157
|
if (existingWrapper) {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1158
|
+
existingWrapper.style.cursor = editable ? "pointer" : "default";
|
|
1159
|
+
const frame = ensureImageMediaFrame(existingWrapper);
|
|
1160
|
+
if (frame && !frame.querySelector(".image-delete-button")) {
|
|
1161
|
+
frame.appendChild(createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1162
|
+
existingWrapper.remove();
|
|
1163
|
+
triggerChange && triggerChange();
|
|
1164
|
+
}));
|
|
945
1165
|
}
|
|
1166
|
+
attachMediaResizeHandle(existingWrapper);
|
|
1167
|
+
updateMediaControlVisibility(existingWrapper);
|
|
946
1168
|
return;
|
|
947
1169
|
}
|
|
948
1170
|
const wrapper = document.createElement("div");
|
|
949
|
-
const align = img.getAttribute(
|
|
1171
|
+
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";
|
|
950
1172
|
wrapper.className = `image-container image-align-${align}`;
|
|
951
|
-
wrapper.style.cursor = editable ?
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
img.
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
if (
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1173
|
+
wrapper.style.cursor = editable ? "pointer" : "default";
|
|
1174
|
+
const frame = document.createElement("div");
|
|
1175
|
+
frame.className = "image-media-frame";
|
|
1176
|
+
if (img.getAttribute("width") && !frame.style.width) {
|
|
1177
|
+
frame.style.width = `${img.getAttribute("width")}px`;
|
|
1178
|
+
} else if (img.style.width && img.style.width.endsWith("px")) {
|
|
1179
|
+
frame.style.width = img.style.width;
|
|
1180
|
+
}
|
|
1181
|
+
img.classList.add("rte-image");
|
|
1182
|
+
img.setAttribute("data-align", align);
|
|
1183
|
+
if (frame.style.width) {
|
|
1184
|
+
img.style.width = "100%";
|
|
1185
|
+
img.style.height = frame.dataset.explicitHeight ? "100%" : "auto";
|
|
1186
|
+
} else {
|
|
1187
|
+
img.style.width = "";
|
|
1188
|
+
img.style.height = "auto";
|
|
1189
|
+
}
|
|
1190
|
+
img.addEventListener("click", event => {
|
|
1191
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1192
|
+
openImageModal(img.src);
|
|
1193
|
+
});
|
|
1194
|
+
const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1195
|
+
wrapper.remove();
|
|
1196
|
+
triggerChange && triggerChange();
|
|
1197
|
+
});
|
|
974
1198
|
const {
|
|
975
1199
|
parentNode,
|
|
976
1200
|
nextSibling
|
|
977
1201
|
} = img;
|
|
978
1202
|
if (parentNode) {
|
|
979
1203
|
parentNode.removeChild(img);
|
|
980
|
-
|
|
981
|
-
|
|
1204
|
+
frame.appendChild(img);
|
|
1205
|
+
frame.appendChild(deleteBtn);
|
|
1206
|
+
wrapper.appendChild(frame);
|
|
1207
|
+
attachMediaResizeHandle(wrapper);
|
|
982
1208
|
if (nextSibling) {
|
|
983
1209
|
parentNode.insertBefore(wrapper, nextSibling);
|
|
984
1210
|
} else {
|
|
@@ -987,11 +1213,6 @@ function RichTextEditor({
|
|
|
987
1213
|
}
|
|
988
1214
|
});
|
|
989
1215
|
};
|
|
990
|
-
React.useEffect(() => {
|
|
991
|
-
if (editorRef.current && value) {
|
|
992
|
-
requestAnimationFrame(() => processExistingImages(editorRef.current));
|
|
993
|
-
}
|
|
994
|
-
}, [value]);
|
|
995
1216
|
|
|
996
1217
|
/*
|
|
997
1218
|
Advanced Tip: Use the 'onImageUpload' prop to handle file uploads to a server
|
|
@@ -1002,24 +1223,23 @@ function RichTextEditor({
|
|
|
1002
1223
|
if (editable) {
|
|
1003
1224
|
// Create container for the image
|
|
1004
1225
|
const container = document.createElement('div');
|
|
1005
|
-
container.className = 'image-container';
|
|
1226
|
+
container.className = 'image-container image-align-left';
|
|
1227
|
+
const frame = document.createElement('div');
|
|
1228
|
+
frame.className = 'image-media-frame';
|
|
1006
1229
|
|
|
1007
1230
|
// Create image element
|
|
1008
1231
|
const img = document.createElement('img');
|
|
1009
1232
|
img.src = dataUrl;
|
|
1010
1233
|
img.alt = fileName || "image";
|
|
1011
1234
|
img.addEventListener("click", () => openImageModal(dataUrl));
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
container.appendChild(img);
|
|
1235
|
+
frame.appendChild(img);
|
|
1236
|
+
container.appendChild(frame);
|
|
1015
1237
|
|
|
1016
1238
|
// Insert at cursor position
|
|
1017
1239
|
insertNodeAtCursor(container);
|
|
1018
|
-
triggerChange();
|
|
1019
|
-
|
|
1020
|
-
// Immediately process newly inserted image so delete button appears
|
|
1021
1240
|
requestAnimationFrame(() => {
|
|
1022
|
-
|
|
1241
|
+
processExistingMedia(editorRef.current);
|
|
1242
|
+
triggerChange();
|
|
1023
1243
|
});
|
|
1024
1244
|
}
|
|
1025
1245
|
} catch (err) {
|
|
@@ -1148,10 +1368,6 @@ function RichTextEditor({
|
|
|
1148
1368
|
}
|
|
1149
1369
|
}
|
|
1150
1370
|
};
|
|
1151
|
-
const getCleanHtml = () => {
|
|
1152
|
-
if (!editorRef.current) return "";
|
|
1153
|
-
return editorRef.current.innerHTML;
|
|
1154
|
-
};
|
|
1155
1371
|
|
|
1156
1372
|
// Helper function to unescape HTML entities
|
|
1157
1373
|
const unescapeHtml = html => {
|
|
@@ -1160,6 +1376,29 @@ function RichTextEditor({
|
|
|
1160
1376
|
txt.innerHTML = html;
|
|
1161
1377
|
return txt.value;
|
|
1162
1378
|
};
|
|
1379
|
+
const isCursorAtEndOfListItem = (range, listItem) => {
|
|
1380
|
+
const suffixRange = document.createRange();
|
|
1381
|
+
suffixRange.setStart(range.startContainer, range.startOffset);
|
|
1382
|
+
suffixRange.setEnd(listItem, listItem.childNodes.length);
|
|
1383
|
+
return suffixRange.toString().replace(/\u200B/g, "").length === 0;
|
|
1384
|
+
};
|
|
1385
|
+
const prepareListItemForTyping = (listItem, selection) => {
|
|
1386
|
+
const activeColor = getActiveTextColor();
|
|
1387
|
+
const newRange = document.createRange();
|
|
1388
|
+
if (activeColor && activeColor.toLowerCase() !== "#000000") {
|
|
1389
|
+
const span = document.createElement("span");
|
|
1390
|
+
span.style.color = activeColor;
|
|
1391
|
+
span.appendChild(document.createTextNode("\u200B"));
|
|
1392
|
+
listItem.appendChild(span);
|
|
1393
|
+
newRange.setStart(span.firstChild, 1);
|
|
1394
|
+
} else {
|
|
1395
|
+
listItem.appendChild(document.createTextNode("\u200B"));
|
|
1396
|
+
newRange.setStart(listItem.firstChild, 1);
|
|
1397
|
+
}
|
|
1398
|
+
newRange.collapse(true);
|
|
1399
|
+
selection.removeAllRanges();
|
|
1400
|
+
selection.addRange(newRange);
|
|
1401
|
+
};
|
|
1163
1402
|
const handleKeyDown = React.useCallback(e => {
|
|
1164
1403
|
// Handle Enter key
|
|
1165
1404
|
if (e.key === 'Enter') {
|
|
@@ -1174,15 +1413,14 @@ function RichTextEditor({
|
|
|
1174
1413
|
const listItem = parent.closest('li');
|
|
1175
1414
|
if (listItem) {
|
|
1176
1415
|
const list = listItem.parentNode;
|
|
1177
|
-
list.tagName === 'OL';
|
|
1178
1416
|
|
|
1179
1417
|
// Create a new list item
|
|
1180
1418
|
const newItem = document.createElement('li');
|
|
1181
1419
|
|
|
1182
1420
|
// If we're at the end of a list item, add a new one
|
|
1183
|
-
if (range.collapsed && range
|
|
1421
|
+
if (range.collapsed && isCursorAtEndOfListItem(range, listItem)) {
|
|
1184
1422
|
// If it's empty, create a regular paragraph instead
|
|
1185
|
-
if (listItem.textContent.trim() === '') {
|
|
1423
|
+
if (listItem.textContent.replace(/\u200B/g, '').trim() === '') {
|
|
1186
1424
|
document.execCommand('insertHTML', false, '<div><br></div>');
|
|
1187
1425
|
// Move the cursor to the new line
|
|
1188
1426
|
const newRange = document.createRange();
|
|
@@ -1191,6 +1429,7 @@ function RichTextEditor({
|
|
|
1191
1429
|
newRange.collapse(true);
|
|
1192
1430
|
selection.removeAllRanges();
|
|
1193
1431
|
selection.addRange(newRange);
|
|
1432
|
+
triggerChange();
|
|
1194
1433
|
return;
|
|
1195
1434
|
}
|
|
1196
1435
|
|
|
@@ -1200,35 +1439,29 @@ function RichTextEditor({
|
|
|
1200
1439
|
} else {
|
|
1201
1440
|
list.appendChild(newItem);
|
|
1202
1441
|
}
|
|
1203
|
-
|
|
1204
|
-
// Move cursor to the new list item
|
|
1205
|
-
const newRange = document.createRange();
|
|
1206
|
-
newRange.setStart(newItem, 0);
|
|
1207
|
-
newRange.collapse(true);
|
|
1208
|
-
selection.removeAllRanges();
|
|
1209
|
-
selection.addRange(newRange);
|
|
1442
|
+
prepareListItemForTyping(newItem, selection);
|
|
1210
1443
|
} else {
|
|
1211
|
-
// If we're in the middle of text, split the list item
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
// Insert new item after current one
|
|
1219
|
-
newItem.textContent = textAfter;
|
|
1444
|
+
// If we're in the middle of text, split the list item while preserving formatting
|
|
1445
|
+
const afterRange = document.createRange();
|
|
1446
|
+
afterRange.setStart(range.startContainer, range.startOffset);
|
|
1447
|
+
afterRange.setEnd(listItem, listItem.childNodes.length);
|
|
1448
|
+
const movedFragment = afterRange.extractContents();
|
|
1449
|
+
newItem.appendChild(movedFragment);
|
|
1220
1450
|
if (listItem.nextSibling) {
|
|
1221
1451
|
list.insertBefore(newItem, listItem.nextSibling);
|
|
1222
1452
|
} else {
|
|
1223
1453
|
list.appendChild(newItem);
|
|
1224
1454
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1455
|
+
if (!newItem.textContent.replace(/\u200B/g, '').trim()) {
|
|
1456
|
+
newItem.textContent = "";
|
|
1457
|
+
prepareListItemForTyping(newItem, selection);
|
|
1458
|
+
} else {
|
|
1459
|
+
const newRange = document.createRange();
|
|
1460
|
+
newRange.setStart(newItem, 0);
|
|
1461
|
+
newRange.collapse(true);
|
|
1462
|
+
selection.removeAllRanges();
|
|
1463
|
+
selection.addRange(newRange);
|
|
1464
|
+
}
|
|
1232
1465
|
}
|
|
1233
1466
|
} else {
|
|
1234
1467
|
// Regular text, insert a new paragraph
|
|
@@ -1249,7 +1482,7 @@ function RichTextEditor({
|
|
|
1249
1482
|
e.preventDefault();
|
|
1250
1483
|
exec("underline");
|
|
1251
1484
|
}
|
|
1252
|
-
}, [exec, triggerChange]);
|
|
1485
|
+
}, [exec, triggerChange, fontColor]);
|
|
1253
1486
|
const confirmLink = () => {
|
|
1254
1487
|
// Add protocol if missing
|
|
1255
1488
|
let url = linkUrl.trim();
|
|
@@ -1422,7 +1655,7 @@ function RichTextEditor({
|
|
|
1422
1655
|
};
|
|
1423
1656
|
const handleInput = React.useCallback(() => {
|
|
1424
1657
|
if (editorRef.current) {
|
|
1425
|
-
const next =
|
|
1658
|
+
const next = getCleanHtml();
|
|
1426
1659
|
setHtml(next);
|
|
1427
1660
|
lastSynchronizedHtmlRef.current = next;
|
|
1428
1661
|
onChange && onChange(next);
|
|
@@ -1459,6 +1692,18 @@ function RichTextEditor({
|
|
|
1459
1692
|
}, [disabled]);
|
|
1460
1693
|
const handleEditorClick = React.useCallback(e => {
|
|
1461
1694
|
setSelectionVersion(v => v + 1);
|
|
1695
|
+
const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
|
|
1696
|
+
if (deleteBtn && editable && editorFocused) {
|
|
1697
|
+
e.preventDefault();
|
|
1698
|
+
e.stopPropagation();
|
|
1699
|
+
const wrapper = deleteBtn.closest('.image-container, .video-container');
|
|
1700
|
+
if (wrapper) {
|
|
1701
|
+
wrapper.remove();
|
|
1702
|
+
triggerChange();
|
|
1703
|
+
}
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1462
1707
|
// Check if the click is on a link
|
|
1463
1708
|
const clickedLink = e.target.closest('a');
|
|
1464
1709
|
if (clickedLink) {
|
|
@@ -1491,7 +1736,7 @@ function RichTextEditor({
|
|
|
1491
1736
|
}
|
|
1492
1737
|
}, 0);
|
|
1493
1738
|
}
|
|
1494
|
-
}, [editable, disabled]);
|
|
1739
|
+
}, [editable, disabled, editorFocused, triggerChange]);
|
|
1495
1740
|
const renderImageToolbar = () => {
|
|
1496
1741
|
if (!selectedImage || !editorRef.current || !editable) return null;
|
|
1497
1742
|
const editorRect = editorRef.current.getBoundingClientRect();
|
|
@@ -1561,6 +1806,9 @@ function RichTextEditor({
|
|
|
1561
1806
|
title: "Remove Image"
|
|
1562
1807
|
}, "\xD7"));
|
|
1563
1808
|
};
|
|
1809
|
+
if (isLoading) {
|
|
1810
|
+
return /*#__PURE__*/React.createElement(Spinner, null);
|
|
1811
|
+
}
|
|
1564
1812
|
return /*#__PURE__*/React.createElement("div", {
|
|
1565
1813
|
className: "rte-main-wrapper",
|
|
1566
1814
|
style: {
|
|
@@ -1986,12 +2234,14 @@ function RichTextEditor({
|
|
|
1986
2234
|
onDragOver: e => e.preventDefault(),
|
|
1987
2235
|
onKeyDown: handleKeyDown,
|
|
1988
2236
|
onClick: handleEditorClick,
|
|
2237
|
+
onFocus: handleEditorFocus,
|
|
2238
|
+
onBlur: handleEditorBlur,
|
|
1989
2239
|
style: {
|
|
1990
2240
|
minHeight: minHeight || '150px',
|
|
1991
2241
|
maxHeight: maxHeight || '500px',
|
|
1992
2242
|
paddingLeft: paddingLeft || '12px'
|
|
1993
2243
|
},
|
|
1994
|
-
className:
|
|
2244
|
+
className: `rte-content${editable ? " rte-is-editable" : ""}${editorFocused ? " rte-is-focused" : ""}`
|
|
1995
2245
|
}), renderImageToolbar(), /*#__PURE__*/React.createElement("div", {
|
|
1996
2246
|
className: "rte-footer"
|
|
1997
2247
|
}, /*#__PURE__*/React.createElement("div", {
|