react-lite-rich-text-editor 1.1.8 → 1.1.10
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 +787 -30
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +787 -30
- 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-bg-color-swatch-none{background-color:#fff;background-image:linear-gradient(45deg,#d1d5db 25%,transparent 0,transparent 75%,#d1d5db 0),linear-gradient(45deg,#d1d5db 25%,transparent 0,transparent 75%,#d1d5db 0);background-position:0 0,4px 4px;background-size:8px 8px}.rte-bg-color-clear{position:relative}.rte-bg-color-clear-icon{align-items:center;display:inline-flex;font-size:11px;font-weight:700;height:14px;justify-content:center;line-height:1;position:relative;width:14px}.rte-bg-color-clear-icon:after{background:currentColor;border-radius:1px;content:\"\";height:2px;left:-1px;position:absolute;right:-1px;top:50%;transform:rotate(-35deg)}.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;max-width:100%}.rte-area-highlight-block>blockquote,.rte-area-highlight-block>div,.rte-area-highlight-block>h1,.rte-area-highlight-block>h2,.rte-area-highlight-block>h3,.rte-area-highlight-block>h4,.rte-area-highlight-block>h5,.rte-area-highlight-block>h6,.rte-area-highlight-block>p{margin-bottom:0;margin-top:0}.rte-area-highlight-block>*+*{margin-top:.25em}.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,220 @@ const escapeAttr = str => escapeHtml(str).replace(/"/g, """);
|
|
|
421
421
|
|
|
422
422
|
// URL detection regex
|
|
423
423
|
const URL_REGEX = /(https?:\/\/[^\s]+)/g;
|
|
424
|
+
const TRANSPARENT_BACKGROUNDS = new Set(["", "transparent", "initial", "inherit", "rgba(0, 0, 0, 0)", "rgb(0, 0, 0, 0)"]);
|
|
425
|
+
const isTransparentBackground = value => {
|
|
426
|
+
if (!value) return true;
|
|
427
|
+
const normalized = value.trim().toLowerCase();
|
|
428
|
+
if (TRANSPARENT_BACKGROUNDS.has(normalized)) return true;
|
|
429
|
+
const match = normalized.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/);
|
|
430
|
+
if ((match === null || match === void 0 ? void 0 : match[4]) !== undefined && parseFloat(match[4]) === 0) return true;
|
|
431
|
+
return false;
|
|
432
|
+
};
|
|
433
|
+
const unwrapNode = element => {
|
|
434
|
+
const parent = element.parentNode;
|
|
435
|
+
if (!parent) return;
|
|
436
|
+
while (element.firstChild) {
|
|
437
|
+
parent.insertBefore(element.firstChild, element);
|
|
438
|
+
}
|
|
439
|
+
parent.removeChild(element);
|
|
440
|
+
};
|
|
441
|
+
const unwrapAreaHighlightBlock = block => {
|
|
442
|
+
var _block$classList;
|
|
443
|
+
if (!(block !== null && block !== void 0 && (_block$classList = block.classList) !== null && _block$classList !== void 0 && _block$classList.contains("rte-area-highlight-block"))) return;
|
|
444
|
+
const parent = block.parentNode;
|
|
445
|
+
if (!parent) return;
|
|
446
|
+
const fragment = document.createDocumentFragment();
|
|
447
|
+
while (block.firstChild) {
|
|
448
|
+
fragment.appendChild(block.firstChild);
|
|
449
|
+
}
|
|
450
|
+
parent.insertBefore(fragment, block);
|
|
451
|
+
parent.removeChild(block);
|
|
452
|
+
};
|
|
453
|
+
const stripInlineBackground = element => {
|
|
454
|
+
if (!(element instanceof HTMLElement)) return;
|
|
455
|
+
if (element.classList.contains("rte-block-highlight")) {
|
|
456
|
+
element.classList.remove("rte-block-highlight");
|
|
457
|
+
}
|
|
458
|
+
if (element.tagName === "MARK") {
|
|
459
|
+
unwrapNode(element);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const hadBackground = element.style.backgroundColor && !isTransparentBackground(element.style.backgroundColor) || element.style.background && !isTransparentBackground(element.style.background);
|
|
463
|
+
if (!hadBackground) return;
|
|
464
|
+
element.style.backgroundColor = "";
|
|
465
|
+
element.style.background = "";
|
|
466
|
+
if (!element.getAttribute("style")) {
|
|
467
|
+
element.removeAttribute("style");
|
|
468
|
+
}
|
|
469
|
+
const tag = element.tagName;
|
|
470
|
+
if ((tag === "SPAN" || tag === "FONT") && !element.className && element.attributes.length === 0) {
|
|
471
|
+
unwrapNode(element);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
const collectElementsInRange = (range, root) => {
|
|
475
|
+
const elements = [];
|
|
476
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
477
|
+
let node = walker.currentNode;
|
|
478
|
+
while (node) {
|
|
479
|
+
if (range.intersectsNode(node)) {
|
|
480
|
+
elements.push(node);
|
|
481
|
+
}
|
|
482
|
+
node = walker.nextNode();
|
|
483
|
+
}
|
|
484
|
+
return elements;
|
|
485
|
+
};
|
|
486
|
+
const elementHasRemovableBackground = element => {
|
|
487
|
+
var _element$style, _element$style2;
|
|
488
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
489
|
+
if (element.classList.contains("rte-area-highlight-block")) return true;
|
|
490
|
+
if (element.classList.contains("rte-block-highlight")) return true;
|
|
491
|
+
if (element.tagName === "MARK") return true;
|
|
492
|
+
if ((_element$style = element.style) !== null && _element$style !== void 0 && _element$style.backgroundColor && !isTransparentBackground(element.style.backgroundColor)) {
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
if ((_element$style2 = element.style) !== null && _element$style2 !== void 0 && _element$style2.background && !isTransparentBackground(element.style.background)) {
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
return false;
|
|
499
|
+
};
|
|
500
|
+
const rectsIntersect = (a, b) => !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom);
|
|
501
|
+
const caretRangeFromClientPoint = (x, y) => {
|
|
502
|
+
var _document$caretPositi, _document;
|
|
503
|
+
if (document.caretRangeFromPoint) {
|
|
504
|
+
return document.caretRangeFromPoint(x, y);
|
|
505
|
+
}
|
|
506
|
+
const position = (_document$caretPositi = (_document = document).caretPositionFromPoint) === null || _document$caretPositi === void 0 ? void 0 : _document$caretPositi.call(_document, x, y);
|
|
507
|
+
if (!position) return null;
|
|
508
|
+
const range = document.createRange();
|
|
509
|
+
range.setStart(position.offsetNode, position.offset);
|
|
510
|
+
range.collapse(true);
|
|
511
|
+
return range;
|
|
512
|
+
};
|
|
513
|
+
const clipRectToBounds = (rect, bounds) => {
|
|
514
|
+
const left = Math.max(rect.left, bounds.left);
|
|
515
|
+
const top = Math.max(rect.top, bounds.top);
|
|
516
|
+
const right = Math.min(rect.right, bounds.right);
|
|
517
|
+
const bottom = Math.min(rect.bottom, bounds.bottom);
|
|
518
|
+
return {
|
|
519
|
+
left,
|
|
520
|
+
top,
|
|
521
|
+
right,
|
|
522
|
+
bottom,
|
|
523
|
+
width: Math.max(0, right - left),
|
|
524
|
+
height: Math.max(0, bottom - top)
|
|
525
|
+
};
|
|
526
|
+
};
|
|
527
|
+
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)";
|
|
528
|
+
const getIntersectingBlocks = (editor, clientRect) => {
|
|
529
|
+
const candidates = [...editor.querySelectorAll(BLOCK_SELECTOR)].filter(el => {
|
|
530
|
+
if (el === editor || !editor.contains(el)) return false;
|
|
531
|
+
if (el.closest(".rte-area-highlight-block, .image-container, .video-container")) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
return rectsIntersect(el.getBoundingClientRect(), clientRect);
|
|
535
|
+
});
|
|
536
|
+
return candidates.filter(el => !candidates.some(other => other !== el && other.contains(el)));
|
|
537
|
+
};
|
|
538
|
+
const isHighlightableBlock = block => {
|
|
539
|
+
const text = (block.textContent || "").replace(/[\u00A0\u200B\s]/g, "");
|
|
540
|
+
if (text.length > 0) return true;
|
|
541
|
+
return !!block.querySelector("img, table, iframe, .image-container, .video-container, svg");
|
|
542
|
+
};
|
|
543
|
+
const createAreaHighlightWrapper = (color, textAlign, clipped, editorRect) => {
|
|
544
|
+
const widthPx = Math.max(40, Math.round(clipped.width));
|
|
545
|
+
const heightPx = Math.max(24, Math.round(clipped.height));
|
|
546
|
+
const region = document.createElement("div");
|
|
547
|
+
region.className = "rte-area-highlight-block";
|
|
548
|
+
region.style.backgroundColor = color;
|
|
549
|
+
region.style.display = "block";
|
|
550
|
+
region.style.width = `${widthPx}px`;
|
|
551
|
+
region.style.minHeight = `${heightPx}px`;
|
|
552
|
+
region.style.maxWidth = "100%";
|
|
553
|
+
region.style.padding = "6px 10px";
|
|
554
|
+
region.style.borderRadius = "4px";
|
|
555
|
+
region.style.margin = "4px 0";
|
|
556
|
+
region.style.boxSizing = "border-box";
|
|
557
|
+
if (textAlign === "center") {
|
|
558
|
+
region.style.marginLeft = "auto";
|
|
559
|
+
region.style.marginRight = "auto";
|
|
560
|
+
} else if (textAlign === "right" || textAlign === "end") {
|
|
561
|
+
region.style.marginLeft = "auto";
|
|
562
|
+
region.style.marginRight = "0";
|
|
563
|
+
} else {
|
|
564
|
+
const leftOffset = Math.max(0, Math.round(clipped.left - editorRect.left));
|
|
565
|
+
region.style.marginLeft = `${leftOffset}px`;
|
|
566
|
+
region.style.marginRight = "auto";
|
|
567
|
+
}
|
|
568
|
+
return region;
|
|
569
|
+
};
|
|
570
|
+
const insertAreaHighlightRegion = (editor, clientRect, color) => {
|
|
571
|
+
var _alignNode;
|
|
572
|
+
const editorRect = editor.getBoundingClientRect();
|
|
573
|
+
const clipped = clipRectToBounds(clientRect, editorRect);
|
|
574
|
+
const blocks = getIntersectingBlocks(editor, clipped).filter(isHighlightableBlock);
|
|
575
|
+
if (blocks.length > 0) {
|
|
576
|
+
const first = blocks[0];
|
|
577
|
+
const textAlign = window.getComputedStyle(first).textAlign;
|
|
578
|
+
const region = createAreaHighlightWrapper(color, textAlign, clipped, editorRect);
|
|
579
|
+
let totalBlockHeight = 0;
|
|
580
|
+
blocks.forEach(block => {
|
|
581
|
+
totalBlockHeight += block.getBoundingClientRect().height;
|
|
582
|
+
});
|
|
583
|
+
const targetHeight = Math.max(24, Math.round(clipped.height));
|
|
584
|
+
const innerPadding = 12;
|
|
585
|
+
const extraVertical = Math.max(0, targetHeight - totalBlockHeight - innerPadding);
|
|
586
|
+
if (extraVertical > 0) {
|
|
587
|
+
const pad = Math.round(extraVertical / 2);
|
|
588
|
+
region.style.paddingTop = `${6 + pad}px`;
|
|
589
|
+
region.style.paddingBottom = `${6 + pad}px`;
|
|
590
|
+
}
|
|
591
|
+
if (first.parentNode) {
|
|
592
|
+
first.parentNode.insertBefore(region, first);
|
|
593
|
+
} else {
|
|
594
|
+
editor.appendChild(region);
|
|
595
|
+
}
|
|
596
|
+
blocks.forEach(block => {
|
|
597
|
+
region.appendChild(block);
|
|
598
|
+
});
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
const insertRange = caretRangeFromClientPoint(clipped.left + clipped.width / 2, clipped.top + Math.min(8, clipped.height / 2)) || caretRangeFromClientPoint(clipped.left + 4, clipped.top + 4);
|
|
602
|
+
let alignNode = insertRange === null || insertRange === void 0 ? void 0 : insertRange.startContainer;
|
|
603
|
+
if (((_alignNode = alignNode) === null || _alignNode === void 0 ? void 0 : _alignNode.nodeType) === 3) alignNode = alignNode.parentNode;
|
|
604
|
+
const textAlign = alignNode instanceof HTMLElement ? window.getComputedStyle(alignNode).textAlign : "left";
|
|
605
|
+
const region = createAreaHighlightWrapper(color, textAlign, clipped, editorRect);
|
|
606
|
+
region.innerHTML = " ";
|
|
607
|
+
if (insertRange && editor.contains(insertRange.startContainer)) {
|
|
608
|
+
insertRange.collapse(true);
|
|
609
|
+
let node = insertRange.startContainer;
|
|
610
|
+
if (node.nodeType === 3) node = node.parentNode;
|
|
611
|
+
while (node && node !== editor && node.parentNode !== editor) {
|
|
612
|
+
node = node.parentNode;
|
|
613
|
+
}
|
|
614
|
+
if (node && node !== editor && editor.contains(node)) {
|
|
615
|
+
node.parentNode.insertBefore(region, node);
|
|
616
|
+
} else {
|
|
617
|
+
editor.appendChild(region);
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
editor.appendChild(region);
|
|
621
|
+
}
|
|
622
|
+
return true;
|
|
623
|
+
};
|
|
624
|
+
const getClientRectFromDrag = (startX, startY, currentX, currentY) => {
|
|
625
|
+
const left = Math.min(startX, currentX);
|
|
626
|
+
const top = Math.min(startY, currentY);
|
|
627
|
+
const right = Math.max(startX, currentX);
|
|
628
|
+
const bottom = Math.max(startY, currentY);
|
|
629
|
+
return {
|
|
630
|
+
left,
|
|
631
|
+
top,
|
|
632
|
+
right,
|
|
633
|
+
bottom,
|
|
634
|
+
width: right - left,
|
|
635
|
+
height: bottom - top
|
|
636
|
+
};
|
|
637
|
+
};
|
|
424
638
|
function RichTextEditor({
|
|
425
639
|
onChange,
|
|
426
640
|
showEditButton,
|
|
@@ -439,8 +653,11 @@ function RichTextEditor({
|
|
|
439
653
|
onImageUpload
|
|
440
654
|
}) {
|
|
441
655
|
const editorRef = useRef(null);
|
|
656
|
+
const contentWrapperRef = useRef(null);
|
|
442
657
|
const fileInputRef = useRef(null);
|
|
443
658
|
const scrollTopRef = useRef(0);
|
|
659
|
+
const areaDragRef = useRef(null);
|
|
660
|
+
const bgColorRef = useRef("#ffff00");
|
|
444
661
|
const [html, setHtml] = useState("");
|
|
445
662
|
const [linkModalOpen, setLinkModalOpen] = useState(false);
|
|
446
663
|
const [linkUrl, setLinkUrl] = useState("");
|
|
@@ -481,11 +698,15 @@ function RichTextEditor({
|
|
|
481
698
|
const [tableCols, setTableCols] = useState(3);
|
|
482
699
|
const [selectionVersion, setSelectionVersion] = useState(0);
|
|
483
700
|
const [selectedMedia, setSelectedMedia] = useState(null);
|
|
701
|
+
const [mediaWidthInput, setMediaWidthInput] = useState("100");
|
|
702
|
+
const [mediaWidthUnit, setMediaWidthUnit] = useState("%");
|
|
484
703
|
const [metrics, setMetrics] = useState({
|
|
485
704
|
words: 0,
|
|
486
705
|
chars: 0
|
|
487
706
|
});
|
|
488
707
|
const [isEmpty, setIsEmpty] = useState(!value);
|
|
708
|
+
const [areaHighlightMode, setAreaHighlightMode] = useState(false);
|
|
709
|
+
const [marqueePreview, setMarqueePreview] = useState(null);
|
|
489
710
|
const updateMetrics = useCallback(() => {
|
|
490
711
|
if (!editorRef.current) return;
|
|
491
712
|
// Calculate metrics immediately but outside of render path
|
|
@@ -522,6 +743,10 @@ function RichTextEditor({
|
|
|
522
743
|
selectionRangeRef.current = sel.getRangeAt(0).cloneRange();
|
|
523
744
|
}
|
|
524
745
|
};
|
|
746
|
+
const handleKeyUp = () => {
|
|
747
|
+
saveSelection();
|
|
748
|
+
setSelectionVersion(v => v + 1);
|
|
749
|
+
};
|
|
525
750
|
const handleZoomIn = () => {
|
|
526
751
|
setZoomLevel(prevZoom => prevZoom + 0.1);
|
|
527
752
|
};
|
|
@@ -732,6 +957,42 @@ function RichTextEditor({
|
|
|
732
957
|
const computedColor = window.getComputedStyle(sel.anchorNode.nodeType === 3 ? sel.anchorNode.parentNode : sel.anchorNode).color;
|
|
733
958
|
return rgbToHex(computedColor);
|
|
734
959
|
};
|
|
960
|
+
const getBackgroundColorAtCursor = () => {
|
|
961
|
+
const sel = window.getSelection();
|
|
962
|
+
if (!sel || !sel.rangeCount || !editorRef.current) return null;
|
|
963
|
+
let node = sel.anchorNode;
|
|
964
|
+
if (node.nodeType === 3) node = node.parentNode;
|
|
965
|
+
while (node && node !== editorRef.current) {
|
|
966
|
+
if (node.nodeType === 1) {
|
|
967
|
+
var _node$classList, _node$classList2, _node$style;
|
|
968
|
+
if ((_node$classList = node.classList) !== null && _node$classList !== void 0 && _node$classList.contains("rte-area-highlight-block")) {
|
|
969
|
+
const bg = node.style.backgroundColor;
|
|
970
|
+
return bg && !isTransparentBackground(bg) ? rgbToHex(bg) : null;
|
|
971
|
+
}
|
|
972
|
+
if ((_node$classList2 = node.classList) !== null && _node$classList2 !== void 0 && _node$classList2.contains("rte-block-highlight")) {
|
|
973
|
+
const bg = node.style.backgroundColor;
|
|
974
|
+
return bg && !isTransparentBackground(bg) ? rgbToHex(bg) : null;
|
|
975
|
+
}
|
|
976
|
+
const inlineBg = (_node$style = node.style) === null || _node$style === void 0 ? void 0 : _node$style.backgroundColor;
|
|
977
|
+
if (inlineBg && !isTransparentBackground(inlineBg)) {
|
|
978
|
+
return rgbToHex(inlineBg);
|
|
979
|
+
}
|
|
980
|
+
if (node.tagName === "MARK") {
|
|
981
|
+
const markBg = window.getComputedStyle(node).backgroundColor;
|
|
982
|
+
return markBg && !isTransparentBackground(markBg) ? rgbToHex(markBg) : null;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
node = node.parentNode;
|
|
986
|
+
}
|
|
987
|
+
return null;
|
|
988
|
+
};
|
|
989
|
+
const syncBackgroundColorState = () => {
|
|
990
|
+
const activeBg = getBackgroundColorAtCursor();
|
|
991
|
+
setHasActiveTextBackground(!!activeBg);
|
|
992
|
+
if (activeBg) {
|
|
993
|
+
setBgColor(activeBg);
|
|
994
|
+
}
|
|
995
|
+
};
|
|
735
996
|
const stripEditorChrome = root => {
|
|
736
997
|
root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handle").forEach(element => element.remove());
|
|
737
998
|
root.querySelectorAll(".rte-media-selected").forEach(element => {
|
|
@@ -771,9 +1032,31 @@ function RichTextEditor({
|
|
|
771
1032
|
}
|
|
772
1033
|
return 100;
|
|
773
1034
|
};
|
|
1035
|
+
const getMediaWidthPx = container => {
|
|
1036
|
+
if (!container) return 0;
|
|
1037
|
+
if (container.dataset.widthPx) {
|
|
1038
|
+
return Number(container.dataset.widthPx);
|
|
1039
|
+
}
|
|
1040
|
+
const width = container.style.width || "";
|
|
1041
|
+
if (width.endsWith("px")) {
|
|
1042
|
+
return parseInt(width, 10) || 0;
|
|
1043
|
+
}
|
|
1044
|
+
return Math.round(container.getBoundingClientRect().width);
|
|
1045
|
+
};
|
|
1046
|
+
const syncMediaWidthControls = container => {
|
|
1047
|
+
if (!container) return;
|
|
1048
|
+
if (container.dataset.widthPx) {
|
|
1049
|
+
setMediaWidthInput(String(getMediaWidthPx(container)));
|
|
1050
|
+
setMediaWidthUnit("px");
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
setMediaWidthInput(String(getMediaWidthPercent(container)));
|
|
1054
|
+
setMediaWidthUnit("%");
|
|
1055
|
+
};
|
|
774
1056
|
const applyMediaWidthPercent = (container, percent) => {
|
|
775
1057
|
if (!container) return;
|
|
776
|
-
const clamped = Math.max(
|
|
1058
|
+
const clamped = Math.max(10, Math.min(100, Math.round(percent)));
|
|
1059
|
+
delete container.dataset.widthPx;
|
|
777
1060
|
container.dataset.widthPercent = String(clamped);
|
|
778
1061
|
container.style.width = `${clamped}%`;
|
|
779
1062
|
container.style.maxWidth = "100%";
|
|
@@ -794,8 +1077,36 @@ function RichTextEditor({
|
|
|
794
1077
|
img.style.objectFit = "";
|
|
795
1078
|
}
|
|
796
1079
|
};
|
|
1080
|
+
const applyMediaWidthPx = (container, px) => {
|
|
1081
|
+
if (!container) return;
|
|
1082
|
+
const clamped = Math.max(20, Math.min(2000, Math.round(px)));
|
|
1083
|
+
delete container.dataset.widthPercent;
|
|
1084
|
+
container.dataset.widthPx = String(clamped);
|
|
1085
|
+
container.style.width = `${clamped}px`;
|
|
1086
|
+
container.style.maxWidth = "100%";
|
|
1087
|
+
container.style.marginLeft = "";
|
|
1088
|
+
container.style.marginTop = "";
|
|
1089
|
+
if (container.classList.contains("video-container")) {
|
|
1090
|
+
container.style.height = "0";
|
|
1091
|
+
container.style.paddingBottom = "56.25%";
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const frame = getImageMediaTarget(container);
|
|
1095
|
+
if (!frame) return;
|
|
1096
|
+
frame.style.width = "100%";
|
|
1097
|
+
frame.style.height = "";
|
|
1098
|
+
const img = frame.querySelector("img");
|
|
1099
|
+
if (img) {
|
|
1100
|
+
img.style.height = "auto";
|
|
1101
|
+
img.style.objectFit = "";
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
797
1104
|
const normalizeMediaWidth = container => {
|
|
798
1105
|
if (!container) return;
|
|
1106
|
+
if (container.dataset.widthPx) {
|
|
1107
|
+
applyMediaWidthPx(container, Number(container.dataset.widthPx));
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
799
1110
|
if (container.classList.contains("image-small")) {
|
|
800
1111
|
container.classList.remove("image-small");
|
|
801
1112
|
applyMediaWidthPercent(container, 50);
|
|
@@ -811,11 +1122,7 @@ function RichTextEditor({
|
|
|
811
1122
|
return;
|
|
812
1123
|
}
|
|
813
1124
|
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
|
-
}
|
|
1125
|
+
applyMediaWidthPx(container, parseFloat(width));
|
|
819
1126
|
return;
|
|
820
1127
|
}
|
|
821
1128
|
if (container.classList.contains("video-container")) {
|
|
@@ -858,12 +1165,18 @@ function RichTextEditor({
|
|
|
858
1165
|
const startWidth = container.getBoundingClientRect().width;
|
|
859
1166
|
const onMouseMove = moveEvent => {
|
|
860
1167
|
const nextWidth = Math.max(60, startWidth + (moveEvent.clientX - startX));
|
|
861
|
-
|
|
862
|
-
|
|
1168
|
+
if (container.dataset.widthPx) {
|
|
1169
|
+
applyMediaWidthPx(container, nextWidth);
|
|
1170
|
+
} else {
|
|
1171
|
+
const percent = Math.round(nextWidth / editorWidth * 100);
|
|
1172
|
+
applyMediaWidthPercent(container, percent);
|
|
1173
|
+
}
|
|
863
1174
|
};
|
|
864
1175
|
const onMouseUp = () => {
|
|
865
1176
|
document.removeEventListener("mousemove", onMouseMove);
|
|
866
1177
|
document.removeEventListener("mouseup", onMouseUp);
|
|
1178
|
+
syncMediaWidthControls(container);
|
|
1179
|
+
setSelectionVersion(v => v + 1);
|
|
867
1180
|
triggerChange();
|
|
868
1181
|
};
|
|
869
1182
|
document.addEventListener("mousemove", onMouseMove);
|
|
@@ -959,12 +1272,14 @@ function RichTextEditor({
|
|
|
959
1272
|
setIsUnderline(isParentStyle(container, "U", "underline"));
|
|
960
1273
|
const computedColor = window.getComputedStyle(container).color;
|
|
961
1274
|
setFontColor(rgbToHex(computedColor));
|
|
1275
|
+
syncBackgroundColorState();
|
|
962
1276
|
} else {
|
|
963
1277
|
setIsBold(document.queryCommandState("bold"));
|
|
964
1278
|
setIsItalic(document.queryCommandState("italic"));
|
|
965
1279
|
setIsUnderline(document.queryCommandState("underline"));
|
|
966
1280
|
const computedColor = window.getComputedStyle(container).color;
|
|
967
1281
|
setFontColor(rgbToHex(computedColor));
|
|
1282
|
+
syncBackgroundColorState();
|
|
968
1283
|
}
|
|
969
1284
|
|
|
970
1285
|
// 3. Current Font Size
|
|
@@ -990,11 +1305,202 @@ function RichTextEditor({
|
|
|
990
1305
|
focus();
|
|
991
1306
|
};
|
|
992
1307
|
const [fontColor, setFontColor] = useState("#000000");
|
|
1308
|
+
const [bgColor, setBgColor] = useState("#ffff00");
|
|
1309
|
+
const [hasActiveTextBackground, setHasActiveTextBackground] = useState(false);
|
|
1310
|
+
useEffect(() => {
|
|
1311
|
+
bgColorRef.current = bgColor;
|
|
1312
|
+
}, [bgColor]);
|
|
993
1313
|
const getActiveTextColor = () => getColorAtCursor() || fontColor;
|
|
994
|
-
const
|
|
1314
|
+
const restoreSavedSelection = () => {
|
|
1315
|
+
if (!selectionRangeRef.current) return;
|
|
1316
|
+
const sel = window.getSelection();
|
|
1317
|
+
if (!sel) return;
|
|
1318
|
+
sel.removeAllRanges();
|
|
1319
|
+
sel.addRange(selectionRangeRef.current);
|
|
1320
|
+
};
|
|
1321
|
+
const applyTextColor = color => {
|
|
995
1322
|
setFontColor(color);
|
|
996
|
-
|
|
1323
|
+
focus();
|
|
1324
|
+
restoreSavedSelection();
|
|
1325
|
+
document.execCommand("styleWithCSS", false, true);
|
|
1326
|
+
document.execCommand("foreColor", false, color);
|
|
1327
|
+
document.execCommand("styleWithCSS", false, false);
|
|
1328
|
+
triggerChange();
|
|
1329
|
+
};
|
|
1330
|
+
const applyBackgroundColor = color => {
|
|
1331
|
+
setBgColor(color);
|
|
1332
|
+
setHasActiveTextBackground(true);
|
|
1333
|
+
focus();
|
|
1334
|
+
restoreSavedSelection();
|
|
1335
|
+
document.execCommand("styleWithCSS", false, true);
|
|
1336
|
+
if (!document.execCommand("hiliteColor", false, color)) {
|
|
1337
|
+
document.execCommand("backColor", false, color);
|
|
1338
|
+
}
|
|
1339
|
+
document.execCommand("styleWithCSS", false, false);
|
|
1340
|
+
triggerChange();
|
|
1341
|
+
};
|
|
1342
|
+
const clearMediaHighlightStyles = editor => {
|
|
1343
|
+
editor.querySelectorAll(".image-container, .video-container, td, th").forEach(element => {
|
|
1344
|
+
if (!editor.contains(element)) return;
|
|
1345
|
+
if (!element.style.backgroundColor && !element.style.padding && !element.style.borderRadius) {
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
element.style.backgroundColor = "";
|
|
1349
|
+
element.style.padding = "";
|
|
1350
|
+
element.style.borderRadius = "";
|
|
1351
|
+
if (!element.getAttribute("style")) {
|
|
1352
|
+
element.removeAttribute("style");
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
};
|
|
1356
|
+
const removeBackgroundColor = () => {
|
|
1357
|
+
const editor = editorRef.current;
|
|
1358
|
+
if (!editor) return;
|
|
1359
|
+
focus();
|
|
1360
|
+
restoreSavedSelection();
|
|
1361
|
+
const sel = window.getSelection();
|
|
1362
|
+
const range = sel !== null && sel !== void 0 && sel.rangeCount ? sel.getRangeAt(0) : null;
|
|
1363
|
+
document.execCommand("styleWithCSS", false, true);
|
|
1364
|
+
document.execCommand("hiliteColor", false, "transparent");
|
|
1365
|
+
document.execCommand("backColor", false, "transparent");
|
|
1366
|
+
document.execCommand("styleWithCSS", false, false);
|
|
1367
|
+
const processElement = element => {
|
|
1368
|
+
var _element$classList, _element$classList2, _element$classList3;
|
|
1369
|
+
if (!editor.contains(element)) return;
|
|
1370
|
+
if ((_element$classList = element.classList) !== null && _element$classList !== void 0 && _element$classList.contains("rte-area-highlight-block")) {
|
|
1371
|
+
unwrapAreaHighlightBlock(element);
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
if ((_element$classList2 = element.classList) !== null && _element$classList2 !== void 0 && _element$classList2.contains("image-container") || (_element$classList3 = element.classList) !== null && _element$classList3 !== void 0 && _element$classList3.contains("video-container")) {
|
|
1375
|
+
element.style.backgroundColor = "";
|
|
1376
|
+
element.style.padding = "";
|
|
1377
|
+
element.style.borderRadius = "";
|
|
1378
|
+
if (!element.getAttribute("style")) {
|
|
1379
|
+
element.removeAttribute("style");
|
|
1380
|
+
}
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
stripInlineBackground(element);
|
|
1384
|
+
};
|
|
1385
|
+
if (range && editor.contains(range.commonAncestorContainer)) {
|
|
1386
|
+
const elements = collectElementsInRange(range, editor).filter(elementHasRemovableBackground).reverse();
|
|
1387
|
+
elements.forEach(processElement);
|
|
1388
|
+
if (range.collapsed) {
|
|
1389
|
+
let node = range.startContainer;
|
|
1390
|
+
if (node.nodeType === 3) node = node.parentNode;
|
|
1391
|
+
while (node && node !== editor) {
|
|
1392
|
+
if (elementHasRemovableBackground(node)) {
|
|
1393
|
+
processElement(node);
|
|
1394
|
+
break;
|
|
1395
|
+
}
|
|
1396
|
+
node = node.parentNode;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
} else {
|
|
1400
|
+
[...editor.querySelectorAll(".rte-area-highlight-block")].forEach(unwrapAreaHighlightBlock);
|
|
1401
|
+
[...editor.querySelectorAll("mark")].forEach(mark => unwrapNode(mark));
|
|
1402
|
+
[...editor.querySelectorAll("[style*='background']")].forEach(element => {
|
|
1403
|
+
if (editor.contains(element)) {
|
|
1404
|
+
stripInlineBackground(element);
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
clearMediaHighlightStyles(editor);
|
|
1409
|
+
editor.querySelectorAll("span[style], font[style]").forEach(element => {
|
|
1410
|
+
if (!editor.contains(element)) return;
|
|
1411
|
+
if (!element.style.backgroundColor && !element.style.background) {
|
|
1412
|
+
if (!element.getAttribute("style")) {
|
|
1413
|
+
unwrapNode(element);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
setHasActiveTextBackground(false);
|
|
1418
|
+
triggerChange();
|
|
1419
|
+
focus();
|
|
997
1420
|
};
|
|
1421
|
+
const applyMarqueeHighlight = useCallback((clientRect, color) => {
|
|
1422
|
+
const editor = editorRef.current;
|
|
1423
|
+
if (!editor || clientRect.width < 10 || clientRect.height < 10) {
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
editor.focus();
|
|
1427
|
+
const editorRect = editor.getBoundingClientRect();
|
|
1428
|
+
const clippedRect = clipRectToBounds(clientRect, editorRect);
|
|
1429
|
+
if (clippedRect.width < 10 || clippedRect.height < 10) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
insertAreaHighlightRegion(editor, clippedRect, color);
|
|
1433
|
+
setAreaHighlightMode(false);
|
|
1434
|
+
setMarqueePreview(null);
|
|
1435
|
+
areaDragRef.current = null;
|
|
1436
|
+
editor.querySelectorAll("td, th").forEach(cell => {
|
|
1437
|
+
if (!editor.contains(cell)) return;
|
|
1438
|
+
if (cell.closest(".rte-area-highlight-block")) return;
|
|
1439
|
+
if (rectsIntersect(cell.getBoundingClientRect(), clippedRect)) {
|
|
1440
|
+
cell.style.backgroundColor = color;
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
editor.querySelectorAll(".image-container, .video-container").forEach(media => {
|
|
1444
|
+
if (!editor.contains(media)) return;
|
|
1445
|
+
if (rectsIntersect(media.getBoundingClientRect(), clippedRect)) {
|
|
1446
|
+
media.style.backgroundColor = color;
|
|
1447
|
+
media.style.padding = "4px";
|
|
1448
|
+
media.style.borderRadius = "4px";
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
updateMetrics();
|
|
1452
|
+
triggerChange();
|
|
1453
|
+
focus();
|
|
1454
|
+
}, [triggerChange, updateMetrics]);
|
|
1455
|
+
const handleAreaSelectMouseDown = useCallback(e => {
|
|
1456
|
+
if (!areaHighlightMode || !editable || disabled) return;
|
|
1457
|
+
if (e.button !== 0) return;
|
|
1458
|
+
if (e.target.closest(".rte-toolbar, .rte-media-toolbar, .rte-modal-overlay")) return;
|
|
1459
|
+
e.preventDefault();
|
|
1460
|
+
e.stopPropagation();
|
|
1461
|
+
const wrapper = contentWrapperRef.current;
|
|
1462
|
+
if (!wrapper) return;
|
|
1463
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
1464
|
+
areaDragRef.current = {
|
|
1465
|
+
startX: e.clientX,
|
|
1466
|
+
startY: e.clientY,
|
|
1467
|
+
currentX: e.clientX,
|
|
1468
|
+
currentY: e.clientY,
|
|
1469
|
+
wrapperRect
|
|
1470
|
+
};
|
|
1471
|
+
const updatePreview = (clientX, clientY) => {
|
|
1472
|
+
const drag = areaDragRef.current;
|
|
1473
|
+
if (!drag) return;
|
|
1474
|
+
drag.currentX = clientX;
|
|
1475
|
+
drag.currentY = clientY;
|
|
1476
|
+
const rect = getClientRectFromDrag(drag.startX, drag.startY, drag.currentX, drag.currentY);
|
|
1477
|
+
setMarqueePreview({
|
|
1478
|
+
left: rect.left - drag.wrapperRect.left,
|
|
1479
|
+
top: rect.top - drag.wrapperRect.top,
|
|
1480
|
+
width: rect.width,
|
|
1481
|
+
height: rect.height
|
|
1482
|
+
});
|
|
1483
|
+
};
|
|
1484
|
+
const onMove = ev => {
|
|
1485
|
+
ev.preventDefault();
|
|
1486
|
+
updatePreview(ev.clientX, ev.clientY);
|
|
1487
|
+
};
|
|
1488
|
+
const onUp = ev => {
|
|
1489
|
+
document.removeEventListener("mousemove", onMove);
|
|
1490
|
+
document.removeEventListener("mouseup", onUp);
|
|
1491
|
+
const drag = areaDragRef.current;
|
|
1492
|
+
areaDragRef.current = null;
|
|
1493
|
+
setMarqueePreview(null);
|
|
1494
|
+
if (!drag) return;
|
|
1495
|
+
const clientRect = getClientRectFromDrag(drag.startX, drag.startY, ev.clientX, ev.clientY);
|
|
1496
|
+
requestAnimationFrame(() => {
|
|
1497
|
+
applyMarqueeHighlight(clientRect, bgColorRef.current);
|
|
1498
|
+
});
|
|
1499
|
+
};
|
|
1500
|
+
document.addEventListener("mousemove", onMove);
|
|
1501
|
+
document.addEventListener("mouseup", onUp);
|
|
1502
|
+
updatePreview(e.clientX, e.clientY);
|
|
1503
|
+
}, [areaHighlightMode, editable, disabled, applyMarqueeHighlight]);
|
|
998
1504
|
const addLink = () => {
|
|
999
1505
|
const sel = window.getSelection();
|
|
1000
1506
|
if (!sel || sel.rangeCount === 0) return;
|
|
@@ -1248,9 +1754,25 @@ function RichTextEditor({
|
|
|
1248
1754
|
if (frame && !frame.querySelector(".image-delete-button")) {
|
|
1249
1755
|
frame.appendChild(createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1250
1756
|
existingWrapper.remove();
|
|
1757
|
+
clearMediaSelection();
|
|
1251
1758
|
triggerChange && triggerChange();
|
|
1252
1759
|
}));
|
|
1253
1760
|
}
|
|
1761
|
+
if (!existingWrapper.dataset.mediaEnhanced) {
|
|
1762
|
+
existingWrapper.dataset.mediaEnhanced = "true";
|
|
1763
|
+
existingWrapper.addEventListener("click", event => {
|
|
1764
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1765
|
+
event.preventDefault();
|
|
1766
|
+
event.stopPropagation();
|
|
1767
|
+
selectMediaContainer(existingWrapper);
|
|
1768
|
+
});
|
|
1769
|
+
img.addEventListener("dblclick", event => {
|
|
1770
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1771
|
+
event.preventDefault();
|
|
1772
|
+
event.stopPropagation();
|
|
1773
|
+
openImageModal(img.src);
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1254
1776
|
attachMediaResizeHandle(existingWrapper);
|
|
1255
1777
|
normalizeMediaWidth(existingWrapper);
|
|
1256
1778
|
updateMediaControlVisibility(existingWrapper);
|
|
@@ -1328,14 +1850,33 @@ function RichTextEditor({
|
|
|
1328
1850
|
event.stopPropagation();
|
|
1329
1851
|
selectMediaContainer(container);
|
|
1330
1852
|
});
|
|
1853
|
+
container.style.cursor = editable ? "pointer" : "default";
|
|
1854
|
+
container.dataset.mediaEnhanced = "true";
|
|
1855
|
+
container.addEventListener("click", event => {
|
|
1856
|
+
if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
|
|
1857
|
+
event.preventDefault();
|
|
1858
|
+
event.stopPropagation();
|
|
1859
|
+
selectMediaContainer(container);
|
|
1860
|
+
});
|
|
1861
|
+
const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
|
|
1862
|
+
container.remove();
|
|
1863
|
+
clearMediaSelection();
|
|
1864
|
+
triggerChange();
|
|
1865
|
+
});
|
|
1331
1866
|
frame.appendChild(img);
|
|
1867
|
+
frame.appendChild(deleteBtn);
|
|
1332
1868
|
container.appendChild(frame);
|
|
1333
|
-
|
|
1869
|
+
attachMediaResizeHandle(container);
|
|
1870
|
+
applyMediaWidthPercent(container, 50);
|
|
1334
1871
|
|
|
1335
1872
|
// Insert at cursor position
|
|
1336
1873
|
insertNodeAtCursor(container);
|
|
1337
1874
|
requestAnimationFrame(() => {
|
|
1875
|
+
var _editorRef$current5;
|
|
1338
1876
|
processExistingMedia(editorRef.current);
|
|
1877
|
+
selectMediaContainer(container);
|
|
1878
|
+
syncMediaWidthControls(container);
|
|
1879
|
+
(_editorRef$current5 = editorRef.current) === null || _editorRef$current5 === void 0 || _editorRef$current5.focus();
|
|
1339
1880
|
triggerChange();
|
|
1340
1881
|
});
|
|
1341
1882
|
}
|
|
@@ -1503,6 +2044,28 @@ function RichTextEditor({
|
|
|
1503
2044
|
selection.addRange(newRange);
|
|
1504
2045
|
};
|
|
1505
2046
|
const handleKeyDown = useCallback(e => {
|
|
2047
|
+
var _editorRef$current6;
|
|
2048
|
+
if (e.key === "Escape") {
|
|
2049
|
+
if (areaHighlightMode) {
|
|
2050
|
+
e.preventDefault();
|
|
2051
|
+
areaDragRef.current = null;
|
|
2052
|
+
setMarqueePreview(null);
|
|
2053
|
+
setAreaHighlightMode(false);
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (selectedMedia) {
|
|
2057
|
+
e.preventDefault();
|
|
2058
|
+
clearMediaSelection();
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
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)) {
|
|
2063
|
+
e.preventDefault();
|
|
2064
|
+
selectedMedia.remove();
|
|
2065
|
+
clearMediaSelection();
|
|
2066
|
+
triggerChange();
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
1506
2069
|
if (applyMarkdownShortcut(e)) return;
|
|
1507
2070
|
|
|
1508
2071
|
// Handle Enter key
|
|
@@ -1576,7 +2139,7 @@ function RichTextEditor({
|
|
|
1576
2139
|
return;
|
|
1577
2140
|
}
|
|
1578
2141
|
if (e.key === "Backspace") {
|
|
1579
|
-
var _node$closest, _node, _editorRef$
|
|
2142
|
+
var _node$closest, _node, _editorRef$current7;
|
|
1580
2143
|
const selection = window.getSelection();
|
|
1581
2144
|
if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
|
|
1582
2145
|
const range = selection.getRangeAt(0);
|
|
@@ -1586,7 +2149,7 @@ function RichTextEditor({
|
|
|
1586
2149
|
node = node.parentNode;
|
|
1587
2150
|
}
|
|
1588
2151
|
const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
|
|
1589
|
-
if (!listItem || !((_editorRef$
|
|
2152
|
+
if (!listItem || !((_editorRef$current7 = editorRef.current) !== null && _editorRef$current7 !== void 0 && _editorRef$current7.contains(listItem))) return;
|
|
1590
2153
|
const list = listItem.parentNode;
|
|
1591
2154
|
if (isListItemEffectivelyEmpty(listItem)) {
|
|
1592
2155
|
e.preventDefault();
|
|
@@ -1635,7 +2198,7 @@ function RichTextEditor({
|
|
|
1635
2198
|
e.preventDefault();
|
|
1636
2199
|
exec("underline");
|
|
1637
2200
|
}
|
|
1638
|
-
}, [exec, triggerChange, fontColor]);
|
|
2201
|
+
}, [exec, triggerChange, fontColor, areaHighlightMode, selectedMedia]);
|
|
1639
2202
|
const confirmLink = () => {
|
|
1640
2203
|
// Add protocol if missing
|
|
1641
2204
|
let url = linkUrl.trim();
|
|
@@ -1677,7 +2240,7 @@ function RichTextEditor({
|
|
|
1677
2240
|
setCurrentFontSize("16");
|
|
1678
2241
|
setCurrentLineHeight("");
|
|
1679
2242
|
setFontColor("#000000");
|
|
1680
|
-
|
|
2243
|
+
removeBackgroundColor();
|
|
1681
2244
|
focus();
|
|
1682
2245
|
};
|
|
1683
2246
|
const deleteTextBeforeCursorInBlock = (block, range, selection) => {
|
|
@@ -1903,7 +2466,11 @@ function RichTextEditor({
|
|
|
1903
2466
|
}
|
|
1904
2467
|
}, [disabled]);
|
|
1905
2468
|
const handleEditorClick = useCallback(e => {
|
|
1906
|
-
var _editorRef$
|
|
2469
|
+
var _editorRef$current8;
|
|
2470
|
+
if (areaHighlightMode) {
|
|
2471
|
+
e.preventDefault();
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
1907
2474
|
setSelectionVersion(v => v + 1);
|
|
1908
2475
|
const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
|
|
1909
2476
|
if (deleteBtn && editable && editorFocused) {
|
|
@@ -1918,7 +2485,10 @@ function RichTextEditor({
|
|
|
1918
2485
|
return;
|
|
1919
2486
|
}
|
|
1920
2487
|
const clickedMedia = e.target.closest('.image-container, .video-container');
|
|
1921
|
-
if (clickedMedia && (_editorRef$
|
|
2488
|
+
if (clickedMedia && (_editorRef$current8 = editorRef.current) !== null && _editorRef$current8 !== void 0 && _editorRef$current8.contains(clickedMedia) && editable) {
|
|
2489
|
+
e.preventDefault();
|
|
2490
|
+
e.stopPropagation();
|
|
2491
|
+
selectMediaContainer(clickedMedia);
|
|
1922
2492
|
return;
|
|
1923
2493
|
}
|
|
1924
2494
|
|
|
@@ -1949,16 +2519,78 @@ function RichTextEditor({
|
|
|
1949
2519
|
}
|
|
1950
2520
|
}, 0);
|
|
1951
2521
|
}
|
|
1952
|
-
}, [editable, disabled, editorFocused, triggerChange]);
|
|
2522
|
+
}, [areaHighlightMode, editable, disabled, editorFocused, triggerChange]);
|
|
2523
|
+
useEffect(() => {
|
|
2524
|
+
if (!selectedMedia) return;
|
|
2525
|
+
syncMediaWidthControls(selectedMedia);
|
|
2526
|
+
}, [selectedMedia, selectionVersion]);
|
|
2527
|
+
useEffect(() => {
|
|
2528
|
+
const editor = editorRef.current;
|
|
2529
|
+
if (!editor || !selectedMedia) return;
|
|
2530
|
+
const bumpToolbarPosition = () => setSelectionVersion(v => v + 1);
|
|
2531
|
+
editor.addEventListener("scroll", bumpToolbarPosition, {
|
|
2532
|
+
passive: true
|
|
2533
|
+
});
|
|
2534
|
+
window.addEventListener("resize", bumpToolbarPosition, {
|
|
2535
|
+
passive: true
|
|
2536
|
+
});
|
|
2537
|
+
return () => {
|
|
2538
|
+
editor.removeEventListener("scroll", bumpToolbarPosition);
|
|
2539
|
+
window.removeEventListener("resize", bumpToolbarPosition);
|
|
2540
|
+
};
|
|
2541
|
+
}, [selectedMedia]);
|
|
1953
2542
|
const renderMediaToolbar = () => {
|
|
1954
2543
|
if (!selectedMedia || !editorRef.current || !editable) return null;
|
|
1955
|
-
const
|
|
2544
|
+
const wrapperEl = contentWrapperRef.current;
|
|
2545
|
+
const anchorRect = (wrapperEl === null || wrapperEl === void 0 ? void 0 : wrapperEl.getBoundingClientRect()) ?? editorRef.current.getBoundingClientRect();
|
|
1956
2546
|
const mediaRect = selectedMedia.getBoundingClientRect();
|
|
1957
|
-
const
|
|
1958
|
-
const
|
|
1959
|
-
const
|
|
2547
|
+
const relTop = mediaRect.top - anchorRect.top;
|
|
2548
|
+
const relLeft = mediaRect.left - anchorRect.left;
|
|
2549
|
+
const mediaWidth = mediaRect.width;
|
|
2550
|
+
const mediaHeight = mediaRect.height;
|
|
1960
2551
|
const currentPercent = getMediaWidthPercent(selectedMedia);
|
|
1961
2552
|
const widthPresets = [25, 50, 75, 100];
|
|
2553
|
+
const toolbarWidth = 340;
|
|
2554
|
+
const toolbarHeight = 40;
|
|
2555
|
+
const gap = 8;
|
|
2556
|
+
const preferredTop = relTop - toolbarHeight - gap;
|
|
2557
|
+
const toolbarTop = preferredTop >= 4 ? preferredTop : relTop + mediaHeight + gap;
|
|
2558
|
+
const applyCustomMediaWidth = () => {
|
|
2559
|
+
const num = parseFloat(mediaWidthInput);
|
|
2560
|
+
if (isNaN(num) || num <= 0) {
|
|
2561
|
+
syncMediaWidthControls(selectedMedia);
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
if (mediaWidthUnit === "px") {
|
|
2565
|
+
applyMediaWidthPx(selectedMedia, num);
|
|
2566
|
+
} else {
|
|
2567
|
+
applyMediaWidthPercent(selectedMedia, num);
|
|
2568
|
+
}
|
|
2569
|
+
syncMediaWidthControls(selectedMedia);
|
|
2570
|
+
setSelectionVersion(v => v + 1);
|
|
2571
|
+
triggerChange();
|
|
2572
|
+
};
|
|
2573
|
+
const handleUnitChange = newUnit => {
|
|
2574
|
+
if (newUnit === mediaWidthUnit) return;
|
|
2575
|
+
const editorInnerWidth = getEditorInnerWidth();
|
|
2576
|
+
const currentVal = parseFloat(mediaWidthInput);
|
|
2577
|
+
if (isNaN(currentVal) || currentVal <= 0) {
|
|
2578
|
+
setMediaWidthUnit(newUnit);
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
let converted = currentVal;
|
|
2582
|
+
if (newUnit === "px" && mediaWidthUnit === "%") {
|
|
2583
|
+
converted = Math.round(editorInnerWidth * currentVal / 100);
|
|
2584
|
+
applyMediaWidthPx(selectedMedia, converted);
|
|
2585
|
+
} else if (newUnit === "%" && mediaWidthUnit === "px") {
|
|
2586
|
+
converted = Math.min(100, Math.max(10, Math.round(currentVal / editorInnerWidth * 100)));
|
|
2587
|
+
applyMediaWidthPercent(selectedMedia, converted);
|
|
2588
|
+
}
|
|
2589
|
+
setMediaWidthInput(String(converted));
|
|
2590
|
+
setMediaWidthUnit(newUnit);
|
|
2591
|
+
setSelectionVersion(v => v + 1);
|
|
2592
|
+
triggerChange();
|
|
2593
|
+
};
|
|
1962
2594
|
const handleAlignment = align => {
|
|
1963
2595
|
selectedMedia.classList.remove("image-align-left", "image-align-center", "image-align-right");
|
|
1964
2596
|
selectedMedia.classList.add(`image-align-${align}`);
|
|
@@ -1968,6 +2600,8 @@ function RichTextEditor({
|
|
|
1968
2600
|
};
|
|
1969
2601
|
const setWidth = percent => {
|
|
1970
2602
|
applyMediaWidthPercent(selectedMedia, percent);
|
|
2603
|
+
syncMediaWidthControls(selectedMedia);
|
|
2604
|
+
setSelectionVersion(v => v + 1);
|
|
1971
2605
|
triggerChange();
|
|
1972
2606
|
};
|
|
1973
2607
|
const removeMedia = () => {
|
|
@@ -1976,17 +2610,26 @@ function RichTextEditor({
|
|
|
1976
2610
|
triggerChange();
|
|
1977
2611
|
};
|
|
1978
2612
|
const isActivePercent = percent => {
|
|
2613
|
+
if (selectedMedia.dataset.widthPx) return false;
|
|
1979
2614
|
if (!selectedMedia.dataset.widthPercent && !(selectedMedia.style.width || "").endsWith("%")) {
|
|
1980
2615
|
return false;
|
|
1981
2616
|
}
|
|
1982
2617
|
return Math.abs(currentPercent - percent) <= 3;
|
|
1983
2618
|
};
|
|
2619
|
+
const isFormControl = target => target instanceof Element && !!target.closest("input, select, textarea");
|
|
1984
2620
|
return /*#__PURE__*/React.createElement("div", {
|
|
1985
2621
|
className: "rte-media-toolbar",
|
|
2622
|
+
onMouseDown: e => {
|
|
2623
|
+
e.stopPropagation();
|
|
2624
|
+
if (!isFormControl(e.target)) {
|
|
2625
|
+
e.preventDefault();
|
|
2626
|
+
}
|
|
2627
|
+
},
|
|
2628
|
+
onClick: e => e.stopPropagation(),
|
|
1986
2629
|
style: {
|
|
1987
2630
|
position: "absolute",
|
|
1988
|
-
top: Math.max(
|
|
1989
|
-
left: Math.max(8,
|
|
2631
|
+
top: Math.max(4, toolbarTop),
|
|
2632
|
+
left: Math.max(8, Math.min(relLeft + mediaWidth / 2 - toolbarWidth / 2, anchorRect.width - toolbarWidth - 8)),
|
|
1990
2633
|
zIndex: 1000
|
|
1991
2634
|
}
|
|
1992
2635
|
}, /*#__PURE__*/React.createElement("button", {
|
|
@@ -2011,6 +2654,44 @@ function RichTextEditor({
|
|
|
2011
2654
|
title: `${percent}% width`
|
|
2012
2655
|
}, percent, "%")), /*#__PURE__*/React.createElement("span", {
|
|
2013
2656
|
className: "rte-media-toolbar-divider"
|
|
2657
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
2658
|
+
className: "rte-media-width-custom"
|
|
2659
|
+
}, /*#__PURE__*/React.createElement("input", {
|
|
2660
|
+
type: "number",
|
|
2661
|
+
className: "rte-media-width-input",
|
|
2662
|
+
value: mediaWidthInput,
|
|
2663
|
+
min: mediaWidthUnit === "%" ? 10 : 20,
|
|
2664
|
+
max: mediaWidthUnit === "%" ? 100 : 2000,
|
|
2665
|
+
title: "Custom width",
|
|
2666
|
+
onChange: e => setMediaWidthInput(e.target.value),
|
|
2667
|
+
onBlur: e => {
|
|
2668
|
+
var _e$relatedTarget, _e$relatedTarget$clos;
|
|
2669
|
+
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;
|
|
2670
|
+
applyCustomMediaWidth();
|
|
2671
|
+
},
|
|
2672
|
+
onMouseDown: e => e.stopPropagation(),
|
|
2673
|
+
onKeyDown: e => {
|
|
2674
|
+
if (e.key === "Enter") {
|
|
2675
|
+
e.preventDefault();
|
|
2676
|
+
applyCustomMediaWidth();
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
}), /*#__PURE__*/React.createElement("select", {
|
|
2680
|
+
className: "rte-media-width-unit",
|
|
2681
|
+
value: mediaWidthUnit,
|
|
2682
|
+
onChange: e => handleUnitChange(e.target.value),
|
|
2683
|
+
onMouseDown: e => e.stopPropagation()
|
|
2684
|
+
}, /*#__PURE__*/React.createElement("option", {
|
|
2685
|
+
value: "%"
|
|
2686
|
+
}, "%"), /*#__PURE__*/React.createElement("option", {
|
|
2687
|
+
value: "px"
|
|
2688
|
+
}, "px")), /*#__PURE__*/React.createElement("button", {
|
|
2689
|
+
type: "button",
|
|
2690
|
+
className: "rte-media-width-apply",
|
|
2691
|
+
title: "Apply size",
|
|
2692
|
+
onClick: applyCustomMediaWidth
|
|
2693
|
+
}, "\u2713")), /*#__PURE__*/React.createElement("span", {
|
|
2694
|
+
className: "rte-media-toolbar-divider"
|
|
2014
2695
|
}), /*#__PURE__*/React.createElement("button", {
|
|
2015
2696
|
type: "button",
|
|
2016
2697
|
onClick: removeMedia,
|
|
@@ -2145,7 +2826,7 @@ function RichTextEditor({
|
|
|
2145
2826
|
key: s,
|
|
2146
2827
|
value: s
|
|
2147
2828
|
}, s, "px"))), /*#__PURE__*/React.createElement("label", {
|
|
2148
|
-
title: "
|
|
2829
|
+
title: "Text Color",
|
|
2149
2830
|
className: "rte-color-picker-label"
|
|
2150
2831
|
}, /*#__PURE__*/React.createElement(FaFont, {
|
|
2151
2832
|
size: 14,
|
|
@@ -2158,12 +2839,68 @@ function RichTextEditor({
|
|
|
2158
2839
|
onMouseDown: e => {
|
|
2159
2840
|
e.preventDefault();
|
|
2160
2841
|
e.stopPropagation();
|
|
2842
|
+
saveSelection();
|
|
2161
2843
|
},
|
|
2162
2844
|
onChange: e => {
|
|
2163
2845
|
e.stopPropagation();
|
|
2164
|
-
|
|
2846
|
+
applyTextColor(e.target.value);
|
|
2165
2847
|
},
|
|
2166
2848
|
className: "rte-color-input"
|
|
2849
|
+
})), /*#__PURE__*/React.createElement("label", {
|
|
2850
|
+
title: areaHighlightMode ? "Highlight color for area selection" : "Text background color",
|
|
2851
|
+
className: "rte-color-picker-label rte-bg-color-picker-label"
|
|
2852
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
2853
|
+
className: `rte-bg-color-swatch${hasActiveTextBackground ? "" : " rte-bg-color-swatch-none"}`,
|
|
2854
|
+
style: hasActiveTextBackground ? {
|
|
2855
|
+
backgroundColor: bgColor,
|
|
2856
|
+
color: fontColor
|
|
2857
|
+
} : {
|
|
2858
|
+
color: fontColor
|
|
2859
|
+
}
|
|
2860
|
+
}, "A"), /*#__PURE__*/React.createElement("input", {
|
|
2861
|
+
type: "color",
|
|
2862
|
+
value: bgColor,
|
|
2863
|
+
onMouseDown: e => {
|
|
2864
|
+
e.preventDefault();
|
|
2865
|
+
e.stopPropagation();
|
|
2866
|
+
if (!areaHighlightMode) saveSelection();
|
|
2867
|
+
},
|
|
2868
|
+
onChange: e => {
|
|
2869
|
+
e.stopPropagation();
|
|
2870
|
+
const color = e.target.value;
|
|
2871
|
+
setBgColor(color);
|
|
2872
|
+
if (!areaHighlightMode) {
|
|
2873
|
+
applyBackgroundColor(color);
|
|
2874
|
+
}
|
|
2875
|
+
},
|
|
2876
|
+
className: "rte-color-input"
|
|
2877
|
+
})), /*#__PURE__*/React.createElement("button", {
|
|
2878
|
+
type: "button",
|
|
2879
|
+
title: "Remove background color",
|
|
2880
|
+
className: `rte-toolbar-button rte-bg-color-clear${hasActiveTextBackground ? " active" : ""}`,
|
|
2881
|
+
onMouseDown: e => {
|
|
2882
|
+
e.preventDefault();
|
|
2883
|
+
e.stopPropagation();
|
|
2884
|
+
saveSelection();
|
|
2885
|
+
removeBackgroundColor();
|
|
2886
|
+
}
|
|
2887
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
2888
|
+
className: "rte-bg-color-clear-icon",
|
|
2889
|
+
"aria-hidden": "true"
|
|
2890
|
+
}, "A")), /*#__PURE__*/React.createElement("button", {
|
|
2891
|
+
type: "button",
|
|
2892
|
+
title: "Area highlight \u2014 drag to select a box (like screenshot)",
|
|
2893
|
+
className: `rte-toolbar-button rte-area-highlight-toggle${areaHighlightMode ? " active" : ""}`,
|
|
2894
|
+
onMouseDown: e => e.preventDefault(),
|
|
2895
|
+
onClick: () => {
|
|
2896
|
+
setAreaHighlightMode(prev => !prev);
|
|
2897
|
+
setMarqueePreview(null);
|
|
2898
|
+
areaDragRef.current = null;
|
|
2899
|
+
focus();
|
|
2900
|
+
}
|
|
2901
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
2902
|
+
className: "rte-area-highlight-icon",
|
|
2903
|
+
"aria-hidden": "true"
|
|
2167
2904
|
})), /*#__PURE__*/React.createElement("div", {
|
|
2168
2905
|
style: {
|
|
2169
2906
|
width: '1px',
|
|
@@ -2460,11 +3197,26 @@ function RichTextEditor({
|
|
|
2460
3197
|
}
|
|
2461
3198
|
return null;
|
|
2462
3199
|
})()), /*#__PURE__*/React.createElement("div", {
|
|
2463
|
-
|
|
3200
|
+
ref: contentWrapperRef,
|
|
3201
|
+
className: `rte-content-wrapper${areaHighlightMode ? " rte-area-highlight-mode" : ""}`,
|
|
2464
3202
|
style: {
|
|
2465
3203
|
position: 'relative'
|
|
3204
|
+
},
|
|
3205
|
+
onMouseDown: handleAreaSelectMouseDown
|
|
3206
|
+
}, areaHighlightMode && /*#__PURE__*/React.createElement("div", {
|
|
3207
|
+
className: "rte-area-highlight-hint",
|
|
3208
|
+
"aria-hidden": "true"
|
|
3209
|
+
}, "Drag to select an area, then release to apply highlight"), marqueePreview && /*#__PURE__*/React.createElement("div", {
|
|
3210
|
+
className: "rte-marquee-preview",
|
|
3211
|
+
style: {
|
|
3212
|
+
left: marqueePreview.left,
|
|
3213
|
+
top: marqueePreview.top,
|
|
3214
|
+
width: marqueePreview.width,
|
|
3215
|
+
height: marqueePreview.height,
|
|
3216
|
+
backgroundColor: `${bgColor}55`,
|
|
3217
|
+
borderColor: bgColor
|
|
2466
3218
|
}
|
|
2467
|
-
}, /*#__PURE__*/React.createElement("div", {
|
|
3219
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
2468
3220
|
ref: editorRef,
|
|
2469
3221
|
contentEditable: editable && disabled !== true,
|
|
2470
3222
|
suppressContentEditableWarning: true,
|
|
@@ -2477,6 +3229,11 @@ function RichTextEditor({
|
|
|
2477
3229
|
onDragStart: e => e.preventDefault(),
|
|
2478
3230
|
onDragOver: e => e.preventDefault(),
|
|
2479
3231
|
onKeyDown: handleKeyDown,
|
|
3232
|
+
onKeyUp: handleKeyUp,
|
|
3233
|
+
onMouseUp: () => {
|
|
3234
|
+
saveSelection();
|
|
3235
|
+
setSelectionVersion(v => v + 1);
|
|
3236
|
+
},
|
|
2480
3237
|
onClick: handleEditorClick,
|
|
2481
3238
|
onFocus: handleEditorFocus,
|
|
2482
3239
|
onBlur: handleEditorBlur,
|
|
@@ -2492,7 +3249,7 @@ function RichTextEditor({
|
|
|
2492
3249
|
left: paddingLeft || '12px'
|
|
2493
3250
|
},
|
|
2494
3251
|
"aria-hidden": "true"
|
|
2495
|
-
}, placeholder)
|
|
3252
|
+
}, placeholder), renderMediaToolbar()), /*#__PURE__*/React.createElement("div", {
|
|
2496
3253
|
className: "rte-footer"
|
|
2497
3254
|
}, /*#__PURE__*/React.createElement("div", {
|
|
2498
3255
|
className: "rte-footer-content"
|