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