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.cjs.js
CHANGED
|
@@ -233,6 +233,27 @@ const FaFont = ({
|
|
|
233
233
|
}
|
|
234
234
|
});
|
|
235
235
|
};
|
|
236
|
+
const FaLink = ({
|
|
237
|
+
className,
|
|
238
|
+
size,
|
|
239
|
+
color,
|
|
240
|
+
style
|
|
241
|
+
}) => {
|
|
242
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
243
|
+
className: className,
|
|
244
|
+
style: {
|
|
245
|
+
display: 'inline-flex',
|
|
246
|
+
alignItems: 'center',
|
|
247
|
+
justifyContent: 'center',
|
|
248
|
+
fontSize: size || '1em',
|
|
249
|
+
color: color || 'inherit',
|
|
250
|
+
...style
|
|
251
|
+
},
|
|
252
|
+
dangerouslySetInnerHTML: {
|
|
253
|
+
__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>`
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
};
|
|
236
257
|
const FaTable = ({
|
|
237
258
|
className,
|
|
238
259
|
size,
|
|
@@ -390,7 +411,7 @@ function styleInject(css, ref) {
|
|
|
390
411
|
}
|
|
391
412
|
}
|
|
392
413
|
|
|
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:
|
|
414
|
+
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}";
|
|
394
415
|
styleInject(css_248z);
|
|
395
416
|
|
|
396
417
|
// Helper functions for HTML escaping
|
|
@@ -407,11 +428,12 @@ function RichTextEditor({
|
|
|
407
428
|
showEditButton,
|
|
408
429
|
onBlur,
|
|
409
430
|
disabled = false,
|
|
410
|
-
editable: initialEditable =
|
|
431
|
+
editable: initialEditable = true,
|
|
411
432
|
value,
|
|
412
433
|
isLoading,
|
|
413
434
|
isList = false,
|
|
414
435
|
label,
|
|
436
|
+
placeholder = "Type here...",
|
|
415
437
|
showBorder = true,
|
|
416
438
|
paddingLeft,
|
|
417
439
|
minHeight,
|
|
@@ -448,6 +470,7 @@ function RichTextEditor({
|
|
|
448
470
|
// NEW: Track current line height
|
|
449
471
|
const [currentLineHeight, setCurrentLineHeight] = React.useState("");
|
|
450
472
|
const [activeAlign, setActiveAlign] = React.useState(null);
|
|
473
|
+
const [currentBlockFormat, setCurrentBlockFormat] = React.useState("div");
|
|
451
474
|
const [imageModalOpen, setImageModalOpen] = React.useState(false);
|
|
452
475
|
const [selectedImageUrl, setSelectedImageUrl] = React.useState("");
|
|
453
476
|
const [zoomLevel, setZoomLevel] = React.useState(1);
|
|
@@ -459,11 +482,12 @@ function RichTextEditor({
|
|
|
459
482
|
const [tableRows, setTableRows] = React.useState(3);
|
|
460
483
|
const [tableCols, setTableCols] = React.useState(3);
|
|
461
484
|
const [selectionVersion, setSelectionVersion] = React.useState(0);
|
|
462
|
-
const [
|
|
485
|
+
const [selectedMedia, setSelectedMedia] = React.useState(null);
|
|
463
486
|
const [metrics, setMetrics] = React.useState({
|
|
464
487
|
words: 0,
|
|
465
488
|
chars: 0
|
|
466
489
|
});
|
|
490
|
+
const [isEmpty, setIsEmpty] = React.useState(!value);
|
|
467
491
|
const updateMetrics = React.useCallback(() => {
|
|
468
492
|
if (!editorRef.current) return;
|
|
469
493
|
// Calculate metrics immediately but outside of render path
|
|
@@ -475,6 +499,11 @@ function RichTextEditor({
|
|
|
475
499
|
words,
|
|
476
500
|
chars
|
|
477
501
|
});
|
|
502
|
+
|
|
503
|
+
// Track emptiness for the placeholder. Account for media-only content.
|
|
504
|
+
const stripped = text.replace(/[\u200B\u00A0\s]/g, "");
|
|
505
|
+
const hasMedia = !!editorRef.current.querySelector("img, table, iframe");
|
|
506
|
+
setIsEmpty(stripped.length === 0 && !hasMedia);
|
|
478
507
|
}, []);
|
|
479
508
|
const openImageModal = url => {
|
|
480
509
|
if (editorRef.current) {
|
|
@@ -539,11 +568,10 @@ function RichTextEditor({
|
|
|
539
568
|
requestAnimationFrame(() => syncProcessedMediaRef.current(editorRef.current));
|
|
540
569
|
}
|
|
541
570
|
}, [value]);
|
|
542
|
-
|
|
543
|
-
// Runs whenever editable changes (toggles delete icon visibility)
|
|
544
571
|
React.useEffect(() => {
|
|
545
572
|
if (!editable) {
|
|
546
573
|
setEditorFocused(false);
|
|
574
|
+
clearMediaSelection();
|
|
547
575
|
}
|
|
548
576
|
syncProcessedMediaRef.current(editorRef.current);
|
|
549
577
|
}, [editable]);
|
|
@@ -707,17 +735,15 @@ function RichTextEditor({
|
|
|
707
735
|
return rgbToHex(computedColor);
|
|
708
736
|
};
|
|
709
737
|
const stripEditorChrome = root => {
|
|
710
|
-
root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-
|
|
738
|
+
root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handle").forEach(element => element.remove());
|
|
739
|
+
root.querySelectorAll(".rte-media-selected").forEach(element => {
|
|
740
|
+
element.classList.remove("rte-media-selected");
|
|
741
|
+
});
|
|
711
742
|
return root;
|
|
712
743
|
};
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
return
|
|
716
|
-
minWidth: 120,
|
|
717
|
-
minHeight: 80,
|
|
718
|
-
maxWidth,
|
|
719
|
-
maxHeight: 720
|
|
720
|
-
};
|
|
744
|
+
const getEditorInnerWidth = () => {
|
|
745
|
+
if (!editorRef.current) return 800;
|
|
746
|
+
return Math.max(editorRef.current.clientWidth - 24, 200);
|
|
721
747
|
};
|
|
722
748
|
const ensureImageMediaFrame = imageContainer => {
|
|
723
749
|
if (!imageContainer) return null;
|
|
@@ -725,160 +751,164 @@ function RichTextEditor({
|
|
|
725
751
|
if (frame) return frame;
|
|
726
752
|
frame = document.createElement("div");
|
|
727
753
|
frame.className = "image-media-frame";
|
|
728
|
-
["width", "height", "marginLeft", "marginTop", "maxWidth"].forEach(prop => {
|
|
729
|
-
if (imageContainer.style[prop]) {
|
|
730
|
-
frame.style[prop] = imageContainer.style[prop];
|
|
731
|
-
imageContainer.style[prop] = "";
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
if (imageContainer.dataset.explicitHeight) {
|
|
735
|
-
frame.dataset.explicitHeight = imageContainer.dataset.explicitHeight;
|
|
736
|
-
delete imageContainer.dataset.explicitHeight;
|
|
737
|
-
}
|
|
738
754
|
const children = Array.from(imageContainer.children);
|
|
739
755
|
imageContainer.appendChild(frame);
|
|
740
756
|
children.forEach(child => frame.appendChild(child));
|
|
741
757
|
return frame;
|
|
742
758
|
};
|
|
743
759
|
const getImageMediaTarget = imageContainer => ensureImageMediaFrame(imageContainer) || imageContainer;
|
|
744
|
-
const
|
|
760
|
+
const getMediaWidthPercent = container => {
|
|
761
|
+
if (!container) return 100;
|
|
762
|
+
if (container.dataset.widthPercent) {
|
|
763
|
+
return Number(container.dataset.widthPercent);
|
|
764
|
+
}
|
|
765
|
+
const width = container.style.width || "";
|
|
766
|
+
if (width.endsWith("%")) {
|
|
767
|
+
return parseInt(width, 10) || 100;
|
|
768
|
+
}
|
|
769
|
+
const editorWidth = getEditorInnerWidth();
|
|
770
|
+
const rect = container.getBoundingClientRect();
|
|
771
|
+
if (editorWidth > 0 && rect.width > 0) {
|
|
772
|
+
return Math.round(rect.width / editorWidth * 100);
|
|
773
|
+
}
|
|
774
|
+
return 100;
|
|
775
|
+
};
|
|
776
|
+
const applyMediaWidthPercent = (container, percent) => {
|
|
777
|
+
if (!container) return;
|
|
778
|
+
const clamped = Math.max(25, Math.min(100, Math.round(percent)));
|
|
779
|
+
container.dataset.widthPercent = String(clamped);
|
|
780
|
+
container.style.width = `${clamped}%`;
|
|
781
|
+
container.style.maxWidth = "100%";
|
|
782
|
+
container.style.marginLeft = "";
|
|
783
|
+
container.style.marginTop = "";
|
|
784
|
+
if (container.classList.contains("video-container")) {
|
|
785
|
+
container.style.height = "0";
|
|
786
|
+
container.style.paddingBottom = "56.25%";
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
const frame = getImageMediaTarget(container);
|
|
790
|
+
if (!frame) return;
|
|
791
|
+
frame.style.width = "100%";
|
|
792
|
+
frame.style.height = "";
|
|
745
793
|
const img = frame.querySelector("img");
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
794
|
+
if (img) {
|
|
795
|
+
img.style.height = "auto";
|
|
796
|
+
img.style.objectFit = "";
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
const normalizeMediaWidth = container => {
|
|
800
|
+
if (!container) return;
|
|
801
|
+
if (container.classList.contains("image-small")) {
|
|
802
|
+
container.classList.remove("image-small");
|
|
803
|
+
applyMediaWidthPercent(container, 50);
|
|
757
804
|
return;
|
|
758
805
|
}
|
|
759
|
-
if (
|
|
760
|
-
|
|
806
|
+
if (container.dataset.widthPercent) {
|
|
807
|
+
applyMediaWidthPercent(container, Number(container.dataset.widthPercent));
|
|
808
|
+
return;
|
|
761
809
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
810
|
+
const width = container.style.width || "";
|
|
811
|
+
if (width.endsWith("%")) {
|
|
812
|
+
applyMediaWidthPercent(container, parseInt(width, 10) || 100);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (width.endsWith("px")) {
|
|
816
|
+
const editorWidth = getEditorInnerWidth();
|
|
817
|
+
const px = parseFloat(width);
|
|
818
|
+
if (editorWidth > 0 && px > 0) {
|
|
819
|
+
applyMediaWidthPercent(container, Math.round(px / editorWidth * 100));
|
|
770
820
|
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (container.classList.contains("video-container")) {
|
|
824
|
+
applyMediaWidthPercent(container, 100);
|
|
771
825
|
}
|
|
772
826
|
};
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
827
|
+
const clearMediaSelection = () => {
|
|
828
|
+
var _editorRef$current;
|
|
829
|
+
(_editorRef$current = editorRef.current) === null || _editorRef$current === void 0 || _editorRef$current.querySelectorAll(".rte-media-selected").forEach(element => {
|
|
830
|
+
element.classList.remove("rte-media-selected");
|
|
831
|
+
});
|
|
832
|
+
setSelectedMedia(null);
|
|
833
|
+
};
|
|
834
|
+
const selectMediaContainer = container => {
|
|
835
|
+
var _editorRef$current2;
|
|
836
|
+
if (!container || !((_editorRef$current2 = editorRef.current) !== null && _editorRef$current2 !== void 0 && _editorRef$current2.contains(container))) return;
|
|
837
|
+
editorRef.current.querySelectorAll(".rte-media-selected").forEach(element => {
|
|
838
|
+
element.classList.remove("rte-media-selected");
|
|
839
|
+
});
|
|
840
|
+
container.classList.add("rte-media-selected");
|
|
841
|
+
setSelectedMedia(container);
|
|
778
842
|
};
|
|
779
843
|
const attachMediaResizeHandle = container => {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
const resizeTarget =
|
|
844
|
+
var _resizeTarget$querySe;
|
|
845
|
+
if (!container) return;
|
|
846
|
+
const resizeTarget = container.classList.contains("video-container") ? container : getImageMediaTarget(container);
|
|
783
847
|
if (!resizeTarget) return;
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
handle.addEventListener("mousedown", event => {
|
|
810
|
-
if (!editable) return;
|
|
811
|
-
event.preventDefault();
|
|
812
|
-
event.stopPropagation();
|
|
813
|
-
const limits = getMediaSizeLimits();
|
|
814
|
-
const rect = resizeTarget.getBoundingClientRect();
|
|
815
|
-
const startX = event.clientX;
|
|
816
|
-
const startY = event.clientY;
|
|
817
|
-
const startWidth = rect.width;
|
|
818
|
-
const startHeight = rect.height;
|
|
819
|
-
const startMarginLeft = Number.parseFloat(resizeTarget.style.marginLeft) || 0;
|
|
820
|
-
const startMarginTop = Number.parseFloat(resizeTarget.style.marginTop) || 0;
|
|
821
|
-
if (isVideo) {
|
|
822
|
-
resizeTarget.style.paddingBottom = "0";
|
|
823
|
-
}
|
|
824
|
-
const onMouseMove = moveEvent => {
|
|
825
|
-
const deltaX = moveEvent.clientX - startX;
|
|
826
|
-
const deltaY = moveEvent.clientY - startY;
|
|
827
|
-
let nextWidth = startWidth;
|
|
828
|
-
let nextHeight = startHeight;
|
|
829
|
-
if (edge === "right") {
|
|
830
|
-
nextWidth = startWidth + deltaX;
|
|
831
|
-
} else if (edge === "left") {
|
|
832
|
-
nextWidth = startWidth - deltaX;
|
|
833
|
-
} else if (edge === "bottom") {
|
|
834
|
-
nextHeight = startHeight + deltaY;
|
|
835
|
-
} else if (edge === "top") {
|
|
836
|
-
nextHeight = startHeight - deltaY;
|
|
837
|
-
}
|
|
838
|
-
nextWidth = Math.max(limits.minWidth, Math.min(nextWidth, limits.maxWidth));
|
|
839
|
-
nextHeight = Math.max(limits.minHeight, Math.min(nextHeight, limits.maxHeight));
|
|
840
|
-
if (edge === "left") {
|
|
841
|
-
resizeTarget.style.marginLeft = `${Math.round(startMarginLeft + (startWidth - nextWidth))}px`;
|
|
842
|
-
}
|
|
843
|
-
if (edge === "top") {
|
|
844
|
-
resizeTarget.style.marginTop = `${Math.round(startMarginTop + (startHeight - nextHeight))}px`;
|
|
845
|
-
}
|
|
846
|
-
if (isVideo) {
|
|
847
|
-
applyVideoMediaSize(resizeTarget, nextWidth, nextHeight);
|
|
848
|
-
} else {
|
|
849
|
-
applyImageMediaSize(resizeTarget, nextWidth, nextHeight, edge);
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
const onMouseUp = () => {
|
|
853
|
-
document.removeEventListener("mousemove", onMouseMove);
|
|
854
|
-
document.removeEventListener("mouseup", onMouseUp);
|
|
855
|
-
triggerChange();
|
|
856
|
-
};
|
|
857
|
-
document.addEventListener("mousemove", onMouseMove);
|
|
858
|
-
document.addEventListener("mouseup", onMouseUp);
|
|
859
|
-
});
|
|
860
|
-
handlesWrapper.appendChild(handle);
|
|
848
|
+
(_resizeTarget$querySe = resizeTarget.querySelector(".media-resize-handle")) === null || _resizeTarget$querySe === void 0 || _resizeTarget$querySe.remove();
|
|
849
|
+
const handle = document.createElement("div");
|
|
850
|
+
handle.className = "media-resize-handle";
|
|
851
|
+
handle.title = "Drag to resize";
|
|
852
|
+
handle.setAttribute("contenteditable", "false");
|
|
853
|
+
handle.addEventListener("mousedown", event => {
|
|
854
|
+
if (!editable) return;
|
|
855
|
+
event.preventDefault();
|
|
856
|
+
event.stopPropagation();
|
|
857
|
+
selectMediaContainer(container);
|
|
858
|
+
const editorWidth = getEditorInnerWidth();
|
|
859
|
+
const startX = event.clientX;
|
|
860
|
+
const startWidth = container.getBoundingClientRect().width;
|
|
861
|
+
const onMouseMove = moveEvent => {
|
|
862
|
+
const nextWidth = Math.max(60, startWidth + (moveEvent.clientX - startX));
|
|
863
|
+
const percent = Math.round(nextWidth / editorWidth * 100);
|
|
864
|
+
applyMediaWidthPercent(container, percent);
|
|
865
|
+
};
|
|
866
|
+
const onMouseUp = () => {
|
|
867
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
868
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
869
|
+
triggerChange();
|
|
870
|
+
};
|
|
871
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
872
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
861
873
|
});
|
|
862
|
-
resizeTarget.appendChild(
|
|
874
|
+
resizeTarget.appendChild(handle);
|
|
863
875
|
};
|
|
864
876
|
const handleEditorFocus = () => {
|
|
865
877
|
setEditorFocused(true);
|
|
866
878
|
};
|
|
867
879
|
const handleEditorBlur = () => {
|
|
868
880
|
requestAnimationFrame(() => {
|
|
869
|
-
var _editorRef$
|
|
870
|
-
if (!((_editorRef$
|
|
881
|
+
var _editorRef$current3;
|
|
882
|
+
if (!((_editorRef$current3 = editorRef.current) !== null && _editorRef$current3 !== void 0 && _editorRef$current3.contains(document.activeElement))) {
|
|
871
883
|
setEditorFocused(false);
|
|
872
884
|
}
|
|
873
885
|
});
|
|
874
886
|
};
|
|
875
887
|
const updateMediaControlVisibility = container => {
|
|
876
|
-
const
|
|
877
|
-
if (
|
|
878
|
-
|
|
879
|
-
|
|
888
|
+
const handle = container.querySelector(".media-resize-handle");
|
|
889
|
+
if (handle instanceof HTMLElement) {
|
|
890
|
+
handle.style.display = editable ? "block" : "none";
|
|
891
|
+
handle.style.pointerEvents = editable ? "auto" : "none";
|
|
880
892
|
}
|
|
881
893
|
};
|
|
894
|
+
const BLOCK_TAGS = ["P", "DIV", "H1", "H2", "H3", "BLOCKQUOTE", "LI"];
|
|
895
|
+
const getActiveBlock = node => {
|
|
896
|
+
if (!editorRef.current || !node) return null;
|
|
897
|
+
let current = node.nodeType === 3 ? node.parentNode : node;
|
|
898
|
+
while (current && current !== editorRef.current) {
|
|
899
|
+
if (current.nodeType === 1 && BLOCK_TAGS.includes(current.tagName)) {
|
|
900
|
+
return current;
|
|
901
|
+
}
|
|
902
|
+
current = current.parentNode;
|
|
903
|
+
}
|
|
904
|
+
return editorRef.current;
|
|
905
|
+
};
|
|
906
|
+
const getBlockFormat = node => {
|
|
907
|
+
const block = getActiveBlock(node);
|
|
908
|
+
if (!block || block === editorRef.current) return "div";
|
|
909
|
+
const tag = block.tagName.toLowerCase();
|
|
910
|
+
return tag === "p" || tag === "li" ? "div" : tag;
|
|
911
|
+
};
|
|
882
912
|
const createMediaDeleteButton = (title, className, onRemove) => {
|
|
883
913
|
const deleteBtn = document.createElement("button");
|
|
884
914
|
deleteBtn.type = "button";
|
|
@@ -897,10 +927,10 @@ function RichTextEditor({
|
|
|
897
927
|
// Listen for selection changes globally to update styles and list type in one pass
|
|
898
928
|
React.useEffect(() => {
|
|
899
929
|
const handleGlobalSelectionSync = () => {
|
|
900
|
-
var _editorRef$
|
|
930
|
+
var _editorRef$current4;
|
|
901
931
|
// Only sync if the editor has focus
|
|
902
932
|
const sel = window.getSelection();
|
|
903
|
-
if (!sel || !sel.rangeCount || !((_editorRef$
|
|
933
|
+
if (!sel || !sel.rangeCount || !((_editorRef$current4 = editorRef.current) !== null && _editorRef$current4 !== void 0 && _editorRef$current4.contains(sel.anchorNode))) {
|
|
904
934
|
return;
|
|
905
935
|
}
|
|
906
936
|
|
|
@@ -948,6 +978,7 @@ function RichTextEditor({
|
|
|
948
978
|
} else {
|
|
949
979
|
setCurrentFontSize("16");
|
|
950
980
|
}
|
|
981
|
+
setCurrentBlockFormat(getBlockFormat(sel.anchorNode));
|
|
951
982
|
};
|
|
952
983
|
document.addEventListener("selectionchange", handleGlobalSelectionSync);
|
|
953
984
|
return () => {
|
|
@@ -1185,11 +1216,25 @@ function RichTextEditor({
|
|
|
1185
1216
|
if (!videoContainer.querySelector(".video-delete-button")) {
|
|
1186
1217
|
const deleteBtn = createMediaDeleteButton("Remove video", "video-delete-button image-delete-button", () => {
|
|
1187
1218
|
videoContainer.remove();
|
|
1219
|
+
clearMediaSelection();
|
|
1188
1220
|
triggerChange && triggerChange();
|
|
1189
1221
|
});
|
|
1190
1222
|
videoContainer.appendChild(deleteBtn);
|
|
1191
1223
|
}
|
|
1224
|
+
if (!videoContainer.dataset.mediaEnhanced) {
|
|
1225
|
+
videoContainer.dataset.mediaEnhanced = "true";
|
|
1226
|
+
if (!videoContainer.classList.contains("image-align-left") && !videoContainer.classList.contains("image-align-center") && !videoContainer.classList.contains("image-align-right")) {
|
|
1227
|
+
videoContainer.classList.add("image-align-left");
|
|
1228
|
+
}
|
|
1229
|
+
videoContainer.addEventListener("click", event => {
|
|
1230
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1231
|
+
event.preventDefault();
|
|
1232
|
+
event.stopPropagation();
|
|
1233
|
+
selectMediaContainer(videoContainer);
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1192
1236
|
attachMediaResizeHandle(videoContainer);
|
|
1237
|
+
normalizeMediaWidth(videoContainer);
|
|
1193
1238
|
updateMediaControlVisibility(videoContainer);
|
|
1194
1239
|
});
|
|
1195
1240
|
};
|
|
@@ -1209,6 +1254,7 @@ function RichTextEditor({
|
|
|
1209
1254
|
}));
|
|
1210
1255
|
}
|
|
1211
1256
|
attachMediaResizeHandle(existingWrapper);
|
|
1257
|
+
normalizeMediaWidth(existingWrapper);
|
|
1212
1258
|
updateMediaControlVisibility(existingWrapper);
|
|
1213
1259
|
return;
|
|
1214
1260
|
}
|
|
@@ -1218,24 +1264,21 @@ function RichTextEditor({
|
|
|
1218
1264
|
wrapper.style.cursor = editable ? "pointer" : "default";
|
|
1219
1265
|
const frame = document.createElement("div");
|
|
1220
1266
|
frame.className = "image-media-frame";
|
|
1221
|
-
if (img.getAttribute("width") && !frame.style.width) {
|
|
1222
|
-
frame.style.width = `${img.getAttribute("width")}px`;
|
|
1223
|
-
} else if (img.style.width && img.style.width.endsWith("px")) {
|
|
1224
|
-
frame.style.width = img.style.width;
|
|
1225
|
-
}
|
|
1226
1267
|
img.classList.add("rte-image");
|
|
1227
1268
|
img.setAttribute("data-align", align);
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
img.style.height = frame.dataset.explicitHeight ? "100%" : "auto";
|
|
1231
|
-
} else {
|
|
1232
|
-
img.style.width = "";
|
|
1233
|
-
img.style.height = "auto";
|
|
1234
|
-
}
|
|
1235
|
-
img.addEventListener("click", event => {
|
|
1269
|
+
img.style.height = "auto";
|
|
1270
|
+
img.addEventListener("dblclick", event => {
|
|
1236
1271
|
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1272
|
+
event.preventDefault();
|
|
1273
|
+
event.stopPropagation();
|
|
1237
1274
|
openImageModal(img.src);
|
|
1238
1275
|
});
|
|
1276
|
+
img.addEventListener("click", event => {
|
|
1277
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1278
|
+
event.preventDefault();
|
|
1279
|
+
event.stopPropagation();
|
|
1280
|
+
selectMediaContainer(wrapper);
|
|
1281
|
+
});
|
|
1239
1282
|
const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1240
1283
|
wrapper.remove();
|
|
1241
1284
|
triggerChange && triggerChange();
|
|
@@ -1250,6 +1293,7 @@ function RichTextEditor({
|
|
|
1250
1293
|
frame.appendChild(deleteBtn);
|
|
1251
1294
|
wrapper.appendChild(frame);
|
|
1252
1295
|
attachMediaResizeHandle(wrapper);
|
|
1296
|
+
normalizeMediaWidth(wrapper);
|
|
1253
1297
|
if (nextSibling) {
|
|
1254
1298
|
parentNode.insertBefore(wrapper, nextSibling);
|
|
1255
1299
|
} else {
|
|
@@ -1276,9 +1320,19 @@ function RichTextEditor({
|
|
|
1276
1320
|
const img = document.createElement('img');
|
|
1277
1321
|
img.src = dataUrl;
|
|
1278
1322
|
img.alt = fileName || "image";
|
|
1279
|
-
img.addEventListener("
|
|
1323
|
+
img.addEventListener("dblclick", event => {
|
|
1324
|
+
event.preventDefault();
|
|
1325
|
+
event.stopPropagation();
|
|
1326
|
+
openImageModal(dataUrl);
|
|
1327
|
+
});
|
|
1328
|
+
img.addEventListener("click", event => {
|
|
1329
|
+
event.preventDefault();
|
|
1330
|
+
event.stopPropagation();
|
|
1331
|
+
selectMediaContainer(container);
|
|
1332
|
+
});
|
|
1280
1333
|
frame.appendChild(img);
|
|
1281
1334
|
container.appendChild(frame);
|
|
1335
|
+
applyMediaWidthPercent(container, 25);
|
|
1282
1336
|
|
|
1283
1337
|
// Insert at cursor position
|
|
1284
1338
|
insertNodeAtCursor(container);
|
|
@@ -1451,6 +1505,8 @@ function RichTextEditor({
|
|
|
1451
1505
|
selection.addRange(newRange);
|
|
1452
1506
|
};
|
|
1453
1507
|
const handleKeyDown = React.useCallback(e => {
|
|
1508
|
+
if (applyMarkdownShortcut(e)) return;
|
|
1509
|
+
|
|
1454
1510
|
// Handle Enter key
|
|
1455
1511
|
if (e.key === 'Enter') {
|
|
1456
1512
|
e.preventDefault();
|
|
@@ -1522,7 +1578,7 @@ function RichTextEditor({
|
|
|
1522
1578
|
return;
|
|
1523
1579
|
}
|
|
1524
1580
|
if (e.key === "Backspace") {
|
|
1525
|
-
var _node$closest, _node, _editorRef$
|
|
1581
|
+
var _node$closest, _node, _editorRef$current5;
|
|
1526
1582
|
const selection = window.getSelection();
|
|
1527
1583
|
if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
|
|
1528
1584
|
const range = selection.getRangeAt(0);
|
|
@@ -1532,7 +1588,7 @@ function RichTextEditor({
|
|
|
1532
1588
|
node = node.parentNode;
|
|
1533
1589
|
}
|
|
1534
1590
|
const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
|
|
1535
|
-
if (!listItem || !((_editorRef$
|
|
1591
|
+
if (!listItem || !((_editorRef$current5 = editorRef.current) !== null && _editorRef$current5 !== void 0 && _editorRef$current5.contains(listItem))) return;
|
|
1536
1592
|
const list = listItem.parentNode;
|
|
1537
1593
|
if (isListItemEffectivelyEmpty(listItem)) {
|
|
1538
1594
|
e.preventDefault();
|
|
@@ -1609,6 +1665,65 @@ function RichTextEditor({
|
|
|
1609
1665
|
const handleSelect = type => {
|
|
1610
1666
|
exec(type === "unordered" ? "insertUnorderedList" : "insertOrderedList");
|
|
1611
1667
|
};
|
|
1668
|
+
const applyBlockFormat = format => {
|
|
1669
|
+
document.execCommand("formatBlock", false, format);
|
|
1670
|
+
setCurrentBlockFormat(format);
|
|
1671
|
+
triggerChange();
|
|
1672
|
+
focus();
|
|
1673
|
+
};
|
|
1674
|
+
const clearFormatting = () => {
|
|
1675
|
+
document.execCommand("removeFormat", false, null);
|
|
1676
|
+
document.execCommand("unlink", false, null);
|
|
1677
|
+
document.execCommand("formatBlock", false, "div");
|
|
1678
|
+
setCurrentBlockFormat("div");
|
|
1679
|
+
setCurrentFontSize("16");
|
|
1680
|
+
setCurrentLineHeight("");
|
|
1681
|
+
setFontColor("#000000");
|
|
1682
|
+
triggerChange();
|
|
1683
|
+
focus();
|
|
1684
|
+
};
|
|
1685
|
+
const deleteTextBeforeCursorInBlock = (block, range, selection) => {
|
|
1686
|
+
const prefixRange = document.createRange();
|
|
1687
|
+
prefixRange.setStart(block, 0);
|
|
1688
|
+
prefixRange.setEnd(range.startContainer, range.startOffset);
|
|
1689
|
+
prefixRange.deleteContents();
|
|
1690
|
+
const nextRange = document.createRange();
|
|
1691
|
+
nextRange.setStart(block, 0);
|
|
1692
|
+
nextRange.collapse(true);
|
|
1693
|
+
selection.removeAllRanges();
|
|
1694
|
+
selection.addRange(nextRange);
|
|
1695
|
+
};
|
|
1696
|
+
const applyMarkdownShortcut = event => {
|
|
1697
|
+
if (event.key !== " " || event.ctrlKey || event.metaKey || event.altKey) {
|
|
1698
|
+
return false;
|
|
1699
|
+
}
|
|
1700
|
+
const selection = window.getSelection();
|
|
1701
|
+
if (!(selection !== null && selection !== void 0 && selection.rangeCount) || !selection.isCollapsed || !editorRef.current) {
|
|
1702
|
+
return false;
|
|
1703
|
+
}
|
|
1704
|
+
const range = selection.getRangeAt(0);
|
|
1705
|
+
const block = getActiveBlock(range.startContainer);
|
|
1706
|
+
if (!block || !editorRef.current.contains(block)) return false;
|
|
1707
|
+
const prefixRange = document.createRange();
|
|
1708
|
+
prefixRange.setStart(block, 0);
|
|
1709
|
+
prefixRange.setEnd(range.startContainer, range.startOffset);
|
|
1710
|
+
const textBeforeCursor = prefixRange.toString().replace(/\u00A0/g, " ").trim();
|
|
1711
|
+
const shortcuts = {
|
|
1712
|
+
"#": () => applyBlockFormat("h1"),
|
|
1713
|
+
"##": () => applyBlockFormat("h2"),
|
|
1714
|
+
"###": () => applyBlockFormat("h3"),
|
|
1715
|
+
">": () => applyBlockFormat("blockquote"),
|
|
1716
|
+
"-": () => handleSelect("unordered"),
|
|
1717
|
+
"*": () => handleSelect("unordered"),
|
|
1718
|
+
"1.": () => handleSelect("ordered")
|
|
1719
|
+
};
|
|
1720
|
+
const action = shortcuts[textBeforeCursor];
|
|
1721
|
+
if (!action) return false;
|
|
1722
|
+
event.preventDefault();
|
|
1723
|
+
deleteTextBeforeCursorInBlock(block, range, selection);
|
|
1724
|
+
action();
|
|
1725
|
+
return true;
|
|
1726
|
+
};
|
|
1612
1727
|
const onLineHeightChange = value => {
|
|
1613
1728
|
if (!value) return;
|
|
1614
1729
|
const sel = window.getSelection();
|
|
@@ -1790,6 +1905,7 @@ function RichTextEditor({
|
|
|
1790
1905
|
}
|
|
1791
1906
|
}, [disabled]);
|
|
1792
1907
|
const handleEditorClick = React.useCallback(e => {
|
|
1908
|
+
var _editorRef$current6;
|
|
1793
1909
|
setSelectionVersion(v => v + 1);
|
|
1794
1910
|
const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
|
|
1795
1911
|
if (deleteBtn && editable && editorFocused) {
|
|
@@ -1798,10 +1914,15 @@ function RichTextEditor({
|
|
|
1798
1914
|
const wrapper = deleteBtn.closest('.image-container, .video-container');
|
|
1799
1915
|
if (wrapper) {
|
|
1800
1916
|
wrapper.remove();
|
|
1917
|
+
clearMediaSelection();
|
|
1801
1918
|
triggerChange();
|
|
1802
1919
|
}
|
|
1803
1920
|
return;
|
|
1804
1921
|
}
|
|
1922
|
+
const clickedMedia = e.target.closest('.image-container, .video-container');
|
|
1923
|
+
if (clickedMedia && (_editorRef$current6 = editorRef.current) !== null && _editorRef$current6 !== void 0 && _editorRef$current6.contains(clickedMedia) && editable) {
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1805
1926
|
|
|
1806
1927
|
// Check if the click is on a link
|
|
1807
1928
|
const clickedLink = e.target.closest('a');
|
|
@@ -1811,13 +1932,8 @@ function RichTextEditor({
|
|
|
1811
1932
|
window.open(clickedLink.href, '_blank');
|
|
1812
1933
|
return;
|
|
1813
1934
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
const clickedImg = e.target.closest('img');
|
|
1817
|
-
if (clickedImg && !clickedImg.closest('.rte-modal')) {
|
|
1818
|
-
setSelectedImage(clickedImg);
|
|
1819
|
-
} else if (!e.target.closest('.rte-image-toolbar')) {
|
|
1820
|
-
setSelectedImage(null);
|
|
1935
|
+
if (!e.target.closest('.rte-media-toolbar')) {
|
|
1936
|
+
clearMediaSelection();
|
|
1821
1937
|
}
|
|
1822
1938
|
|
|
1823
1939
|
// If disabled is true, prevent editing
|
|
@@ -1836,73 +1952,72 @@ function RichTextEditor({
|
|
|
1836
1952
|
}, 0);
|
|
1837
1953
|
}
|
|
1838
1954
|
}, [editable, disabled, editorFocused, triggerChange]);
|
|
1839
|
-
const
|
|
1840
|
-
if (!
|
|
1955
|
+
const renderMediaToolbar = () => {
|
|
1956
|
+
if (!selectedMedia || !editorRef.current || !editable) return null;
|
|
1841
1957
|
const editorRect = editorRef.current.getBoundingClientRect();
|
|
1842
|
-
const
|
|
1843
|
-
const top =
|
|
1844
|
-
const left =
|
|
1845
|
-
const width =
|
|
1958
|
+
const mediaRect = selectedMedia.getBoundingClientRect();
|
|
1959
|
+
const top = mediaRect.top - editorRect.top + editorRef.current.scrollTop;
|
|
1960
|
+
const left = mediaRect.left - editorRect.left + editorRef.current.scrollLeft;
|
|
1961
|
+
const width = mediaRect.width;
|
|
1962
|
+
const currentPercent = getMediaWidthPercent(selectedMedia);
|
|
1963
|
+
const widthPresets = [25, 50, 75, 100];
|
|
1846
1964
|
const handleAlignment = align => {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
wrapper.classList.add(`image-align-${align}`);
|
|
1853
|
-
selectedImage.setAttribute('data-align', align);
|
|
1854
|
-
triggerChange();
|
|
1855
|
-
}
|
|
1965
|
+
selectedMedia.classList.remove("image-align-left", "image-align-center", "image-align-right");
|
|
1966
|
+
selectedMedia.classList.add(`image-align-${align}`);
|
|
1967
|
+
const img = selectedMedia.querySelector("img");
|
|
1968
|
+
if (img) img.setAttribute("data-align", align);
|
|
1969
|
+
triggerChange();
|
|
1856
1970
|
};
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
wrapper.remove();
|
|
1861
|
-
setSelectedImage(null);
|
|
1862
|
-
triggerChange();
|
|
1863
|
-
}
|
|
1971
|
+
const setWidth = percent => {
|
|
1972
|
+
applyMediaWidthPercent(selectedMedia, percent);
|
|
1973
|
+
triggerChange();
|
|
1864
1974
|
};
|
|
1865
|
-
const
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
}
|
|
1874
|
-
triggerChange();
|
|
1975
|
+
const removeMedia = () => {
|
|
1976
|
+
selectedMedia.remove();
|
|
1977
|
+
clearMediaSelection();
|
|
1978
|
+
triggerChange();
|
|
1979
|
+
};
|
|
1980
|
+
const isActivePercent = percent => {
|
|
1981
|
+
if (!selectedMedia.dataset.widthPercent && !(selectedMedia.style.width || "").endsWith("%")) {
|
|
1982
|
+
return false;
|
|
1875
1983
|
}
|
|
1984
|
+
return Math.abs(currentPercent - percent) <= 3;
|
|
1876
1985
|
};
|
|
1877
1986
|
return /*#__PURE__*/React.createElement("div", {
|
|
1878
|
-
className: "rte-
|
|
1987
|
+
className: "rte-media-toolbar",
|
|
1879
1988
|
style: {
|
|
1880
|
-
position:
|
|
1881
|
-
top: Math.max(0, top -
|
|
1882
|
-
left: Math.max(
|
|
1989
|
+
position: "absolute",
|
|
1990
|
+
top: Math.max(0, top - 44),
|
|
1991
|
+
left: Math.max(8, left + width / 2 - 156),
|
|
1883
1992
|
zIndex: 1000
|
|
1884
1993
|
}
|
|
1885
1994
|
}, /*#__PURE__*/React.createElement("button", {
|
|
1886
1995
|
type: "button",
|
|
1887
|
-
onClick: () => handleAlignment(
|
|
1996
|
+
onClick: () => handleAlignment("left"),
|
|
1888
1997
|
title: "Align Left"
|
|
1889
1998
|
}, "L"), /*#__PURE__*/React.createElement("button", {
|
|
1890
1999
|
type: "button",
|
|
1891
|
-
onClick: () => handleAlignment(
|
|
2000
|
+
onClick: () => handleAlignment("center"),
|
|
1892
2001
|
title: "Align Center"
|
|
1893
2002
|
}, "C"), /*#__PURE__*/React.createElement("button", {
|
|
1894
2003
|
type: "button",
|
|
1895
|
-
onClick: () => handleAlignment(
|
|
2004
|
+
onClick: () => handleAlignment("right"),
|
|
1896
2005
|
title: "Align Right"
|
|
1897
|
-
}, "R"), /*#__PURE__*/React.createElement("
|
|
2006
|
+
}, "R"), /*#__PURE__*/React.createElement("span", {
|
|
2007
|
+
className: "rte-media-toolbar-divider"
|
|
2008
|
+
}), widthPresets.map(percent => /*#__PURE__*/React.createElement("button", {
|
|
2009
|
+
key: percent,
|
|
1898
2010
|
type: "button",
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2011
|
+
className: isActivePercent(percent) ? "active" : "",
|
|
2012
|
+
onClick: () => setWidth(percent),
|
|
2013
|
+
title: `${percent}% width`
|
|
2014
|
+
}, percent, "%")), /*#__PURE__*/React.createElement("span", {
|
|
2015
|
+
className: "rte-media-toolbar-divider"
|
|
2016
|
+
}), /*#__PURE__*/React.createElement("button", {
|
|
1902
2017
|
type: "button",
|
|
1903
|
-
onClick:
|
|
2018
|
+
onClick: removeMedia,
|
|
1904
2019
|
className: "danger",
|
|
1905
|
-
title: "Remove
|
|
2020
|
+
title: "Remove"
|
|
1906
2021
|
}, "\xD7"));
|
|
1907
2022
|
};
|
|
1908
2023
|
if (isLoading) {
|
|
@@ -1938,17 +2053,14 @@ function RichTextEditor({
|
|
|
1938
2053
|
e.preventDefault();
|
|
1939
2054
|
e.stopPropagation();
|
|
1940
2055
|
}
|
|
1941
|
-
}, /*#__PURE__*/React.createElement("div", {
|
|
2056
|
+
}, !disabled && /*#__PURE__*/React.createElement("div", {
|
|
1942
2057
|
className: "rte-toolbar"
|
|
1943
2058
|
}, /*#__PURE__*/React.createElement("button", {
|
|
1944
2059
|
type: "button",
|
|
1945
2060
|
title: "Bold",
|
|
1946
|
-
|
|
2061
|
+
onMouseDown: e => {
|
|
1947
2062
|
e.preventDefault();
|
|
1948
|
-
|
|
1949
|
-
document.execCommand("bold");
|
|
1950
|
-
handleInput();
|
|
1951
|
-
focus();
|
|
2063
|
+
exec("bold");
|
|
1952
2064
|
},
|
|
1953
2065
|
className: `rte-toolbar-button ${isBold ? "active" : ""}`
|
|
1954
2066
|
}, /*#__PURE__*/React.createElement(FaBold, {
|
|
@@ -1956,12 +2068,9 @@ function RichTextEditor({
|
|
|
1956
2068
|
})), /*#__PURE__*/React.createElement("button", {
|
|
1957
2069
|
type: "button",
|
|
1958
2070
|
title: "Italic",
|
|
1959
|
-
|
|
2071
|
+
onMouseDown: e => {
|
|
1960
2072
|
e.preventDefault();
|
|
1961
|
-
|
|
1962
|
-
document.execCommand("italic");
|
|
1963
|
-
handleInput();
|
|
1964
|
-
focus();
|
|
2073
|
+
exec("italic");
|
|
1965
2074
|
},
|
|
1966
2075
|
className: `rte-toolbar-button ${isItalic ? "active" : ""}`
|
|
1967
2076
|
}, /*#__PURE__*/React.createElement(FaItalic, {
|
|
@@ -1969,12 +2078,9 @@ function RichTextEditor({
|
|
|
1969
2078
|
})), /*#__PURE__*/React.createElement("button", {
|
|
1970
2079
|
type: "button",
|
|
1971
2080
|
title: "Underline",
|
|
1972
|
-
|
|
2081
|
+
onMouseDown: e => {
|
|
1973
2082
|
e.preventDefault();
|
|
1974
|
-
|
|
1975
|
-
document.execCommand("underline");
|
|
1976
|
-
handleInput();
|
|
1977
|
-
focus();
|
|
2083
|
+
exec("underline");
|
|
1978
2084
|
},
|
|
1979
2085
|
className: `rte-toolbar-button ${isUnderline ? "active" : ""}`
|
|
1980
2086
|
}, /*#__PURE__*/React.createElement(FaUnderline, {
|
|
@@ -1986,6 +2092,41 @@ function RichTextEditor({
|
|
|
1986
2092
|
backgroundColor: '#e5e7eb',
|
|
1987
2093
|
margin: '0 4px'
|
|
1988
2094
|
}
|
|
2095
|
+
}), /*#__PURE__*/React.createElement("select", {
|
|
2096
|
+
value: currentBlockFormat,
|
|
2097
|
+
onMouseDown: e => e.stopPropagation(),
|
|
2098
|
+
onChange: e => {
|
|
2099
|
+
e.preventDefault();
|
|
2100
|
+
e.stopPropagation();
|
|
2101
|
+
applyBlockFormat(e.target.value);
|
|
2102
|
+
},
|
|
2103
|
+
className: "rte-toolbar-select rte-heading-select",
|
|
2104
|
+
title: "Text style"
|
|
2105
|
+
}, /*#__PURE__*/React.createElement("option", {
|
|
2106
|
+
value: "div"
|
|
2107
|
+
}, "Paragraph"), /*#__PURE__*/React.createElement("option", {
|
|
2108
|
+
value: "h1"
|
|
2109
|
+
}, "Heading 1"), /*#__PURE__*/React.createElement("option", {
|
|
2110
|
+
value: "h2"
|
|
2111
|
+
}, "Heading 2"), /*#__PURE__*/React.createElement("option", {
|
|
2112
|
+
value: "h3"
|
|
2113
|
+
}, "Heading 3"), /*#__PURE__*/React.createElement("option", {
|
|
2114
|
+
value: "blockquote"
|
|
2115
|
+
}, "Quote")), /*#__PURE__*/React.createElement("button", {
|
|
2116
|
+
type: "button",
|
|
2117
|
+
title: "Clear Formatting",
|
|
2118
|
+
className: "rte-toolbar-button rte-toolbar-button-text",
|
|
2119
|
+
onMouseDown: e => {
|
|
2120
|
+
e.preventDefault();
|
|
2121
|
+
clearFormatting();
|
|
2122
|
+
}
|
|
2123
|
+
}, "Tx"), /*#__PURE__*/React.createElement("div", {
|
|
2124
|
+
style: {
|
|
2125
|
+
width: '1px',
|
|
2126
|
+
height: '20px',
|
|
2127
|
+
backgroundColor: '#e5e7eb',
|
|
2128
|
+
margin: '0 4px'
|
|
2129
|
+
}
|
|
1989
2130
|
}), /*#__PURE__*/React.createElement("select", {
|
|
1990
2131
|
value: currentFontSize,
|
|
1991
2132
|
onMouseDown: e => e.stopPropagation(),
|
|
@@ -2152,11 +2293,9 @@ function RichTextEditor({
|
|
|
2152
2293
|
e.preventDefault();
|
|
2153
2294
|
addLink();
|
|
2154
2295
|
}
|
|
2155
|
-
}, /*#__PURE__*/React.createElement(
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
}
|
|
2159
|
-
}, "\uD83D\uDD17")), /*#__PURE__*/React.createElement("input", {
|
|
2296
|
+
}, /*#__PURE__*/React.createElement(FaLink, {
|
|
2297
|
+
size: 14
|
|
2298
|
+
})), /*#__PURE__*/React.createElement("input", {
|
|
2160
2299
|
ref: fileInputRef,
|
|
2161
2300
|
type: "file",
|
|
2162
2301
|
accept: "image/*",
|
|
@@ -2323,9 +2462,17 @@ function RichTextEditor({
|
|
|
2323
2462
|
}
|
|
2324
2463
|
return null;
|
|
2325
2464
|
})()), /*#__PURE__*/React.createElement("div", {
|
|
2465
|
+
className: "rte-content-wrapper",
|
|
2466
|
+
style: {
|
|
2467
|
+
position: 'relative'
|
|
2468
|
+
}
|
|
2469
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
2326
2470
|
ref: editorRef,
|
|
2327
2471
|
contentEditable: editable && disabled !== true,
|
|
2328
2472
|
suppressContentEditableWarning: true,
|
|
2473
|
+
role: "textbox",
|
|
2474
|
+
"aria-multiline": "true",
|
|
2475
|
+
"aria-label": label || "Rich text editor",
|
|
2329
2476
|
onInput: handleInput,
|
|
2330
2477
|
onPaste: handlePaste,
|
|
2331
2478
|
onDrop: handleDrop,
|
|
@@ -2341,7 +2488,13 @@ function RichTextEditor({
|
|
|
2341
2488
|
paddingLeft: paddingLeft || '12px'
|
|
2342
2489
|
},
|
|
2343
2490
|
className: `rte-content${editable ? " rte-is-editable" : ""}${editorFocused ? " rte-is-focused" : ""}`
|
|
2344
|
-
}),
|
|
2491
|
+
}), isEmpty && editable && disabled !== true && /*#__PURE__*/React.createElement("div", {
|
|
2492
|
+
className: "rte-placeholder",
|
|
2493
|
+
style: {
|
|
2494
|
+
left: paddingLeft || '12px'
|
|
2495
|
+
},
|
|
2496
|
+
"aria-hidden": "true"
|
|
2497
|
+
}, placeholder)), renderMediaToolbar(), /*#__PURE__*/React.createElement("div", {
|
|
2345
2498
|
className: "rte-footer"
|
|
2346
2499
|
}, /*#__PURE__*/React.createElement("div", {
|
|
2347
2500
|
className: "rte-footer-content"
|
|
@@ -2357,13 +2510,11 @@ function RichTextEditor({
|
|
|
2357
2510
|
}, /*#__PURE__*/React.createElement("div", {
|
|
2358
2511
|
className: "rte-modal",
|
|
2359
2512
|
onClick: e => e.stopPropagation()
|
|
2513
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
2514
|
+
className: "rte-modal-header"
|
|
2360
2515
|
}, /*#__PURE__*/React.createElement("h3", {
|
|
2361
2516
|
className: "rte-modal-title"
|
|
2362
|
-
}, "Insert Link"), /*#__PURE__*/React.createElement("div", {
|
|
2363
|
-
className: "rte-modal-divider"
|
|
2364
|
-
}), /*#__PURE__*/React.createElement("div", {
|
|
2365
|
-
className: "rte-modal-body"
|
|
2366
|
-
}, /*#__PURE__*/React.createElement("div", {
|
|
2517
|
+
}, "Insert Link")), /*#__PURE__*/React.createElement("div", {
|
|
2367
2518
|
className: "rte-form-group"
|
|
2368
2519
|
}, /*#__PURE__*/React.createElement("label", {
|
|
2369
2520
|
className: "rte-label"
|
|
@@ -2385,20 +2536,15 @@ function RichTextEditor({
|
|
|
2385
2536
|
onChange: e => setLinkUrl(e.target.value),
|
|
2386
2537
|
onKeyDown: e => e.key === 'Enter' && confirmLink(),
|
|
2387
2538
|
autoFocus: true
|
|
2388
|
-
}))
|
|
2389
|
-
className: "rte-modal-
|
|
2390
|
-
style: {
|
|
2391
|
-
margin: '8px 0 20px 0'
|
|
2392
|
-
}
|
|
2393
|
-
}), /*#__PURE__*/React.createElement("div", {
|
|
2394
|
-
className: "rte-modal-footer"
|
|
2539
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
2540
|
+
className: "rte-modal-actions"
|
|
2395
2541
|
}, /*#__PURE__*/React.createElement("button", {
|
|
2396
2542
|
type: "button",
|
|
2397
|
-
className: "rte-
|
|
2543
|
+
className: "rte-button rte-button-secondary",
|
|
2398
2544
|
onClick: cancelLink
|
|
2399
2545
|
}, "Cancel"), /*#__PURE__*/React.createElement("button", {
|
|
2400
2546
|
type: "button",
|
|
2401
|
-
className: "rte-
|
|
2547
|
+
className: "rte-button rte-button-primary",
|
|
2402
2548
|
onClick: confirmLink,
|
|
2403
2549
|
disabled: !linkUrl
|
|
2404
2550
|
}, "Insert")))), tableModalOpen && /*#__PURE__*/React.createElement("div", {
|