react-lite-rich-text-editor 1.1.8 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +542 -29
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +542 -29
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -409,7 +409,7 @@ function styleInject(css, ref) {
|
|
|
409
409
|
}
|
|
410
410
|
}
|
|
411
411
|
|
|
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}";
|
|
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-bg-color-picker-label .rte-bg-color-swatch{align-items:center;border:1px solid #d1d5db;border-radius:4px;display:inline-flex;font-size:11px;font-weight:700;height:18px;justify-content:center;line-height:1;pointer-events:none;width:18px}.rte-media-width-custom{align-items:center;display:flex;gap:2px}.rte-media-width-input{border:1px solid #e5e7eb;border-radius:4px;color:#374151;font-size:11px;height:28px;outline:none;padding:0 6px;width:56px}.rte-media-width-input:focus{border-color:#3b82f6}.rte-media-width-unit{background:#fff;border:1px solid #e5e7eb;border-radius:4px;color:#374151;cursor:pointer;font-size:11px;height:28px;outline:none;padding:0 4px;width:44px}.rte-media-width-apply{color:#2563eb!important;font-size:13px!important;font-weight:700;height:28px;min-width:28px!important;padding:0 6px!important}.rte-media-width-apply:hover{background-color:#eff6ff!important}.rte-area-highlight-mode .rte-content{cursor:crosshair}.rte-area-highlight-block{box-decoration-break:clone;-webkit-box-decoration-break:clone;box-sizing:border-box;display:block}.rte-area-highlight-hint{background:rgba(37,99,235,.92);border-radius:999px;color:#fff;font-size:11px;font-weight:600;left:50%;padding:4px 10px;pointer-events:none;position:absolute;top:8px;transform:translateX(-50%);white-space:nowrap;z-index:5}.rte-marquee-preview{border:2px dashed;border-radius:2px;box-sizing:border-box;pointer-events:none;position:absolute;z-index:6}.rte-area-highlight-toggle{min-width:32px;padding:0 6px!important;width:auto!important}.rte-area-highlight-icon{border:2px dashed;border-radius:2px;box-sizing:border-box;display:block;height:14px;width:14px}.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{box-sizing:border-box;display:block;line-height:0;margin:16px 0;max-width:100%;position:relative}.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-container[data-width-px] .image-media-frame,.image-container[data-width-px] .image-media-frame img{height:auto;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;flex-wrap:wrap;gap:2px;max-width:calc(100% - 16px);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}";
|
|
413
413
|
styleInject(css_248z);
|
|
414
414
|
|
|
415
415
|
// Helper functions for HTML escaping
|
|
@@ -421,6 +421,108 @@ const escapeAttr = str => escapeHtml(str).replace(/"/g, """);
|
|
|
421
421
|
|
|
422
422
|
// URL detection regex
|
|
423
423
|
const URL_REGEX = /(https?:\/\/[^\s]+)/g;
|
|
424
|
+
const rectsIntersect = (a, b) => !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom);
|
|
425
|
+
const caretRangeFromClientPoint = (x, y) => {
|
|
426
|
+
var _document$caretPositi, _document;
|
|
427
|
+
if (document.caretRangeFromPoint) {
|
|
428
|
+
return document.caretRangeFromPoint(x, y);
|
|
429
|
+
}
|
|
430
|
+
const position = (_document$caretPositi = (_document = document).caretPositionFromPoint) === null || _document$caretPositi === void 0 ? void 0 : _document$caretPositi.call(_document, x, y);
|
|
431
|
+
if (!position) return null;
|
|
432
|
+
const range = document.createRange();
|
|
433
|
+
range.setStart(position.offsetNode, position.offset);
|
|
434
|
+
range.collapse(true);
|
|
435
|
+
return range;
|
|
436
|
+
};
|
|
437
|
+
const clipRectToBounds = (rect, bounds) => {
|
|
438
|
+
const left = Math.max(rect.left, bounds.left);
|
|
439
|
+
const top = Math.max(rect.top, bounds.top);
|
|
440
|
+
const right = Math.min(rect.right, bounds.right);
|
|
441
|
+
const bottom = Math.min(rect.bottom, bounds.bottom);
|
|
442
|
+
return {
|
|
443
|
+
left,
|
|
444
|
+
top,
|
|
445
|
+
right,
|
|
446
|
+
bottom,
|
|
447
|
+
width: Math.max(0, right - left),
|
|
448
|
+
height: Math.max(0, bottom - top)
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
const BLOCK_SELECTOR = "h1, h2, h3, h4, h5, h6, p, blockquote, li, td, th, div:not(.rte-area-highlight-block):not(.image-container):not(.video-container):not(.image-media-frame)";
|
|
452
|
+
const getIntersectingBlocks = (editor, clientRect) => {
|
|
453
|
+
const candidates = [...editor.querySelectorAll(BLOCK_SELECTOR)].filter(el => {
|
|
454
|
+
if (el === editor || !editor.contains(el)) return false;
|
|
455
|
+
if (el.closest(".rte-area-highlight-block, .image-container, .video-container")) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
return rectsIntersect(el.getBoundingClientRect(), clientRect);
|
|
459
|
+
});
|
|
460
|
+
return candidates.filter(el => !candidates.some(other => other !== el && other.contains(el)));
|
|
461
|
+
};
|
|
462
|
+
const insertAreaHighlightRegion = (editor, clientRect, color) => {
|
|
463
|
+
const editorRect = editor.getBoundingClientRect();
|
|
464
|
+
const clipped = clipRectToBounds(clientRect, editorRect);
|
|
465
|
+
const widthPx = Math.max(40, Math.round(clipped.width));
|
|
466
|
+
const heightPx = Math.max(24, Math.round(clipped.height));
|
|
467
|
+
const region = document.createElement("div");
|
|
468
|
+
region.className = "rte-area-highlight-block";
|
|
469
|
+
region.style.backgroundColor = color;
|
|
470
|
+
region.style.minHeight = `${heightPx}px`;
|
|
471
|
+
region.style.width = `${widthPx}px`;
|
|
472
|
+
region.style.maxWidth = "100%";
|
|
473
|
+
region.style.boxSizing = "border-box";
|
|
474
|
+
region.style.padding = "12px";
|
|
475
|
+
region.style.borderRadius = "4px";
|
|
476
|
+
region.style.margin = "8px 0";
|
|
477
|
+
region.style.display = "block";
|
|
478
|
+
const blocks = getIntersectingBlocks(editor, clipped);
|
|
479
|
+
if (blocks.length > 0) {
|
|
480
|
+
const first = blocks[0];
|
|
481
|
+
if (first.parentNode) {
|
|
482
|
+
first.parentNode.insertBefore(region, first);
|
|
483
|
+
} else {
|
|
484
|
+
editor.appendChild(region);
|
|
485
|
+
}
|
|
486
|
+
blocks.forEach(block => {
|
|
487
|
+
block.style.backgroundColor = "";
|
|
488
|
+
block.style.borderRadius = "";
|
|
489
|
+
region.appendChild(block);
|
|
490
|
+
});
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
const insertRange = caretRangeFromClientPoint(clipped.left + clipped.width / 2, clipped.top + Math.min(8, clipped.height / 2)) || caretRangeFromClientPoint(clipped.left + 4, clipped.top + 4);
|
|
494
|
+
region.innerHTML = " ";
|
|
495
|
+
if (insertRange && editor.contains(insertRange.startContainer)) {
|
|
496
|
+
insertRange.collapse(true);
|
|
497
|
+
let node = insertRange.startContainer;
|
|
498
|
+
if (node.nodeType === 3) node = node.parentNode;
|
|
499
|
+
while (node && node !== editor && node.parentNode !== editor) {
|
|
500
|
+
node = node.parentNode;
|
|
501
|
+
}
|
|
502
|
+
if (node && node !== editor && editor.contains(node)) {
|
|
503
|
+
node.parentNode.insertBefore(region, node);
|
|
504
|
+
} else {
|
|
505
|
+
editor.appendChild(region);
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
editor.appendChild(region);
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
};
|
|
512
|
+
const getClientRectFromDrag = (startX, startY, currentX, currentY) => {
|
|
513
|
+
const left = Math.min(startX, currentX);
|
|
514
|
+
const top = Math.min(startY, currentY);
|
|
515
|
+
const right = Math.max(startX, currentX);
|
|
516
|
+
const bottom = Math.max(startY, currentY);
|
|
517
|
+
return {
|
|
518
|
+
left,
|
|
519
|
+
top,
|
|
520
|
+
right,
|
|
521
|
+
bottom,
|
|
522
|
+
width: right - left,
|
|
523
|
+
height: bottom - top
|
|
524
|
+
};
|
|
525
|
+
};
|
|
424
526
|
function RichTextEditor({
|
|
425
527
|
onChange,
|
|
426
528
|
showEditButton,
|
|
@@ -439,8 +541,11 @@ function RichTextEditor({
|
|
|
439
541
|
onImageUpload
|
|
440
542
|
}) {
|
|
441
543
|
const editorRef = useRef(null);
|
|
544
|
+
const contentWrapperRef = useRef(null);
|
|
442
545
|
const fileInputRef = useRef(null);
|
|
443
546
|
const scrollTopRef = useRef(0);
|
|
547
|
+
const areaDragRef = useRef(null);
|
|
548
|
+
const bgColorRef = useRef("#ffff00");
|
|
444
549
|
const [html, setHtml] = useState("");
|
|
445
550
|
const [linkModalOpen, setLinkModalOpen] = useState(false);
|
|
446
551
|
const [linkUrl, setLinkUrl] = useState("");
|
|
@@ -481,11 +586,15 @@ function RichTextEditor({
|
|
|
481
586
|
const [tableCols, setTableCols] = useState(3);
|
|
482
587
|
const [selectionVersion, setSelectionVersion] = useState(0);
|
|
483
588
|
const [selectedMedia, setSelectedMedia] = useState(null);
|
|
589
|
+
const [mediaWidthInput, setMediaWidthInput] = useState("100");
|
|
590
|
+
const [mediaWidthUnit, setMediaWidthUnit] = useState("%");
|
|
484
591
|
const [metrics, setMetrics] = useState({
|
|
485
592
|
words: 0,
|
|
486
593
|
chars: 0
|
|
487
594
|
});
|
|
488
595
|
const [isEmpty, setIsEmpty] = useState(!value);
|
|
596
|
+
const [areaHighlightMode, setAreaHighlightMode] = useState(false);
|
|
597
|
+
const [marqueePreview, setMarqueePreview] = useState(null);
|
|
489
598
|
const updateMetrics = useCallback(() => {
|
|
490
599
|
if (!editorRef.current) return;
|
|
491
600
|
// Calculate metrics immediately but outside of render path
|
|
@@ -522,6 +631,10 @@ function RichTextEditor({
|
|
|
522
631
|
selectionRangeRef.current = sel.getRangeAt(0).cloneRange();
|
|
523
632
|
}
|
|
524
633
|
};
|
|
634
|
+
const handleKeyUp = () => {
|
|
635
|
+
saveSelection();
|
|
636
|
+
setSelectionVersion(v => v + 1);
|
|
637
|
+
};
|
|
525
638
|
const handleZoomIn = () => {
|
|
526
639
|
setZoomLevel(prevZoom => prevZoom + 0.1);
|
|
527
640
|
};
|
|
@@ -771,9 +884,31 @@ function RichTextEditor({
|
|
|
771
884
|
}
|
|
772
885
|
return 100;
|
|
773
886
|
};
|
|
887
|
+
const getMediaWidthPx = container => {
|
|
888
|
+
if (!container) return 0;
|
|
889
|
+
if (container.dataset.widthPx) {
|
|
890
|
+
return Number(container.dataset.widthPx);
|
|
891
|
+
}
|
|
892
|
+
const width = container.style.width || "";
|
|
893
|
+
if (width.endsWith("px")) {
|
|
894
|
+
return parseInt(width, 10) || 0;
|
|
895
|
+
}
|
|
896
|
+
return Math.round(container.getBoundingClientRect().width);
|
|
897
|
+
};
|
|
898
|
+
const syncMediaWidthControls = container => {
|
|
899
|
+
if (!container) return;
|
|
900
|
+
if (container.dataset.widthPx) {
|
|
901
|
+
setMediaWidthInput(String(getMediaWidthPx(container)));
|
|
902
|
+
setMediaWidthUnit("px");
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
setMediaWidthInput(String(getMediaWidthPercent(container)));
|
|
906
|
+
setMediaWidthUnit("%");
|
|
907
|
+
};
|
|
774
908
|
const applyMediaWidthPercent = (container, percent) => {
|
|
775
909
|
if (!container) return;
|
|
776
|
-
const clamped = Math.max(
|
|
910
|
+
const clamped = Math.max(10, Math.min(100, Math.round(percent)));
|
|
911
|
+
delete container.dataset.widthPx;
|
|
777
912
|
container.dataset.widthPercent = String(clamped);
|
|
778
913
|
container.style.width = `${clamped}%`;
|
|
779
914
|
container.style.maxWidth = "100%";
|
|
@@ -794,8 +929,36 @@ function RichTextEditor({
|
|
|
794
929
|
img.style.objectFit = "";
|
|
795
930
|
}
|
|
796
931
|
};
|
|
932
|
+
const applyMediaWidthPx = (container, px) => {
|
|
933
|
+
if (!container) return;
|
|
934
|
+
const clamped = Math.max(20, Math.min(2000, Math.round(px)));
|
|
935
|
+
delete container.dataset.widthPercent;
|
|
936
|
+
container.dataset.widthPx = String(clamped);
|
|
937
|
+
container.style.width = `${clamped}px`;
|
|
938
|
+
container.style.maxWidth = "100%";
|
|
939
|
+
container.style.marginLeft = "";
|
|
940
|
+
container.style.marginTop = "";
|
|
941
|
+
if (container.classList.contains("video-container")) {
|
|
942
|
+
container.style.height = "0";
|
|
943
|
+
container.style.paddingBottom = "56.25%";
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const frame = getImageMediaTarget(container);
|
|
947
|
+
if (!frame) return;
|
|
948
|
+
frame.style.width = "100%";
|
|
949
|
+
frame.style.height = "";
|
|
950
|
+
const img = frame.querySelector("img");
|
|
951
|
+
if (img) {
|
|
952
|
+
img.style.height = "auto";
|
|
953
|
+
img.style.objectFit = "";
|
|
954
|
+
}
|
|
955
|
+
};
|
|
797
956
|
const normalizeMediaWidth = container => {
|
|
798
957
|
if (!container) return;
|
|
958
|
+
if (container.dataset.widthPx) {
|
|
959
|
+
applyMediaWidthPx(container, Number(container.dataset.widthPx));
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
799
962
|
if (container.classList.contains("image-small")) {
|
|
800
963
|
container.classList.remove("image-small");
|
|
801
964
|
applyMediaWidthPercent(container, 50);
|
|
@@ -811,11 +974,7 @@ function RichTextEditor({
|
|
|
811
974
|
return;
|
|
812
975
|
}
|
|
813
976
|
if (width.endsWith("px")) {
|
|
814
|
-
|
|
815
|
-
const px = parseFloat(width);
|
|
816
|
-
if (editorWidth > 0 && px > 0) {
|
|
817
|
-
applyMediaWidthPercent(container, Math.round(px / editorWidth * 100));
|
|
818
|
-
}
|
|
977
|
+
applyMediaWidthPx(container, parseFloat(width));
|
|
819
978
|
return;
|
|
820
979
|
}
|
|
821
980
|
if (container.classList.contains("video-container")) {
|
|
@@ -858,12 +1017,18 @@ function RichTextEditor({
|
|
|
858
1017
|
const startWidth = container.getBoundingClientRect().width;
|
|
859
1018
|
const onMouseMove = moveEvent => {
|
|
860
1019
|
const nextWidth = Math.max(60, startWidth + (moveEvent.clientX - startX));
|
|
861
|
-
|
|
862
|
-
|
|
1020
|
+
if (container.dataset.widthPx) {
|
|
1021
|
+
applyMediaWidthPx(container, nextWidth);
|
|
1022
|
+
} else {
|
|
1023
|
+
const percent = Math.round(nextWidth / editorWidth * 100);
|
|
1024
|
+
applyMediaWidthPercent(container, percent);
|
|
1025
|
+
}
|
|
863
1026
|
};
|
|
864
1027
|
const onMouseUp = () => {
|
|
865
1028
|
document.removeEventListener("mousemove", onMouseMove);
|
|
866
1029
|
document.removeEventListener("mouseup", onMouseUp);
|
|
1030
|
+
syncMediaWidthControls(container);
|
|
1031
|
+
setSelectionVersion(v => v + 1);
|
|
867
1032
|
triggerChange();
|
|
868
1033
|
};
|
|
869
1034
|
document.addEventListener("mousemove", onMouseMove);
|
|
@@ -990,11 +1155,118 @@ function RichTextEditor({
|
|
|
990
1155
|
focus();
|
|
991
1156
|
};
|
|
992
1157
|
const [fontColor, setFontColor] = useState("#000000");
|
|
1158
|
+
const [bgColor, setBgColor] = useState("#ffff00");
|
|
1159
|
+
useEffect(() => {
|
|
1160
|
+
bgColorRef.current = bgColor;
|
|
1161
|
+
}, [bgColor]);
|
|
993
1162
|
const getActiveTextColor = () => getColorAtCursor() || fontColor;
|
|
994
|
-
const
|
|
1163
|
+
const restoreSavedSelection = () => {
|
|
1164
|
+
if (!selectionRangeRef.current) return;
|
|
1165
|
+
const sel = window.getSelection();
|
|
1166
|
+
if (!sel) return;
|
|
1167
|
+
sel.removeAllRanges();
|
|
1168
|
+
sel.addRange(selectionRangeRef.current);
|
|
1169
|
+
};
|
|
1170
|
+
const applyTextColor = color => {
|
|
995
1171
|
setFontColor(color);
|
|
996
|
-
|
|
1172
|
+
focus();
|
|
1173
|
+
restoreSavedSelection();
|
|
1174
|
+
document.execCommand("styleWithCSS", false, true);
|
|
1175
|
+
document.execCommand("foreColor", false, color);
|
|
1176
|
+
document.execCommand("styleWithCSS", false, false);
|
|
1177
|
+
triggerChange();
|
|
1178
|
+
};
|
|
1179
|
+
const applyBackgroundColor = color => {
|
|
1180
|
+
setBgColor(color);
|
|
1181
|
+
focus();
|
|
1182
|
+
restoreSavedSelection();
|
|
1183
|
+
document.execCommand("styleWithCSS", false, true);
|
|
1184
|
+
if (!document.execCommand("hiliteColor", false, color)) {
|
|
1185
|
+
document.execCommand("backColor", false, color);
|
|
1186
|
+
}
|
|
1187
|
+
document.execCommand("styleWithCSS", false, false);
|
|
1188
|
+
triggerChange();
|
|
997
1189
|
};
|
|
1190
|
+
const applyMarqueeHighlight = useCallback((clientRect, color) => {
|
|
1191
|
+
const editor = editorRef.current;
|
|
1192
|
+
if (!editor || clientRect.width < 10 || clientRect.height < 10) {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
editor.focus();
|
|
1196
|
+
const editorRect = editor.getBoundingClientRect();
|
|
1197
|
+
const clippedRect = clipRectToBounds(clientRect, editorRect);
|
|
1198
|
+
if (clippedRect.width < 10 || clippedRect.height < 10) {
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
insertAreaHighlightRegion(editor, clippedRect, color);
|
|
1202
|
+
editor.querySelectorAll("td, th").forEach(cell => {
|
|
1203
|
+
if (!editor.contains(cell)) return;
|
|
1204
|
+
if (cell.closest(".rte-area-highlight-block")) return;
|
|
1205
|
+
if (rectsIntersect(cell.getBoundingClientRect(), clippedRect)) {
|
|
1206
|
+
cell.style.backgroundColor = color;
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
editor.querySelectorAll(".image-container, .video-container").forEach(media => {
|
|
1210
|
+
if (!editor.contains(media)) return;
|
|
1211
|
+
if (rectsIntersect(media.getBoundingClientRect(), clippedRect)) {
|
|
1212
|
+
media.style.backgroundColor = color;
|
|
1213
|
+
media.style.padding = "4px";
|
|
1214
|
+
media.style.borderRadius = "4px";
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
updateMetrics();
|
|
1218
|
+
triggerChange();
|
|
1219
|
+
focus();
|
|
1220
|
+
}, [triggerChange, updateMetrics]);
|
|
1221
|
+
const handleAreaSelectMouseDown = useCallback(e => {
|
|
1222
|
+
if (!areaHighlightMode || !editable || disabled) return;
|
|
1223
|
+
if (e.button !== 0) return;
|
|
1224
|
+
if (e.target.closest(".rte-toolbar, .rte-media-toolbar, .rte-modal-overlay")) return;
|
|
1225
|
+
e.preventDefault();
|
|
1226
|
+
e.stopPropagation();
|
|
1227
|
+
const wrapper = contentWrapperRef.current;
|
|
1228
|
+
if (!wrapper) return;
|
|
1229
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
1230
|
+
areaDragRef.current = {
|
|
1231
|
+
startX: e.clientX,
|
|
1232
|
+
startY: e.clientY,
|
|
1233
|
+
currentX: e.clientX,
|
|
1234
|
+
currentY: e.clientY,
|
|
1235
|
+
wrapperRect
|
|
1236
|
+
};
|
|
1237
|
+
const updatePreview = (clientX, clientY) => {
|
|
1238
|
+
const drag = areaDragRef.current;
|
|
1239
|
+
if (!drag) return;
|
|
1240
|
+
drag.currentX = clientX;
|
|
1241
|
+
drag.currentY = clientY;
|
|
1242
|
+
const rect = getClientRectFromDrag(drag.startX, drag.startY, drag.currentX, drag.currentY);
|
|
1243
|
+
setMarqueePreview({
|
|
1244
|
+
left: rect.left - drag.wrapperRect.left,
|
|
1245
|
+
top: rect.top - drag.wrapperRect.top,
|
|
1246
|
+
width: rect.width,
|
|
1247
|
+
height: rect.height
|
|
1248
|
+
});
|
|
1249
|
+
};
|
|
1250
|
+
const onMove = ev => {
|
|
1251
|
+
ev.preventDefault();
|
|
1252
|
+
updatePreview(ev.clientX, ev.clientY);
|
|
1253
|
+
};
|
|
1254
|
+
const onUp = ev => {
|
|
1255
|
+
document.removeEventListener("mousemove", onMove);
|
|
1256
|
+
document.removeEventListener("mouseup", onUp);
|
|
1257
|
+
const drag = areaDragRef.current;
|
|
1258
|
+
areaDragRef.current = null;
|
|
1259
|
+
setMarqueePreview(null);
|
|
1260
|
+
if (!drag) return;
|
|
1261
|
+
const clientRect = getClientRectFromDrag(drag.startX, drag.startY, ev.clientX, ev.clientY);
|
|
1262
|
+
requestAnimationFrame(() => {
|
|
1263
|
+
applyMarqueeHighlight(clientRect, bgColorRef.current);
|
|
1264
|
+
});
|
|
1265
|
+
};
|
|
1266
|
+
document.addEventListener("mousemove", onMove);
|
|
1267
|
+
document.addEventListener("mouseup", onUp);
|
|
1268
|
+
updatePreview(e.clientX, e.clientY);
|
|
1269
|
+
}, [areaHighlightMode, editable, disabled, applyMarqueeHighlight]);
|
|
998
1270
|
const addLink = () => {
|
|
999
1271
|
const sel = window.getSelection();
|
|
1000
1272
|
if (!sel || sel.rangeCount === 0) return;
|
|
@@ -1248,9 +1520,25 @@ function RichTextEditor({
|
|
|
1248
1520
|
if (frame && !frame.querySelector(".image-delete-button")) {
|
|
1249
1521
|
frame.appendChild(createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1250
1522
|
existingWrapper.remove();
|
|
1523
|
+
clearMediaSelection();
|
|
1251
1524
|
triggerChange && triggerChange();
|
|
1252
1525
|
}));
|
|
1253
1526
|
}
|
|
1527
|
+
if (!existingWrapper.dataset.mediaEnhanced) {
|
|
1528
|
+
existingWrapper.dataset.mediaEnhanced = "true";
|
|
1529
|
+
existingWrapper.addEventListener("click", event => {
|
|
1530
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1531
|
+
event.preventDefault();
|
|
1532
|
+
event.stopPropagation();
|
|
1533
|
+
selectMediaContainer(existingWrapper);
|
|
1534
|
+
});
|
|
1535
|
+
img.addEventListener("dblclick", event => {
|
|
1536
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1537
|
+
event.preventDefault();
|
|
1538
|
+
event.stopPropagation();
|
|
1539
|
+
openImageModal(img.src);
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1254
1542
|
attachMediaResizeHandle(existingWrapper);
|
|
1255
1543
|
normalizeMediaWidth(existingWrapper);
|
|
1256
1544
|
updateMediaControlVisibility(existingWrapper);
|
|
@@ -1328,14 +1616,33 @@ function RichTextEditor({
|
|
|
1328
1616
|
event.stopPropagation();
|
|
1329
1617
|
selectMediaContainer(container);
|
|
1330
1618
|
});
|
|
1619
|
+
container.style.cursor = editable ? "pointer" : "default";
|
|
1620
|
+
container.dataset.mediaEnhanced = "true";
|
|
1621
|
+
container.addEventListener("click", event => {
|
|
1622
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1623
|
+
event.preventDefault();
|
|
1624
|
+
event.stopPropagation();
|
|
1625
|
+
selectMediaContainer(container);
|
|
1626
|
+
});
|
|
1627
|
+
const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1628
|
+
container.remove();
|
|
1629
|
+
clearMediaSelection();
|
|
1630
|
+
triggerChange();
|
|
1631
|
+
});
|
|
1331
1632
|
frame.appendChild(img);
|
|
1633
|
+
frame.appendChild(deleteBtn);
|
|
1332
1634
|
container.appendChild(frame);
|
|
1333
|
-
|
|
1635
|
+
attachMediaResizeHandle(container);
|
|
1636
|
+
applyMediaWidthPercent(container, 50);
|
|
1334
1637
|
|
|
1335
1638
|
// Insert at cursor position
|
|
1336
1639
|
insertNodeAtCursor(container);
|
|
1337
1640
|
requestAnimationFrame(() => {
|
|
1641
|
+
var _editorRef$current5;
|
|
1338
1642
|
processExistingMedia(editorRef.current);
|
|
1643
|
+
selectMediaContainer(container);
|
|
1644
|
+
syncMediaWidthControls(container);
|
|
1645
|
+
(_editorRef$current5 = editorRef.current) === null || _editorRef$current5 === void 0 || _editorRef$current5.focus();
|
|
1339
1646
|
triggerChange();
|
|
1340
1647
|
});
|
|
1341
1648
|
}
|
|
@@ -1503,6 +1810,28 @@ function RichTextEditor({
|
|
|
1503
1810
|
selection.addRange(newRange);
|
|
1504
1811
|
};
|
|
1505
1812
|
const handleKeyDown = useCallback(e => {
|
|
1813
|
+
var _editorRef$current6;
|
|
1814
|
+
if (e.key === "Escape") {
|
|
1815
|
+
if (areaHighlightMode) {
|
|
1816
|
+
e.preventDefault();
|
|
1817
|
+
areaDragRef.current = null;
|
|
1818
|
+
setMarqueePreview(null);
|
|
1819
|
+
setAreaHighlightMode(false);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
if (selectedMedia) {
|
|
1823
|
+
e.preventDefault();
|
|
1824
|
+
clearMediaSelection();
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
if ((e.key === "Delete" || e.key === "Backspace") && selectedMedia !== null && selectedMedia !== void 0 && selectedMedia.classList.contains("rte-media-selected") && (_editorRef$current6 = editorRef.current) !== null && _editorRef$current6 !== void 0 && _editorRef$current6.contains(selectedMedia)) {
|
|
1829
|
+
e.preventDefault();
|
|
1830
|
+
selectedMedia.remove();
|
|
1831
|
+
clearMediaSelection();
|
|
1832
|
+
triggerChange();
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1506
1835
|
if (applyMarkdownShortcut(e)) return;
|
|
1507
1836
|
|
|
1508
1837
|
// Handle Enter key
|
|
@@ -1576,7 +1905,7 @@ function RichTextEditor({
|
|
|
1576
1905
|
return;
|
|
1577
1906
|
}
|
|
1578
1907
|
if (e.key === "Backspace") {
|
|
1579
|
-
var _node$closest, _node, _editorRef$
|
|
1908
|
+
var _node$closest, _node, _editorRef$current7;
|
|
1580
1909
|
const selection = window.getSelection();
|
|
1581
1910
|
if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
|
|
1582
1911
|
const range = selection.getRangeAt(0);
|
|
@@ -1586,7 +1915,7 @@ function RichTextEditor({
|
|
|
1586
1915
|
node = node.parentNode;
|
|
1587
1916
|
}
|
|
1588
1917
|
const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
|
|
1589
|
-
if (!listItem || !((_editorRef$
|
|
1918
|
+
if (!listItem || !((_editorRef$current7 = editorRef.current) !== null && _editorRef$current7 !== void 0 && _editorRef$current7.contains(listItem))) return;
|
|
1590
1919
|
const list = listItem.parentNode;
|
|
1591
1920
|
if (isListItemEffectivelyEmpty(listItem)) {
|
|
1592
1921
|
e.preventDefault();
|
|
@@ -1635,7 +1964,7 @@ function RichTextEditor({
|
|
|
1635
1964
|
e.preventDefault();
|
|
1636
1965
|
exec("underline");
|
|
1637
1966
|
}
|
|
1638
|
-
}, [exec, triggerChange, fontColor]);
|
|
1967
|
+
}, [exec, triggerChange, fontColor, areaHighlightMode, selectedMedia]);
|
|
1639
1968
|
const confirmLink = () => {
|
|
1640
1969
|
// Add protocol if missing
|
|
1641
1970
|
let url = linkUrl.trim();
|
|
@@ -1677,6 +2006,11 @@ function RichTextEditor({
|
|
|
1677
2006
|
setCurrentFontSize("16");
|
|
1678
2007
|
setCurrentLineHeight("");
|
|
1679
2008
|
setFontColor("#000000");
|
|
2009
|
+
setBgColor("#ffff00");
|
|
2010
|
+
document.execCommand("styleWithCSS", false, true);
|
|
2011
|
+
document.execCommand("hiliteColor", false, "transparent");
|
|
2012
|
+
document.execCommand("backColor", false, "transparent");
|
|
2013
|
+
document.execCommand("styleWithCSS", false, false);
|
|
1680
2014
|
triggerChange();
|
|
1681
2015
|
focus();
|
|
1682
2016
|
};
|
|
@@ -1903,7 +2237,11 @@ function RichTextEditor({
|
|
|
1903
2237
|
}
|
|
1904
2238
|
}, [disabled]);
|
|
1905
2239
|
const handleEditorClick = useCallback(e => {
|
|
1906
|
-
var _editorRef$
|
|
2240
|
+
var _editorRef$current8;
|
|
2241
|
+
if (areaHighlightMode) {
|
|
2242
|
+
e.preventDefault();
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
1907
2245
|
setSelectionVersion(v => v + 1);
|
|
1908
2246
|
const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
|
|
1909
2247
|
if (deleteBtn && editable && editorFocused) {
|
|
@@ -1918,7 +2256,10 @@ function RichTextEditor({
|
|
|
1918
2256
|
return;
|
|
1919
2257
|
}
|
|
1920
2258
|
const clickedMedia = e.target.closest('.image-container, .video-container');
|
|
1921
|
-
if (clickedMedia && (_editorRef$
|
|
2259
|
+
if (clickedMedia && (_editorRef$current8 = editorRef.current) !== null && _editorRef$current8 !== void 0 && _editorRef$current8.contains(clickedMedia) && editable) {
|
|
2260
|
+
e.preventDefault();
|
|
2261
|
+
e.stopPropagation();
|
|
2262
|
+
selectMediaContainer(clickedMedia);
|
|
1922
2263
|
return;
|
|
1923
2264
|
}
|
|
1924
2265
|
|
|
@@ -1949,16 +2290,78 @@ function RichTextEditor({
|
|
|
1949
2290
|
}
|
|
1950
2291
|
}, 0);
|
|
1951
2292
|
}
|
|
1952
|
-
}, [editable, disabled, editorFocused, triggerChange]);
|
|
2293
|
+
}, [areaHighlightMode, editable, disabled, editorFocused, triggerChange]);
|
|
2294
|
+
useEffect(() => {
|
|
2295
|
+
if (!selectedMedia) return;
|
|
2296
|
+
syncMediaWidthControls(selectedMedia);
|
|
2297
|
+
}, [selectedMedia, selectionVersion]);
|
|
2298
|
+
useEffect(() => {
|
|
2299
|
+
const editor = editorRef.current;
|
|
2300
|
+
if (!editor || !selectedMedia) return;
|
|
2301
|
+
const bumpToolbarPosition = () => setSelectionVersion(v => v + 1);
|
|
2302
|
+
editor.addEventListener("scroll", bumpToolbarPosition, {
|
|
2303
|
+
passive: true
|
|
2304
|
+
});
|
|
2305
|
+
window.addEventListener("resize", bumpToolbarPosition, {
|
|
2306
|
+
passive: true
|
|
2307
|
+
});
|
|
2308
|
+
return () => {
|
|
2309
|
+
editor.removeEventListener("scroll", bumpToolbarPosition);
|
|
2310
|
+
window.removeEventListener("resize", bumpToolbarPosition);
|
|
2311
|
+
};
|
|
2312
|
+
}, [selectedMedia]);
|
|
1953
2313
|
const renderMediaToolbar = () => {
|
|
1954
2314
|
if (!selectedMedia || !editorRef.current || !editable) return null;
|
|
1955
|
-
const
|
|
2315
|
+
const wrapperEl = contentWrapperRef.current;
|
|
2316
|
+
const anchorRect = (wrapperEl === null || wrapperEl === void 0 ? void 0 : wrapperEl.getBoundingClientRect()) ?? editorRef.current.getBoundingClientRect();
|
|
1956
2317
|
const mediaRect = selectedMedia.getBoundingClientRect();
|
|
1957
|
-
const
|
|
1958
|
-
const
|
|
1959
|
-
const
|
|
2318
|
+
const relTop = mediaRect.top - anchorRect.top;
|
|
2319
|
+
const relLeft = mediaRect.left - anchorRect.left;
|
|
2320
|
+
const mediaWidth = mediaRect.width;
|
|
2321
|
+
const mediaHeight = mediaRect.height;
|
|
1960
2322
|
const currentPercent = getMediaWidthPercent(selectedMedia);
|
|
1961
2323
|
const widthPresets = [25, 50, 75, 100];
|
|
2324
|
+
const toolbarWidth = 340;
|
|
2325
|
+
const toolbarHeight = 40;
|
|
2326
|
+
const gap = 8;
|
|
2327
|
+
const preferredTop = relTop - toolbarHeight - gap;
|
|
2328
|
+
const toolbarTop = preferredTop >= 4 ? preferredTop : relTop + mediaHeight + gap;
|
|
2329
|
+
const applyCustomMediaWidth = () => {
|
|
2330
|
+
const num = parseFloat(mediaWidthInput);
|
|
2331
|
+
if (isNaN(num) || num <= 0) {
|
|
2332
|
+
syncMediaWidthControls(selectedMedia);
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
if (mediaWidthUnit === "px") {
|
|
2336
|
+
applyMediaWidthPx(selectedMedia, num);
|
|
2337
|
+
} else {
|
|
2338
|
+
applyMediaWidthPercent(selectedMedia, num);
|
|
2339
|
+
}
|
|
2340
|
+
syncMediaWidthControls(selectedMedia);
|
|
2341
|
+
setSelectionVersion(v => v + 1);
|
|
2342
|
+
triggerChange();
|
|
2343
|
+
};
|
|
2344
|
+
const handleUnitChange = newUnit => {
|
|
2345
|
+
if (newUnit === mediaWidthUnit) return;
|
|
2346
|
+
const editorInnerWidth = getEditorInnerWidth();
|
|
2347
|
+
const currentVal = parseFloat(mediaWidthInput);
|
|
2348
|
+
if (isNaN(currentVal) || currentVal <= 0) {
|
|
2349
|
+
setMediaWidthUnit(newUnit);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
let converted = currentVal;
|
|
2353
|
+
if (newUnit === "px" && mediaWidthUnit === "%") {
|
|
2354
|
+
converted = Math.round(editorInnerWidth * currentVal / 100);
|
|
2355
|
+
applyMediaWidthPx(selectedMedia, converted);
|
|
2356
|
+
} else if (newUnit === "%" && mediaWidthUnit === "px") {
|
|
2357
|
+
converted = Math.min(100, Math.max(10, Math.round(currentVal / editorInnerWidth * 100)));
|
|
2358
|
+
applyMediaWidthPercent(selectedMedia, converted);
|
|
2359
|
+
}
|
|
2360
|
+
setMediaWidthInput(String(converted));
|
|
2361
|
+
setMediaWidthUnit(newUnit);
|
|
2362
|
+
setSelectionVersion(v => v + 1);
|
|
2363
|
+
triggerChange();
|
|
2364
|
+
};
|
|
1962
2365
|
const handleAlignment = align => {
|
|
1963
2366
|
selectedMedia.classList.remove("image-align-left", "image-align-center", "image-align-right");
|
|
1964
2367
|
selectedMedia.classList.add(`image-align-${align}`);
|
|
@@ -1968,6 +2371,8 @@ function RichTextEditor({
|
|
|
1968
2371
|
};
|
|
1969
2372
|
const setWidth = percent => {
|
|
1970
2373
|
applyMediaWidthPercent(selectedMedia, percent);
|
|
2374
|
+
syncMediaWidthControls(selectedMedia);
|
|
2375
|
+
setSelectionVersion(v => v + 1);
|
|
1971
2376
|
triggerChange();
|
|
1972
2377
|
};
|
|
1973
2378
|
const removeMedia = () => {
|
|
@@ -1976,17 +2381,26 @@ function RichTextEditor({
|
|
|
1976
2381
|
triggerChange();
|
|
1977
2382
|
};
|
|
1978
2383
|
const isActivePercent = percent => {
|
|
2384
|
+
if (selectedMedia.dataset.widthPx) return false;
|
|
1979
2385
|
if (!selectedMedia.dataset.widthPercent && !(selectedMedia.style.width || "").endsWith("%")) {
|
|
1980
2386
|
return false;
|
|
1981
2387
|
}
|
|
1982
2388
|
return Math.abs(currentPercent - percent) <= 3;
|
|
1983
2389
|
};
|
|
2390
|
+
const isFormControl = target => target instanceof Element && !!target.closest("input, select, textarea");
|
|
1984
2391
|
return /*#__PURE__*/React.createElement("div", {
|
|
1985
2392
|
className: "rte-media-toolbar",
|
|
2393
|
+
onMouseDown: e => {
|
|
2394
|
+
e.stopPropagation();
|
|
2395
|
+
if (!isFormControl(e.target)) {
|
|
2396
|
+
e.preventDefault();
|
|
2397
|
+
}
|
|
2398
|
+
},
|
|
2399
|
+
onClick: e => e.stopPropagation(),
|
|
1986
2400
|
style: {
|
|
1987
2401
|
position: "absolute",
|
|
1988
|
-
top: Math.max(
|
|
1989
|
-
left: Math.max(8,
|
|
2402
|
+
top: Math.max(4, toolbarTop),
|
|
2403
|
+
left: Math.max(8, Math.min(relLeft + mediaWidth / 2 - toolbarWidth / 2, anchorRect.width - toolbarWidth - 8)),
|
|
1990
2404
|
zIndex: 1000
|
|
1991
2405
|
}
|
|
1992
2406
|
}, /*#__PURE__*/React.createElement("button", {
|
|
@@ -2011,6 +2425,44 @@ function RichTextEditor({
|
|
|
2011
2425
|
title: `${percent}% width`
|
|
2012
2426
|
}, percent, "%")), /*#__PURE__*/React.createElement("span", {
|
|
2013
2427
|
className: "rte-media-toolbar-divider"
|
|
2428
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
2429
|
+
className: "rte-media-width-custom"
|
|
2430
|
+
}, /*#__PURE__*/React.createElement("input", {
|
|
2431
|
+
type: "number",
|
|
2432
|
+
className: "rte-media-width-input",
|
|
2433
|
+
value: mediaWidthInput,
|
|
2434
|
+
min: mediaWidthUnit === "%" ? 10 : 20,
|
|
2435
|
+
max: mediaWidthUnit === "%" ? 100 : 2000,
|
|
2436
|
+
title: "Custom width",
|
|
2437
|
+
onChange: e => setMediaWidthInput(e.target.value),
|
|
2438
|
+
onBlur: e => {
|
|
2439
|
+
var _e$relatedTarget, _e$relatedTarget$clos;
|
|
2440
|
+
if ((_e$relatedTarget = e.relatedTarget) !== null && _e$relatedTarget !== void 0 && (_e$relatedTarget$clos = _e$relatedTarget.closest) !== null && _e$relatedTarget$clos !== void 0 && _e$relatedTarget$clos.call(_e$relatedTarget, ".rte-media-width-custom")) return;
|
|
2441
|
+
applyCustomMediaWidth();
|
|
2442
|
+
},
|
|
2443
|
+
onMouseDown: e => e.stopPropagation(),
|
|
2444
|
+
onKeyDown: e => {
|
|
2445
|
+
if (e.key === "Enter") {
|
|
2446
|
+
e.preventDefault();
|
|
2447
|
+
applyCustomMediaWidth();
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
}), /*#__PURE__*/React.createElement("select", {
|
|
2451
|
+
className: "rte-media-width-unit",
|
|
2452
|
+
value: mediaWidthUnit,
|
|
2453
|
+
onChange: e => handleUnitChange(e.target.value),
|
|
2454
|
+
onMouseDown: e => e.stopPropagation()
|
|
2455
|
+
}, /*#__PURE__*/React.createElement("option", {
|
|
2456
|
+
value: "%"
|
|
2457
|
+
}, "%"), /*#__PURE__*/React.createElement("option", {
|
|
2458
|
+
value: "px"
|
|
2459
|
+
}, "px")), /*#__PURE__*/React.createElement("button", {
|
|
2460
|
+
type: "button",
|
|
2461
|
+
className: "rte-media-width-apply",
|
|
2462
|
+
title: "Apply size",
|
|
2463
|
+
onClick: applyCustomMediaWidth
|
|
2464
|
+
}, "\u2713")), /*#__PURE__*/React.createElement("span", {
|
|
2465
|
+
className: "rte-media-toolbar-divider"
|
|
2014
2466
|
}), /*#__PURE__*/React.createElement("button", {
|
|
2015
2467
|
type: "button",
|
|
2016
2468
|
onClick: removeMedia,
|
|
@@ -2145,7 +2597,7 @@ function RichTextEditor({
|
|
|
2145
2597
|
key: s,
|
|
2146
2598
|
value: s
|
|
2147
2599
|
}, s, "px"))), /*#__PURE__*/React.createElement("label", {
|
|
2148
|
-
title: "
|
|
2600
|
+
title: "Text Color",
|
|
2149
2601
|
className: "rte-color-picker-label"
|
|
2150
2602
|
}, /*#__PURE__*/React.createElement(FaFont, {
|
|
2151
2603
|
size: 14,
|
|
@@ -2158,12 +2610,53 @@ function RichTextEditor({
|
|
|
2158
2610
|
onMouseDown: e => {
|
|
2159
2611
|
e.preventDefault();
|
|
2160
2612
|
e.stopPropagation();
|
|
2613
|
+
saveSelection();
|
|
2614
|
+
},
|
|
2615
|
+
onChange: e => {
|
|
2616
|
+
e.stopPropagation();
|
|
2617
|
+
applyTextColor(e.target.value);
|
|
2618
|
+
},
|
|
2619
|
+
className: "rte-color-input"
|
|
2620
|
+
})), /*#__PURE__*/React.createElement("label", {
|
|
2621
|
+
title: areaHighlightMode ? "Highlight color for area selection" : "Text background color",
|
|
2622
|
+
className: "rte-color-picker-label rte-bg-color-picker-label"
|
|
2623
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
2624
|
+
className: "rte-bg-color-swatch",
|
|
2625
|
+
style: {
|
|
2626
|
+
backgroundColor: bgColor,
|
|
2627
|
+
color: fontColor
|
|
2628
|
+
}
|
|
2629
|
+
}, "A"), /*#__PURE__*/React.createElement("input", {
|
|
2630
|
+
type: "color",
|
|
2631
|
+
value: bgColor,
|
|
2632
|
+
onMouseDown: e => {
|
|
2633
|
+
e.preventDefault();
|
|
2634
|
+
e.stopPropagation();
|
|
2635
|
+
if (!areaHighlightMode) saveSelection();
|
|
2161
2636
|
},
|
|
2162
2637
|
onChange: e => {
|
|
2163
2638
|
e.stopPropagation();
|
|
2164
|
-
|
|
2639
|
+
const color = e.target.value;
|
|
2640
|
+
setBgColor(color);
|
|
2641
|
+
if (!areaHighlightMode) {
|
|
2642
|
+
applyBackgroundColor(color);
|
|
2643
|
+
}
|
|
2165
2644
|
},
|
|
2166
2645
|
className: "rte-color-input"
|
|
2646
|
+
})), /*#__PURE__*/React.createElement("button", {
|
|
2647
|
+
type: "button",
|
|
2648
|
+
title: "Area highlight \u2014 drag to select a box (like screenshot)",
|
|
2649
|
+
className: `rte-toolbar-button rte-area-highlight-toggle${areaHighlightMode ? " active" : ""}`,
|
|
2650
|
+
onMouseDown: e => e.preventDefault(),
|
|
2651
|
+
onClick: () => {
|
|
2652
|
+
setAreaHighlightMode(prev => !prev);
|
|
2653
|
+
setMarqueePreview(null);
|
|
2654
|
+
areaDragRef.current = null;
|
|
2655
|
+
focus();
|
|
2656
|
+
}
|
|
2657
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
2658
|
+
className: "rte-area-highlight-icon",
|
|
2659
|
+
"aria-hidden": "true"
|
|
2167
2660
|
})), /*#__PURE__*/React.createElement("div", {
|
|
2168
2661
|
style: {
|
|
2169
2662
|
width: '1px',
|
|
@@ -2460,11 +2953,26 @@ function RichTextEditor({
|
|
|
2460
2953
|
}
|
|
2461
2954
|
return null;
|
|
2462
2955
|
})()), /*#__PURE__*/React.createElement("div", {
|
|
2463
|
-
|
|
2956
|
+
ref: contentWrapperRef,
|
|
2957
|
+
className: `rte-content-wrapper${areaHighlightMode ? " rte-area-highlight-mode" : ""}`,
|
|
2464
2958
|
style: {
|
|
2465
2959
|
position: 'relative'
|
|
2960
|
+
},
|
|
2961
|
+
onMouseDown: handleAreaSelectMouseDown
|
|
2962
|
+
}, areaHighlightMode && /*#__PURE__*/React.createElement("div", {
|
|
2963
|
+
className: "rte-area-highlight-hint",
|
|
2964
|
+
"aria-hidden": "true"
|
|
2965
|
+
}, "Drag to select an area, then release to apply highlight"), marqueePreview && /*#__PURE__*/React.createElement("div", {
|
|
2966
|
+
className: "rte-marquee-preview",
|
|
2967
|
+
style: {
|
|
2968
|
+
left: marqueePreview.left,
|
|
2969
|
+
top: marqueePreview.top,
|
|
2970
|
+
width: marqueePreview.width,
|
|
2971
|
+
height: marqueePreview.height,
|
|
2972
|
+
backgroundColor: `${bgColor}55`,
|
|
2973
|
+
borderColor: bgColor
|
|
2466
2974
|
}
|
|
2467
|
-
}, /*#__PURE__*/React.createElement("div", {
|
|
2975
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
2468
2976
|
ref: editorRef,
|
|
2469
2977
|
contentEditable: editable && disabled !== true,
|
|
2470
2978
|
suppressContentEditableWarning: true,
|
|
@@ -2477,6 +2985,11 @@ function RichTextEditor({
|
|
|
2477
2985
|
onDragStart: e => e.preventDefault(),
|
|
2478
2986
|
onDragOver: e => e.preventDefault(),
|
|
2479
2987
|
onKeyDown: handleKeyDown,
|
|
2988
|
+
onKeyUp: handleKeyUp,
|
|
2989
|
+
onMouseUp: () => {
|
|
2990
|
+
saveSelection();
|
|
2991
|
+
setSelectionVersion(v => v + 1);
|
|
2992
|
+
},
|
|
2480
2993
|
onClick: handleEditorClick,
|
|
2481
2994
|
onFocus: handleEditorFocus,
|
|
2482
2995
|
onBlur: handleEditorBlur,
|
|
@@ -2492,7 +3005,7 @@ function RichTextEditor({
|
|
|
2492
3005
|
left: paddingLeft || '12px'
|
|
2493
3006
|
},
|
|
2494
3007
|
"aria-hidden": "true"
|
|
2495
|
-
}, placeholder)
|
|
3008
|
+
}, placeholder), renderMediaToolbar()), /*#__PURE__*/React.createElement("div", {
|
|
2496
3009
|
className: "rte-footer"
|
|
2497
3010
|
}, /*#__PURE__*/React.createElement("div", {
|
|
2498
3011
|
className: "rte-footer-content"
|