react-lite-rich-text-editor 1.1.6 → 1.1.8
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/README.md +48 -1
- package/dist/index.cjs.js +390 -244
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.esm.js +390 -244
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -231,6 +231,27 @@ const FaFont = ({
|
|
|
231
231
|
}
|
|
232
232
|
});
|
|
233
233
|
};
|
|
234
|
+
const FaLink = ({
|
|
235
|
+
className,
|
|
236
|
+
size,
|
|
237
|
+
color,
|
|
238
|
+
style
|
|
239
|
+
}) => {
|
|
240
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
241
|
+
className: className,
|
|
242
|
+
style: {
|
|
243
|
+
display: 'inline-flex',
|
|
244
|
+
alignItems: 'center',
|
|
245
|
+
justifyContent: 'center',
|
|
246
|
+
fontSize: size || '1em',
|
|
247
|
+
color: color || 'inherit',
|
|
248
|
+
...style
|
|
249
|
+
},
|
|
250
|
+
dangerouslySetInnerHTML: {
|
|
251
|
+
__html: `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z"></path></svg>`
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
};
|
|
234
255
|
const FaTable = ({
|
|
235
256
|
className,
|
|
236
257
|
size,
|
|
@@ -388,7 +409,7 @@ function styleInject(css, ref) {
|
|
|
388
409
|
}
|
|
389
410
|
}
|
|
390
411
|
|
|
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:
|
|
412
|
+
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-heading-select{width:112px}.rte-toolbar-button-text{font-size:12px;font-weight:700;min-width:32px;padding:0 8px;width:auto}.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-wrapper{position:relative}.rte-placeholder{color:#9ca3af;font-size:16px;line-height:1.6;pointer-events:none;position:absolute;top:12px;user-select:none}.rte-content ul{list-style-type:disc;margin-left:1.5rem}.rte-content ol{list-style-type:decimal;margin-left:1.5rem}.rte-content blockquote{background:#f8fafc;border-left:4px solid #bfdbfe;border-radius:0 8px 8px 0;color:#475569;margin:12px 0;padding:8px 14px}.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);display:block;height:0;line-height:0;margin:16px 0;max-width:100%;overflow:hidden;padding-bottom:56.25%;position:relative;width:100%}.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;box-sizing:border-box;font-size:14px;outline:none;padding:8px 12px;transition:all .15s ease;width:100%}.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%;position:relative;width:fit-content}.image-container.image-align-center,.video-container.image-align-center{margin-left:auto;margin-right:auto}.image-container.image-align-left,.video-container.image-align-left{margin-left:0;margin-right:auto}.image-container.image-align-right,.video-container.image-align-right{margin-left:auto;margin-right:0}.image-container.rte-media-selected,.video-container.rte-media-selected{border-radius:12px;outline:2px solid #3b82f6;outline-offset:2px}.image-media-frame{display:block;line-height:0;max-width:100%;position:relative;width:100%}.image-media-frame img{border-radius:12px;display:block;height:auto;margin:0;max-width:100%;width:auto}.image-container[data-width-percent] .image-media-frame,.image-container[data-width-percent] .image-media-frame img{width:100%}.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}.rte-content.rte-is-editable .video-container.rte-media-selected iframe{pointer-events:none}.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-handle{background:#3b82f6;border:2px solid #fff;border-radius:3px;bottom:2px;box-shadow:0 1px 3px rgba(15,23,42,.2);cursor:nwse-resize;height:12px;pointer-events:auto;position:absolute;right:2px;width:12px;z-index:60}.media-resize-handle:hover{background:#2563eb}.video-container .media-resize-handle{bottom:6px;right:6px}.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-media-toolbar{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);display:flex;gap:2px;padding:4px;pointer-events:auto}.rte-media-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:28px;padding:0 6px;transition:all .15s ease}.rte-media-toolbar button.active,.rte-media-toolbar button:hover{background-color:#eff6ff;color:#2563eb}.rte-media-toolbar button.danger{color:#ef4444}.rte-media-toolbar button.danger:hover{background-color:#fef2f2;color:#dc2626}.rte-media-toolbar-divider{background:#e5e7eb;height:18px;margin:0 2px;width:1px}.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
413
|
styleInject(css_248z);
|
|
393
414
|
|
|
394
415
|
// Helper functions for HTML escaping
|
|
@@ -405,11 +426,12 @@ function RichTextEditor({
|
|
|
405
426
|
showEditButton,
|
|
406
427
|
onBlur,
|
|
407
428
|
disabled = false,
|
|
408
|
-
editable: initialEditable =
|
|
429
|
+
editable: initialEditable = true,
|
|
409
430
|
value,
|
|
410
431
|
isLoading,
|
|
411
432
|
isList = false,
|
|
412
433
|
label,
|
|
434
|
+
placeholder = "Type here...",
|
|
413
435
|
showBorder = true,
|
|
414
436
|
paddingLeft,
|
|
415
437
|
minHeight,
|
|
@@ -446,6 +468,7 @@ function RichTextEditor({
|
|
|
446
468
|
// NEW: Track current line height
|
|
447
469
|
const [currentLineHeight, setCurrentLineHeight] = useState("");
|
|
448
470
|
const [activeAlign, setActiveAlign] = useState(null);
|
|
471
|
+
const [currentBlockFormat, setCurrentBlockFormat] = useState("div");
|
|
449
472
|
const [imageModalOpen, setImageModalOpen] = useState(false);
|
|
450
473
|
const [selectedImageUrl, setSelectedImageUrl] = useState("");
|
|
451
474
|
const [zoomLevel, setZoomLevel] = useState(1);
|
|
@@ -457,11 +480,12 @@ function RichTextEditor({
|
|
|
457
480
|
const [tableRows, setTableRows] = useState(3);
|
|
458
481
|
const [tableCols, setTableCols] = useState(3);
|
|
459
482
|
const [selectionVersion, setSelectionVersion] = useState(0);
|
|
460
|
-
const [
|
|
483
|
+
const [selectedMedia, setSelectedMedia] = useState(null);
|
|
461
484
|
const [metrics, setMetrics] = useState({
|
|
462
485
|
words: 0,
|
|
463
486
|
chars: 0
|
|
464
487
|
});
|
|
488
|
+
const [isEmpty, setIsEmpty] = useState(!value);
|
|
465
489
|
const updateMetrics = useCallback(() => {
|
|
466
490
|
if (!editorRef.current) return;
|
|
467
491
|
// Calculate metrics immediately but outside of render path
|
|
@@ -473,6 +497,11 @@ function RichTextEditor({
|
|
|
473
497
|
words,
|
|
474
498
|
chars
|
|
475
499
|
});
|
|
500
|
+
|
|
501
|
+
// Track emptiness for the placeholder. Account for media-only content.
|
|
502
|
+
const stripped = text.replace(/[\u200B\u00A0\s]/g, "");
|
|
503
|
+
const hasMedia = !!editorRef.current.querySelector("img, table, iframe");
|
|
504
|
+
setIsEmpty(stripped.length === 0 && !hasMedia);
|
|
476
505
|
}, []);
|
|
477
506
|
const openImageModal = url => {
|
|
478
507
|
if (editorRef.current) {
|
|
@@ -537,11 +566,10 @@ function RichTextEditor({
|
|
|
537
566
|
requestAnimationFrame(() => syncProcessedMediaRef.current(editorRef.current));
|
|
538
567
|
}
|
|
539
568
|
}, [value]);
|
|
540
|
-
|
|
541
|
-
// Runs whenever editable changes (toggles delete icon visibility)
|
|
542
569
|
useEffect(() => {
|
|
543
570
|
if (!editable) {
|
|
544
571
|
setEditorFocused(false);
|
|
572
|
+
clearMediaSelection();
|
|
545
573
|
}
|
|
546
574
|
syncProcessedMediaRef.current(editorRef.current);
|
|
547
575
|
}, [editable]);
|
|
@@ -705,17 +733,15 @@ function RichTextEditor({
|
|
|
705
733
|
return rgbToHex(computedColor);
|
|
706
734
|
};
|
|
707
735
|
const stripEditorChrome = root => {
|
|
708
|
-
root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-
|
|
736
|
+
root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handle").forEach(element => element.remove());
|
|
737
|
+
root.querySelectorAll(".rte-media-selected").forEach(element => {
|
|
738
|
+
element.classList.remove("rte-media-selected");
|
|
739
|
+
});
|
|
709
740
|
return root;
|
|
710
741
|
};
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
return
|
|
714
|
-
minWidth: 120,
|
|
715
|
-
minHeight: 80,
|
|
716
|
-
maxWidth,
|
|
717
|
-
maxHeight: 720
|
|
718
|
-
};
|
|
742
|
+
const getEditorInnerWidth = () => {
|
|
743
|
+
if (!editorRef.current) return 800;
|
|
744
|
+
return Math.max(editorRef.current.clientWidth - 24, 200);
|
|
719
745
|
};
|
|
720
746
|
const ensureImageMediaFrame = imageContainer => {
|
|
721
747
|
if (!imageContainer) return null;
|
|
@@ -723,160 +749,164 @@ function RichTextEditor({
|
|
|
723
749
|
if (frame) return frame;
|
|
724
750
|
frame = document.createElement("div");
|
|
725
751
|
frame.className = "image-media-frame";
|
|
726
|
-
["width", "height", "marginLeft", "marginTop", "maxWidth"].forEach(prop => {
|
|
727
|
-
if (imageContainer.style[prop]) {
|
|
728
|
-
frame.style[prop] = imageContainer.style[prop];
|
|
729
|
-
imageContainer.style[prop] = "";
|
|
730
|
-
}
|
|
731
|
-
});
|
|
732
|
-
if (imageContainer.dataset.explicitHeight) {
|
|
733
|
-
frame.dataset.explicitHeight = imageContainer.dataset.explicitHeight;
|
|
734
|
-
delete imageContainer.dataset.explicitHeight;
|
|
735
|
-
}
|
|
736
752
|
const children = Array.from(imageContainer.children);
|
|
737
753
|
imageContainer.appendChild(frame);
|
|
738
754
|
children.forEach(child => frame.appendChild(child));
|
|
739
755
|
return frame;
|
|
740
756
|
};
|
|
741
757
|
const getImageMediaTarget = imageContainer => ensureImageMediaFrame(imageContainer) || imageContainer;
|
|
742
|
-
const
|
|
758
|
+
const getMediaWidthPercent = container => {
|
|
759
|
+
if (!container) return 100;
|
|
760
|
+
if (container.dataset.widthPercent) {
|
|
761
|
+
return Number(container.dataset.widthPercent);
|
|
762
|
+
}
|
|
763
|
+
const width = container.style.width || "";
|
|
764
|
+
if (width.endsWith("%")) {
|
|
765
|
+
return parseInt(width, 10) || 100;
|
|
766
|
+
}
|
|
767
|
+
const editorWidth = getEditorInnerWidth();
|
|
768
|
+
const rect = container.getBoundingClientRect();
|
|
769
|
+
if (editorWidth > 0 && rect.width > 0) {
|
|
770
|
+
return Math.round(rect.width / editorWidth * 100);
|
|
771
|
+
}
|
|
772
|
+
return 100;
|
|
773
|
+
};
|
|
774
|
+
const applyMediaWidthPercent = (container, percent) => {
|
|
775
|
+
if (!container) return;
|
|
776
|
+
const clamped = Math.max(25, Math.min(100, Math.round(percent)));
|
|
777
|
+
container.dataset.widthPercent = String(clamped);
|
|
778
|
+
container.style.width = `${clamped}%`;
|
|
779
|
+
container.style.maxWidth = "100%";
|
|
780
|
+
container.style.marginLeft = "";
|
|
781
|
+
container.style.marginTop = "";
|
|
782
|
+
if (container.classList.contains("video-container")) {
|
|
783
|
+
container.style.height = "0";
|
|
784
|
+
container.style.paddingBottom = "56.25%";
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const frame = getImageMediaTarget(container);
|
|
788
|
+
if (!frame) return;
|
|
789
|
+
frame.style.width = "100%";
|
|
790
|
+
frame.style.height = "";
|
|
743
791
|
const img = frame.querySelector("img");
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
792
|
+
if (img) {
|
|
793
|
+
img.style.height = "auto";
|
|
794
|
+
img.style.objectFit = "";
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
const normalizeMediaWidth = container => {
|
|
798
|
+
if (!container) return;
|
|
799
|
+
if (container.classList.contains("image-small")) {
|
|
800
|
+
container.classList.remove("image-small");
|
|
801
|
+
applyMediaWidthPercent(container, 50);
|
|
755
802
|
return;
|
|
756
803
|
}
|
|
757
|
-
if (
|
|
758
|
-
|
|
804
|
+
if (container.dataset.widthPercent) {
|
|
805
|
+
applyMediaWidthPercent(container, Number(container.dataset.widthPercent));
|
|
806
|
+
return;
|
|
759
807
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
808
|
+
const width = container.style.width || "";
|
|
809
|
+
if (width.endsWith("%")) {
|
|
810
|
+
applyMediaWidthPercent(container, parseInt(width, 10) || 100);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (width.endsWith("px")) {
|
|
814
|
+
const editorWidth = getEditorInnerWidth();
|
|
815
|
+
const px = parseFloat(width);
|
|
816
|
+
if (editorWidth > 0 && px > 0) {
|
|
817
|
+
applyMediaWidthPercent(container, Math.round(px / editorWidth * 100));
|
|
768
818
|
}
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (container.classList.contains("video-container")) {
|
|
822
|
+
applyMediaWidthPercent(container, 100);
|
|
769
823
|
}
|
|
770
824
|
};
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
825
|
+
const clearMediaSelection = () => {
|
|
826
|
+
var _editorRef$current;
|
|
827
|
+
(_editorRef$current = editorRef.current) === null || _editorRef$current === void 0 || _editorRef$current.querySelectorAll(".rte-media-selected").forEach(element => {
|
|
828
|
+
element.classList.remove("rte-media-selected");
|
|
829
|
+
});
|
|
830
|
+
setSelectedMedia(null);
|
|
831
|
+
};
|
|
832
|
+
const selectMediaContainer = container => {
|
|
833
|
+
var _editorRef$current2;
|
|
834
|
+
if (!container || !((_editorRef$current2 = editorRef.current) !== null && _editorRef$current2 !== void 0 && _editorRef$current2.contains(container))) return;
|
|
835
|
+
editorRef.current.querySelectorAll(".rte-media-selected").forEach(element => {
|
|
836
|
+
element.classList.remove("rte-media-selected");
|
|
837
|
+
});
|
|
838
|
+
container.classList.add("rte-media-selected");
|
|
839
|
+
setSelectedMedia(container);
|
|
776
840
|
};
|
|
777
841
|
const attachMediaResizeHandle = container => {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const resizeTarget =
|
|
842
|
+
var _resizeTarget$querySe;
|
|
843
|
+
if (!container) return;
|
|
844
|
+
const resizeTarget = container.classList.contains("video-container") ? container : getImageMediaTarget(container);
|
|
781
845
|
if (!resizeTarget) return;
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
handle.addEventListener("mousedown", event => {
|
|
808
|
-
if (!editable) return;
|
|
809
|
-
event.preventDefault();
|
|
810
|
-
event.stopPropagation();
|
|
811
|
-
const limits = getMediaSizeLimits();
|
|
812
|
-
const rect = resizeTarget.getBoundingClientRect();
|
|
813
|
-
const startX = event.clientX;
|
|
814
|
-
const startY = event.clientY;
|
|
815
|
-
const startWidth = rect.width;
|
|
816
|
-
const startHeight = rect.height;
|
|
817
|
-
const startMarginLeft = Number.parseFloat(resizeTarget.style.marginLeft) || 0;
|
|
818
|
-
const startMarginTop = Number.parseFloat(resizeTarget.style.marginTop) || 0;
|
|
819
|
-
if (isVideo) {
|
|
820
|
-
resizeTarget.style.paddingBottom = "0";
|
|
821
|
-
}
|
|
822
|
-
const onMouseMove = moveEvent => {
|
|
823
|
-
const deltaX = moveEvent.clientX - startX;
|
|
824
|
-
const deltaY = moveEvent.clientY - startY;
|
|
825
|
-
let nextWidth = startWidth;
|
|
826
|
-
let nextHeight = startHeight;
|
|
827
|
-
if (edge === "right") {
|
|
828
|
-
nextWidth = startWidth + deltaX;
|
|
829
|
-
} else if (edge === "left") {
|
|
830
|
-
nextWidth = startWidth - deltaX;
|
|
831
|
-
} else if (edge === "bottom") {
|
|
832
|
-
nextHeight = startHeight + deltaY;
|
|
833
|
-
} else if (edge === "top") {
|
|
834
|
-
nextHeight = startHeight - deltaY;
|
|
835
|
-
}
|
|
836
|
-
nextWidth = Math.max(limits.minWidth, Math.min(nextWidth, limits.maxWidth));
|
|
837
|
-
nextHeight = Math.max(limits.minHeight, Math.min(nextHeight, limits.maxHeight));
|
|
838
|
-
if (edge === "left") {
|
|
839
|
-
resizeTarget.style.marginLeft = `${Math.round(startMarginLeft + (startWidth - nextWidth))}px`;
|
|
840
|
-
}
|
|
841
|
-
if (edge === "top") {
|
|
842
|
-
resizeTarget.style.marginTop = `${Math.round(startMarginTop + (startHeight - nextHeight))}px`;
|
|
843
|
-
}
|
|
844
|
-
if (isVideo) {
|
|
845
|
-
applyVideoMediaSize(resizeTarget, nextWidth, nextHeight);
|
|
846
|
-
} else {
|
|
847
|
-
applyImageMediaSize(resizeTarget, nextWidth, nextHeight, edge);
|
|
848
|
-
}
|
|
849
|
-
};
|
|
850
|
-
const onMouseUp = () => {
|
|
851
|
-
document.removeEventListener("mousemove", onMouseMove);
|
|
852
|
-
document.removeEventListener("mouseup", onMouseUp);
|
|
853
|
-
triggerChange();
|
|
854
|
-
};
|
|
855
|
-
document.addEventListener("mousemove", onMouseMove);
|
|
856
|
-
document.addEventListener("mouseup", onMouseUp);
|
|
857
|
-
});
|
|
858
|
-
handlesWrapper.appendChild(handle);
|
|
846
|
+
(_resizeTarget$querySe = resizeTarget.querySelector(".media-resize-handle")) === null || _resizeTarget$querySe === void 0 || _resizeTarget$querySe.remove();
|
|
847
|
+
const handle = document.createElement("div");
|
|
848
|
+
handle.className = "media-resize-handle";
|
|
849
|
+
handle.title = "Drag to resize";
|
|
850
|
+
handle.setAttribute("contenteditable", "false");
|
|
851
|
+
handle.addEventListener("mousedown", event => {
|
|
852
|
+
if (!editable) return;
|
|
853
|
+
event.preventDefault();
|
|
854
|
+
event.stopPropagation();
|
|
855
|
+
selectMediaContainer(container);
|
|
856
|
+
const editorWidth = getEditorInnerWidth();
|
|
857
|
+
const startX = event.clientX;
|
|
858
|
+
const startWidth = container.getBoundingClientRect().width;
|
|
859
|
+
const onMouseMove = moveEvent => {
|
|
860
|
+
const nextWidth = Math.max(60, startWidth + (moveEvent.clientX - startX));
|
|
861
|
+
const percent = Math.round(nextWidth / editorWidth * 100);
|
|
862
|
+
applyMediaWidthPercent(container, percent);
|
|
863
|
+
};
|
|
864
|
+
const onMouseUp = () => {
|
|
865
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
866
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
867
|
+
triggerChange();
|
|
868
|
+
};
|
|
869
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
870
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
859
871
|
});
|
|
860
|
-
resizeTarget.appendChild(
|
|
872
|
+
resizeTarget.appendChild(handle);
|
|
861
873
|
};
|
|
862
874
|
const handleEditorFocus = () => {
|
|
863
875
|
setEditorFocused(true);
|
|
864
876
|
};
|
|
865
877
|
const handleEditorBlur = () => {
|
|
866
878
|
requestAnimationFrame(() => {
|
|
867
|
-
var _editorRef$
|
|
868
|
-
if (!((_editorRef$
|
|
879
|
+
var _editorRef$current3;
|
|
880
|
+
if (!((_editorRef$current3 = editorRef.current) !== null && _editorRef$current3 !== void 0 && _editorRef$current3.contains(document.activeElement))) {
|
|
869
881
|
setEditorFocused(false);
|
|
870
882
|
}
|
|
871
883
|
});
|
|
872
884
|
};
|
|
873
885
|
const updateMediaControlVisibility = container => {
|
|
874
|
-
const
|
|
875
|
-
if (
|
|
876
|
-
|
|
877
|
-
|
|
886
|
+
const handle = container.querySelector(".media-resize-handle");
|
|
887
|
+
if (handle instanceof HTMLElement) {
|
|
888
|
+
handle.style.display = editable ? "block" : "none";
|
|
889
|
+
handle.style.pointerEvents = editable ? "auto" : "none";
|
|
878
890
|
}
|
|
879
891
|
};
|
|
892
|
+
const BLOCK_TAGS = ["P", "DIV", "H1", "H2", "H3", "BLOCKQUOTE", "LI"];
|
|
893
|
+
const getActiveBlock = node => {
|
|
894
|
+
if (!editorRef.current || !node) return null;
|
|
895
|
+
let current = node.nodeType === 3 ? node.parentNode : node;
|
|
896
|
+
while (current && current !== editorRef.current) {
|
|
897
|
+
if (current.nodeType === 1 && BLOCK_TAGS.includes(current.tagName)) {
|
|
898
|
+
return current;
|
|
899
|
+
}
|
|
900
|
+
current = current.parentNode;
|
|
901
|
+
}
|
|
902
|
+
return editorRef.current;
|
|
903
|
+
};
|
|
904
|
+
const getBlockFormat = node => {
|
|
905
|
+
const block = getActiveBlock(node);
|
|
906
|
+
if (!block || block === editorRef.current) return "div";
|
|
907
|
+
const tag = block.tagName.toLowerCase();
|
|
908
|
+
return tag === "p" || tag === "li" ? "div" : tag;
|
|
909
|
+
};
|
|
880
910
|
const createMediaDeleteButton = (title, className, onRemove) => {
|
|
881
911
|
const deleteBtn = document.createElement("button");
|
|
882
912
|
deleteBtn.type = "button";
|
|
@@ -895,10 +925,10 @@ function RichTextEditor({
|
|
|
895
925
|
// Listen for selection changes globally to update styles and list type in one pass
|
|
896
926
|
useEffect(() => {
|
|
897
927
|
const handleGlobalSelectionSync = () => {
|
|
898
|
-
var _editorRef$
|
|
928
|
+
var _editorRef$current4;
|
|
899
929
|
// Only sync if the editor has focus
|
|
900
930
|
const sel = window.getSelection();
|
|
901
|
-
if (!sel || !sel.rangeCount || !((_editorRef$
|
|
931
|
+
if (!sel || !sel.rangeCount || !((_editorRef$current4 = editorRef.current) !== null && _editorRef$current4 !== void 0 && _editorRef$current4.contains(sel.anchorNode))) {
|
|
902
932
|
return;
|
|
903
933
|
}
|
|
904
934
|
|
|
@@ -946,6 +976,7 @@ function RichTextEditor({
|
|
|
946
976
|
} else {
|
|
947
977
|
setCurrentFontSize("16");
|
|
948
978
|
}
|
|
979
|
+
setCurrentBlockFormat(getBlockFormat(sel.anchorNode));
|
|
949
980
|
};
|
|
950
981
|
document.addEventListener("selectionchange", handleGlobalSelectionSync);
|
|
951
982
|
return () => {
|
|
@@ -1183,11 +1214,25 @@ function RichTextEditor({
|
|
|
1183
1214
|
if (!videoContainer.querySelector(".video-delete-button")) {
|
|
1184
1215
|
const deleteBtn = createMediaDeleteButton("Remove video", "video-delete-button image-delete-button", () => {
|
|
1185
1216
|
videoContainer.remove();
|
|
1217
|
+
clearMediaSelection();
|
|
1186
1218
|
triggerChange && triggerChange();
|
|
1187
1219
|
});
|
|
1188
1220
|
videoContainer.appendChild(deleteBtn);
|
|
1189
1221
|
}
|
|
1222
|
+
if (!videoContainer.dataset.mediaEnhanced) {
|
|
1223
|
+
videoContainer.dataset.mediaEnhanced = "true";
|
|
1224
|
+
if (!videoContainer.classList.contains("image-align-left") && !videoContainer.classList.contains("image-align-center") && !videoContainer.classList.contains("image-align-right")) {
|
|
1225
|
+
videoContainer.classList.add("image-align-left");
|
|
1226
|
+
}
|
|
1227
|
+
videoContainer.addEventListener("click", event => {
|
|
1228
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1229
|
+
event.preventDefault();
|
|
1230
|
+
event.stopPropagation();
|
|
1231
|
+
selectMediaContainer(videoContainer);
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1190
1234
|
attachMediaResizeHandle(videoContainer);
|
|
1235
|
+
normalizeMediaWidth(videoContainer);
|
|
1191
1236
|
updateMediaControlVisibility(videoContainer);
|
|
1192
1237
|
});
|
|
1193
1238
|
};
|
|
@@ -1207,6 +1252,7 @@ function RichTextEditor({
|
|
|
1207
1252
|
}));
|
|
1208
1253
|
}
|
|
1209
1254
|
attachMediaResizeHandle(existingWrapper);
|
|
1255
|
+
normalizeMediaWidth(existingWrapper);
|
|
1210
1256
|
updateMediaControlVisibility(existingWrapper);
|
|
1211
1257
|
return;
|
|
1212
1258
|
}
|
|
@@ -1216,24 +1262,21 @@ function RichTextEditor({
|
|
|
1216
1262
|
wrapper.style.cursor = editable ? "pointer" : "default";
|
|
1217
1263
|
const frame = document.createElement("div");
|
|
1218
1264
|
frame.className = "image-media-frame";
|
|
1219
|
-
if (img.getAttribute("width") && !frame.style.width) {
|
|
1220
|
-
frame.style.width = `${img.getAttribute("width")}px`;
|
|
1221
|
-
} else if (img.style.width && img.style.width.endsWith("px")) {
|
|
1222
|
-
frame.style.width = img.style.width;
|
|
1223
|
-
}
|
|
1224
1265
|
img.classList.add("rte-image");
|
|
1225
1266
|
img.setAttribute("data-align", align);
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
img.style.height = frame.dataset.explicitHeight ? "100%" : "auto";
|
|
1229
|
-
} else {
|
|
1230
|
-
img.style.width = "";
|
|
1231
|
-
img.style.height = "auto";
|
|
1232
|
-
}
|
|
1233
|
-
img.addEventListener("click", event => {
|
|
1267
|
+
img.style.height = "auto";
|
|
1268
|
+
img.addEventListener("dblclick", event => {
|
|
1234
1269
|
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1270
|
+
event.preventDefault();
|
|
1271
|
+
event.stopPropagation();
|
|
1235
1272
|
openImageModal(img.src);
|
|
1236
1273
|
});
|
|
1274
|
+
img.addEventListener("click", event => {
|
|
1275
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1276
|
+
event.preventDefault();
|
|
1277
|
+
event.stopPropagation();
|
|
1278
|
+
selectMediaContainer(wrapper);
|
|
1279
|
+
});
|
|
1237
1280
|
const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1238
1281
|
wrapper.remove();
|
|
1239
1282
|
triggerChange && triggerChange();
|
|
@@ -1248,6 +1291,7 @@ function RichTextEditor({
|
|
|
1248
1291
|
frame.appendChild(deleteBtn);
|
|
1249
1292
|
wrapper.appendChild(frame);
|
|
1250
1293
|
attachMediaResizeHandle(wrapper);
|
|
1294
|
+
normalizeMediaWidth(wrapper);
|
|
1251
1295
|
if (nextSibling) {
|
|
1252
1296
|
parentNode.insertBefore(wrapper, nextSibling);
|
|
1253
1297
|
} else {
|
|
@@ -1274,9 +1318,19 @@ function RichTextEditor({
|
|
|
1274
1318
|
const img = document.createElement('img');
|
|
1275
1319
|
img.src = dataUrl;
|
|
1276
1320
|
img.alt = fileName || "image";
|
|
1277
|
-
img.addEventListener("
|
|
1321
|
+
img.addEventListener("dblclick", event => {
|
|
1322
|
+
event.preventDefault();
|
|
1323
|
+
event.stopPropagation();
|
|
1324
|
+
openImageModal(dataUrl);
|
|
1325
|
+
});
|
|
1326
|
+
img.addEventListener("click", event => {
|
|
1327
|
+
event.preventDefault();
|
|
1328
|
+
event.stopPropagation();
|
|
1329
|
+
selectMediaContainer(container);
|
|
1330
|
+
});
|
|
1278
1331
|
frame.appendChild(img);
|
|
1279
1332
|
container.appendChild(frame);
|
|
1333
|
+
applyMediaWidthPercent(container, 25);
|
|
1280
1334
|
|
|
1281
1335
|
// Insert at cursor position
|
|
1282
1336
|
insertNodeAtCursor(container);
|
|
@@ -1449,6 +1503,8 @@ function RichTextEditor({
|
|
|
1449
1503
|
selection.addRange(newRange);
|
|
1450
1504
|
};
|
|
1451
1505
|
const handleKeyDown = useCallback(e => {
|
|
1506
|
+
if (applyMarkdownShortcut(e)) return;
|
|
1507
|
+
|
|
1452
1508
|
// Handle Enter key
|
|
1453
1509
|
if (e.key === 'Enter') {
|
|
1454
1510
|
e.preventDefault();
|
|
@@ -1520,7 +1576,7 @@ function RichTextEditor({
|
|
|
1520
1576
|
return;
|
|
1521
1577
|
}
|
|
1522
1578
|
if (e.key === "Backspace") {
|
|
1523
|
-
var _node$closest, _node, _editorRef$
|
|
1579
|
+
var _node$closest, _node, _editorRef$current5;
|
|
1524
1580
|
const selection = window.getSelection();
|
|
1525
1581
|
if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
|
|
1526
1582
|
const range = selection.getRangeAt(0);
|
|
@@ -1530,7 +1586,7 @@ function RichTextEditor({
|
|
|
1530
1586
|
node = node.parentNode;
|
|
1531
1587
|
}
|
|
1532
1588
|
const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
|
|
1533
|
-
if (!listItem || !((_editorRef$
|
|
1589
|
+
if (!listItem || !((_editorRef$current5 = editorRef.current) !== null && _editorRef$current5 !== void 0 && _editorRef$current5.contains(listItem))) return;
|
|
1534
1590
|
const list = listItem.parentNode;
|
|
1535
1591
|
if (isListItemEffectivelyEmpty(listItem)) {
|
|
1536
1592
|
e.preventDefault();
|
|
@@ -1607,6 +1663,65 @@ function RichTextEditor({
|
|
|
1607
1663
|
const handleSelect = type => {
|
|
1608
1664
|
exec(type === "unordered" ? "insertUnorderedList" : "insertOrderedList");
|
|
1609
1665
|
};
|
|
1666
|
+
const applyBlockFormat = format => {
|
|
1667
|
+
document.execCommand("formatBlock", false, format);
|
|
1668
|
+
setCurrentBlockFormat(format);
|
|
1669
|
+
triggerChange();
|
|
1670
|
+
focus();
|
|
1671
|
+
};
|
|
1672
|
+
const clearFormatting = () => {
|
|
1673
|
+
document.execCommand("removeFormat", false, null);
|
|
1674
|
+
document.execCommand("unlink", false, null);
|
|
1675
|
+
document.execCommand("formatBlock", false, "div");
|
|
1676
|
+
setCurrentBlockFormat("div");
|
|
1677
|
+
setCurrentFontSize("16");
|
|
1678
|
+
setCurrentLineHeight("");
|
|
1679
|
+
setFontColor("#000000");
|
|
1680
|
+
triggerChange();
|
|
1681
|
+
focus();
|
|
1682
|
+
};
|
|
1683
|
+
const deleteTextBeforeCursorInBlock = (block, range, selection) => {
|
|
1684
|
+
const prefixRange = document.createRange();
|
|
1685
|
+
prefixRange.setStart(block, 0);
|
|
1686
|
+
prefixRange.setEnd(range.startContainer, range.startOffset);
|
|
1687
|
+
prefixRange.deleteContents();
|
|
1688
|
+
const nextRange = document.createRange();
|
|
1689
|
+
nextRange.setStart(block, 0);
|
|
1690
|
+
nextRange.collapse(true);
|
|
1691
|
+
selection.removeAllRanges();
|
|
1692
|
+
selection.addRange(nextRange);
|
|
1693
|
+
};
|
|
1694
|
+
const applyMarkdownShortcut = event => {
|
|
1695
|
+
if (event.key !== " " || event.ctrlKey || event.metaKey || event.altKey) {
|
|
1696
|
+
return false;
|
|
1697
|
+
}
|
|
1698
|
+
const selection = window.getSelection();
|
|
1699
|
+
if (!(selection !== null && selection !== void 0 && selection.rangeCount) || !selection.isCollapsed || !editorRef.current) {
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
const range = selection.getRangeAt(0);
|
|
1703
|
+
const block = getActiveBlock(range.startContainer);
|
|
1704
|
+
if (!block || !editorRef.current.contains(block)) return false;
|
|
1705
|
+
const prefixRange = document.createRange();
|
|
1706
|
+
prefixRange.setStart(block, 0);
|
|
1707
|
+
prefixRange.setEnd(range.startContainer, range.startOffset);
|
|
1708
|
+
const textBeforeCursor = prefixRange.toString().replace(/\u00A0/g, " ").trim();
|
|
1709
|
+
const shortcuts = {
|
|
1710
|
+
"#": () => applyBlockFormat("h1"),
|
|
1711
|
+
"##": () => applyBlockFormat("h2"),
|
|
1712
|
+
"###": () => applyBlockFormat("h3"),
|
|
1713
|
+
">": () => applyBlockFormat("blockquote"),
|
|
1714
|
+
"-": () => handleSelect("unordered"),
|
|
1715
|
+
"*": () => handleSelect("unordered"),
|
|
1716
|
+
"1.": () => handleSelect("ordered")
|
|
1717
|
+
};
|
|
1718
|
+
const action = shortcuts[textBeforeCursor];
|
|
1719
|
+
if (!action) return false;
|
|
1720
|
+
event.preventDefault();
|
|
1721
|
+
deleteTextBeforeCursorInBlock(block, range, selection);
|
|
1722
|
+
action();
|
|
1723
|
+
return true;
|
|
1724
|
+
};
|
|
1610
1725
|
const onLineHeightChange = value => {
|
|
1611
1726
|
if (!value) return;
|
|
1612
1727
|
const sel = window.getSelection();
|
|
@@ -1788,6 +1903,7 @@ function RichTextEditor({
|
|
|
1788
1903
|
}
|
|
1789
1904
|
}, [disabled]);
|
|
1790
1905
|
const handleEditorClick = useCallback(e => {
|
|
1906
|
+
var _editorRef$current6;
|
|
1791
1907
|
setSelectionVersion(v => v + 1);
|
|
1792
1908
|
const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
|
|
1793
1909
|
if (deleteBtn && editable && editorFocused) {
|
|
@@ -1796,10 +1912,15 @@ function RichTextEditor({
|
|
|
1796
1912
|
const wrapper = deleteBtn.closest('.image-container, .video-container');
|
|
1797
1913
|
if (wrapper) {
|
|
1798
1914
|
wrapper.remove();
|
|
1915
|
+
clearMediaSelection();
|
|
1799
1916
|
triggerChange();
|
|
1800
1917
|
}
|
|
1801
1918
|
return;
|
|
1802
1919
|
}
|
|
1920
|
+
const clickedMedia = e.target.closest('.image-container, .video-container');
|
|
1921
|
+
if (clickedMedia && (_editorRef$current6 = editorRef.current) !== null && _editorRef$current6 !== void 0 && _editorRef$current6.contains(clickedMedia) && editable) {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1803
1924
|
|
|
1804
1925
|
// Check if the click is on a link
|
|
1805
1926
|
const clickedLink = e.target.closest('a');
|
|
@@ -1809,13 +1930,8 @@ function RichTextEditor({
|
|
|
1809
1930
|
window.open(clickedLink.href, '_blank');
|
|
1810
1931
|
return;
|
|
1811
1932
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
const clickedImg = e.target.closest('img');
|
|
1815
|
-
if (clickedImg && !clickedImg.closest('.rte-modal')) {
|
|
1816
|
-
setSelectedImage(clickedImg);
|
|
1817
|
-
} else if (!e.target.closest('.rte-image-toolbar')) {
|
|
1818
|
-
setSelectedImage(null);
|
|
1933
|
+
if (!e.target.closest('.rte-media-toolbar')) {
|
|
1934
|
+
clearMediaSelection();
|
|
1819
1935
|
}
|
|
1820
1936
|
|
|
1821
1937
|
// If disabled is true, prevent editing
|
|
@@ -1834,73 +1950,72 @@ function RichTextEditor({
|
|
|
1834
1950
|
}, 0);
|
|
1835
1951
|
}
|
|
1836
1952
|
}, [editable, disabled, editorFocused, triggerChange]);
|
|
1837
|
-
const
|
|
1838
|
-
if (!
|
|
1953
|
+
const renderMediaToolbar = () => {
|
|
1954
|
+
if (!selectedMedia || !editorRef.current || !editable) return null;
|
|
1839
1955
|
const editorRect = editorRef.current.getBoundingClientRect();
|
|
1840
|
-
const
|
|
1841
|
-
const top =
|
|
1842
|
-
const left =
|
|
1843
|
-
const width =
|
|
1956
|
+
const mediaRect = selectedMedia.getBoundingClientRect();
|
|
1957
|
+
const top = mediaRect.top - editorRect.top + editorRef.current.scrollTop;
|
|
1958
|
+
const left = mediaRect.left - editorRect.left + editorRef.current.scrollLeft;
|
|
1959
|
+
const width = mediaRect.width;
|
|
1960
|
+
const currentPercent = getMediaWidthPercent(selectedMedia);
|
|
1961
|
+
const widthPresets = [25, 50, 75, 100];
|
|
1844
1962
|
const handleAlignment = align => {
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
wrapper.classList.add(`image-align-${align}`);
|
|
1851
|
-
selectedImage.setAttribute('data-align', align);
|
|
1852
|
-
triggerChange();
|
|
1853
|
-
}
|
|
1963
|
+
selectedMedia.classList.remove("image-align-left", "image-align-center", "image-align-right");
|
|
1964
|
+
selectedMedia.classList.add(`image-align-${align}`);
|
|
1965
|
+
const img = selectedMedia.querySelector("img");
|
|
1966
|
+
if (img) img.setAttribute("data-align", align);
|
|
1967
|
+
triggerChange();
|
|
1854
1968
|
};
|
|
1855
|
-
const
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
wrapper.remove();
|
|
1859
|
-
setSelectedImage(null);
|
|
1860
|
-
triggerChange();
|
|
1861
|
-
}
|
|
1969
|
+
const setWidth = percent => {
|
|
1970
|
+
applyMediaWidthPercent(selectedMedia, percent);
|
|
1971
|
+
triggerChange();
|
|
1862
1972
|
};
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
}
|
|
1872
|
-
triggerChange();
|
|
1973
|
+
const removeMedia = () => {
|
|
1974
|
+
selectedMedia.remove();
|
|
1975
|
+
clearMediaSelection();
|
|
1976
|
+
triggerChange();
|
|
1977
|
+
};
|
|
1978
|
+
const isActivePercent = percent => {
|
|
1979
|
+
if (!selectedMedia.dataset.widthPercent && !(selectedMedia.style.width || "").endsWith("%")) {
|
|
1980
|
+
return false;
|
|
1873
1981
|
}
|
|
1982
|
+
return Math.abs(currentPercent - percent) <= 3;
|
|
1874
1983
|
};
|
|
1875
1984
|
return /*#__PURE__*/React.createElement("div", {
|
|
1876
|
-
className: "rte-
|
|
1985
|
+
className: "rte-media-toolbar",
|
|
1877
1986
|
style: {
|
|
1878
|
-
position:
|
|
1879
|
-
top: Math.max(0, top -
|
|
1880
|
-
left: Math.max(
|
|
1987
|
+
position: "absolute",
|
|
1988
|
+
top: Math.max(0, top - 44),
|
|
1989
|
+
left: Math.max(8, left + width / 2 - 156),
|
|
1881
1990
|
zIndex: 1000
|
|
1882
1991
|
}
|
|
1883
1992
|
}, /*#__PURE__*/React.createElement("button", {
|
|
1884
1993
|
type: "button",
|
|
1885
|
-
onClick: () => handleAlignment(
|
|
1994
|
+
onClick: () => handleAlignment("left"),
|
|
1886
1995
|
title: "Align Left"
|
|
1887
1996
|
}, "L"), /*#__PURE__*/React.createElement("button", {
|
|
1888
1997
|
type: "button",
|
|
1889
|
-
onClick: () => handleAlignment(
|
|
1998
|
+
onClick: () => handleAlignment("center"),
|
|
1890
1999
|
title: "Align Center"
|
|
1891
2000
|
}, "C"), /*#__PURE__*/React.createElement("button", {
|
|
1892
2001
|
type: "button",
|
|
1893
|
-
onClick: () => handleAlignment(
|
|
2002
|
+
onClick: () => handleAlignment("right"),
|
|
1894
2003
|
title: "Align Right"
|
|
1895
|
-
}, "R"), /*#__PURE__*/React.createElement("
|
|
2004
|
+
}, "R"), /*#__PURE__*/React.createElement("span", {
|
|
2005
|
+
className: "rte-media-toolbar-divider"
|
|
2006
|
+
}), widthPresets.map(percent => /*#__PURE__*/React.createElement("button", {
|
|
2007
|
+
key: percent,
|
|
1896
2008
|
type: "button",
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2009
|
+
className: isActivePercent(percent) ? "active" : "",
|
|
2010
|
+
onClick: () => setWidth(percent),
|
|
2011
|
+
title: `${percent}% width`
|
|
2012
|
+
}, percent, "%")), /*#__PURE__*/React.createElement("span", {
|
|
2013
|
+
className: "rte-media-toolbar-divider"
|
|
2014
|
+
}), /*#__PURE__*/React.createElement("button", {
|
|
1900
2015
|
type: "button",
|
|
1901
|
-
onClick:
|
|
2016
|
+
onClick: removeMedia,
|
|
1902
2017
|
className: "danger",
|
|
1903
|
-
title: "Remove
|
|
2018
|
+
title: "Remove"
|
|
1904
2019
|
}, "\xD7"));
|
|
1905
2020
|
};
|
|
1906
2021
|
if (isLoading) {
|
|
@@ -1936,17 +2051,14 @@ function RichTextEditor({
|
|
|
1936
2051
|
e.preventDefault();
|
|
1937
2052
|
e.stopPropagation();
|
|
1938
2053
|
}
|
|
1939
|
-
}, /*#__PURE__*/React.createElement("div", {
|
|
2054
|
+
}, !disabled && /*#__PURE__*/React.createElement("div", {
|
|
1940
2055
|
className: "rte-toolbar"
|
|
1941
2056
|
}, /*#__PURE__*/React.createElement("button", {
|
|
1942
2057
|
type: "button",
|
|
1943
2058
|
title: "Bold",
|
|
1944
|
-
|
|
2059
|
+
onMouseDown: e => {
|
|
1945
2060
|
e.preventDefault();
|
|
1946
|
-
|
|
1947
|
-
document.execCommand("bold");
|
|
1948
|
-
handleInput();
|
|
1949
|
-
focus();
|
|
2061
|
+
exec("bold");
|
|
1950
2062
|
},
|
|
1951
2063
|
className: `rte-toolbar-button ${isBold ? "active" : ""}`
|
|
1952
2064
|
}, /*#__PURE__*/React.createElement(FaBold, {
|
|
@@ -1954,12 +2066,9 @@ function RichTextEditor({
|
|
|
1954
2066
|
})), /*#__PURE__*/React.createElement("button", {
|
|
1955
2067
|
type: "button",
|
|
1956
2068
|
title: "Italic",
|
|
1957
|
-
|
|
2069
|
+
onMouseDown: e => {
|
|
1958
2070
|
e.preventDefault();
|
|
1959
|
-
|
|
1960
|
-
document.execCommand("italic");
|
|
1961
|
-
handleInput();
|
|
1962
|
-
focus();
|
|
2071
|
+
exec("italic");
|
|
1963
2072
|
},
|
|
1964
2073
|
className: `rte-toolbar-button ${isItalic ? "active" : ""}`
|
|
1965
2074
|
}, /*#__PURE__*/React.createElement(FaItalic, {
|
|
@@ -1967,12 +2076,9 @@ function RichTextEditor({
|
|
|
1967
2076
|
})), /*#__PURE__*/React.createElement("button", {
|
|
1968
2077
|
type: "button",
|
|
1969
2078
|
title: "Underline",
|
|
1970
|
-
|
|
2079
|
+
onMouseDown: e => {
|
|
1971
2080
|
e.preventDefault();
|
|
1972
|
-
|
|
1973
|
-
document.execCommand("underline");
|
|
1974
|
-
handleInput();
|
|
1975
|
-
focus();
|
|
2081
|
+
exec("underline");
|
|
1976
2082
|
},
|
|
1977
2083
|
className: `rte-toolbar-button ${isUnderline ? "active" : ""}`
|
|
1978
2084
|
}, /*#__PURE__*/React.createElement(FaUnderline, {
|
|
@@ -1984,6 +2090,41 @@ function RichTextEditor({
|
|
|
1984
2090
|
backgroundColor: '#e5e7eb',
|
|
1985
2091
|
margin: '0 4px'
|
|
1986
2092
|
}
|
|
2093
|
+
}), /*#__PURE__*/React.createElement("select", {
|
|
2094
|
+
value: currentBlockFormat,
|
|
2095
|
+
onMouseDown: e => e.stopPropagation(),
|
|
2096
|
+
onChange: e => {
|
|
2097
|
+
e.preventDefault();
|
|
2098
|
+
e.stopPropagation();
|
|
2099
|
+
applyBlockFormat(e.target.value);
|
|
2100
|
+
},
|
|
2101
|
+
className: "rte-toolbar-select rte-heading-select",
|
|
2102
|
+
title: "Text style"
|
|
2103
|
+
}, /*#__PURE__*/React.createElement("option", {
|
|
2104
|
+
value: "div"
|
|
2105
|
+
}, "Paragraph"), /*#__PURE__*/React.createElement("option", {
|
|
2106
|
+
value: "h1"
|
|
2107
|
+
}, "Heading 1"), /*#__PURE__*/React.createElement("option", {
|
|
2108
|
+
value: "h2"
|
|
2109
|
+
}, "Heading 2"), /*#__PURE__*/React.createElement("option", {
|
|
2110
|
+
value: "h3"
|
|
2111
|
+
}, "Heading 3"), /*#__PURE__*/React.createElement("option", {
|
|
2112
|
+
value: "blockquote"
|
|
2113
|
+
}, "Quote")), /*#__PURE__*/React.createElement("button", {
|
|
2114
|
+
type: "button",
|
|
2115
|
+
title: "Clear Formatting",
|
|
2116
|
+
className: "rte-toolbar-button rte-toolbar-button-text",
|
|
2117
|
+
onMouseDown: e => {
|
|
2118
|
+
e.preventDefault();
|
|
2119
|
+
clearFormatting();
|
|
2120
|
+
}
|
|
2121
|
+
}, "Tx"), /*#__PURE__*/React.createElement("div", {
|
|
2122
|
+
style: {
|
|
2123
|
+
width: '1px',
|
|
2124
|
+
height: '20px',
|
|
2125
|
+
backgroundColor: '#e5e7eb',
|
|
2126
|
+
margin: '0 4px'
|
|
2127
|
+
}
|
|
1987
2128
|
}), /*#__PURE__*/React.createElement("select", {
|
|
1988
2129
|
value: currentFontSize,
|
|
1989
2130
|
onMouseDown: e => e.stopPropagation(),
|
|
@@ -2150,11 +2291,9 @@ function RichTextEditor({
|
|
|
2150
2291
|
e.preventDefault();
|
|
2151
2292
|
addLink();
|
|
2152
2293
|
}
|
|
2153
|
-
}, /*#__PURE__*/React.createElement(
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
}
|
|
2157
|
-
}, "\uD83D\uDD17")), /*#__PURE__*/React.createElement("input", {
|
|
2294
|
+
}, /*#__PURE__*/React.createElement(FaLink, {
|
|
2295
|
+
size: 14
|
|
2296
|
+
})), /*#__PURE__*/React.createElement("input", {
|
|
2158
2297
|
ref: fileInputRef,
|
|
2159
2298
|
type: "file",
|
|
2160
2299
|
accept: "image/*",
|
|
@@ -2321,9 +2460,17 @@ function RichTextEditor({
|
|
|
2321
2460
|
}
|
|
2322
2461
|
return null;
|
|
2323
2462
|
})()), /*#__PURE__*/React.createElement("div", {
|
|
2463
|
+
className: "rte-content-wrapper",
|
|
2464
|
+
style: {
|
|
2465
|
+
position: 'relative'
|
|
2466
|
+
}
|
|
2467
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
2324
2468
|
ref: editorRef,
|
|
2325
2469
|
contentEditable: editable && disabled !== true,
|
|
2326
2470
|
suppressContentEditableWarning: true,
|
|
2471
|
+
role: "textbox",
|
|
2472
|
+
"aria-multiline": "true",
|
|
2473
|
+
"aria-label": label || "Rich text editor",
|
|
2327
2474
|
onInput: handleInput,
|
|
2328
2475
|
onPaste: handlePaste,
|
|
2329
2476
|
onDrop: handleDrop,
|
|
@@ -2339,7 +2486,13 @@ function RichTextEditor({
|
|
|
2339
2486
|
paddingLeft: paddingLeft || '12px'
|
|
2340
2487
|
},
|
|
2341
2488
|
className: `rte-content${editable ? " rte-is-editable" : ""}${editorFocused ? " rte-is-focused" : ""}`
|
|
2342
|
-
}),
|
|
2489
|
+
}), isEmpty && editable && disabled !== true && /*#__PURE__*/React.createElement("div", {
|
|
2490
|
+
className: "rte-placeholder",
|
|
2491
|
+
style: {
|
|
2492
|
+
left: paddingLeft || '12px'
|
|
2493
|
+
},
|
|
2494
|
+
"aria-hidden": "true"
|
|
2495
|
+
}, placeholder)), renderMediaToolbar(), /*#__PURE__*/React.createElement("div", {
|
|
2343
2496
|
className: "rte-footer"
|
|
2344
2497
|
}, /*#__PURE__*/React.createElement("div", {
|
|
2345
2498
|
className: "rte-footer-content"
|
|
@@ -2355,13 +2508,11 @@ function RichTextEditor({
|
|
|
2355
2508
|
}, /*#__PURE__*/React.createElement("div", {
|
|
2356
2509
|
className: "rte-modal",
|
|
2357
2510
|
onClick: e => e.stopPropagation()
|
|
2511
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
2512
|
+
className: "rte-modal-header"
|
|
2358
2513
|
}, /*#__PURE__*/React.createElement("h3", {
|
|
2359
2514
|
className: "rte-modal-title"
|
|
2360
|
-
}, "Insert Link"), /*#__PURE__*/React.createElement("div", {
|
|
2361
|
-
className: "rte-modal-divider"
|
|
2362
|
-
}), /*#__PURE__*/React.createElement("div", {
|
|
2363
|
-
className: "rte-modal-body"
|
|
2364
|
-
}, /*#__PURE__*/React.createElement("div", {
|
|
2515
|
+
}, "Insert Link")), /*#__PURE__*/React.createElement("div", {
|
|
2365
2516
|
className: "rte-form-group"
|
|
2366
2517
|
}, /*#__PURE__*/React.createElement("label", {
|
|
2367
2518
|
className: "rte-label"
|
|
@@ -2383,20 +2534,15 @@ function RichTextEditor({
|
|
|
2383
2534
|
onChange: e => setLinkUrl(e.target.value),
|
|
2384
2535
|
onKeyDown: e => e.key === 'Enter' && confirmLink(),
|
|
2385
2536
|
autoFocus: true
|
|
2386
|
-
}))
|
|
2387
|
-
className: "rte-modal-
|
|
2388
|
-
style: {
|
|
2389
|
-
margin: '8px 0 20px 0'
|
|
2390
|
-
}
|
|
2391
|
-
}), /*#__PURE__*/React.createElement("div", {
|
|
2392
|
-
className: "rte-modal-footer"
|
|
2537
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
2538
|
+
className: "rte-modal-actions"
|
|
2393
2539
|
}, /*#__PURE__*/React.createElement("button", {
|
|
2394
2540
|
type: "button",
|
|
2395
|
-
className: "rte-
|
|
2541
|
+
className: "rte-button rte-button-secondary",
|
|
2396
2542
|
onClick: cancelLink
|
|
2397
2543
|
}, "Cancel"), /*#__PURE__*/React.createElement("button", {
|
|
2398
2544
|
type: "button",
|
|
2399
|
-
className: "rte-
|
|
2545
|
+
className: "rte-button rte-button-primary",
|
|
2400
2546
|
onClick: confirmLink,
|
|
2401
2547
|
disabled: !linkUrl
|
|
2402
2548
|
}, "Insert")))), tableModalOpen && /*#__PURE__*/React.createElement("div", {
|