]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Fixed tiny image resizer on image insert
authorDan Brown <redacted>
Fri, 13 Jun 2025 14:58:59 +0000 (15:58 +0100)
committerDan Brown <redacted>
Fri, 13 Jun 2025 14:58:59 +0000 (15:58 +0100)
Added specific focus on image insert, and updated resize handler to
watch for load events and toggle a resize once loaded.

resources/js/wysiwyg/ui/defaults/buttons/objects.ts
resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts

index 4eb4c5a4e68845cd9f776f797ac958d711989680..41f6061b64878075712671024a7ddd6ac532eb55 100644 (file)
@@ -92,6 +92,7 @@ export const image: EditorButtonDefinition = {
                 context.editor.update(() => {
                     const link = $createLinkedImageNodeFromImageData(image);
                     $insertNodes([link]);
+                    link.select();
                 });
             })
         });
index fa8ff48be56fa1361c52fae6ddbf982ce95141ef..934ac5763b3a69e668cd400216bae3eb4ba8f1a3 100644 (file)
@@ -12,17 +12,21 @@ function isNodeWithSize(node: LexicalNode): node is NodeHasSize&LexicalNode {
 
 class NodeResizer {
     protected context: EditorUiContext;
-    protected dom: HTMLElement|null = null;
+    protected resizerDOM: HTMLElement|null = null;
+    protected targetDOM: HTMLElement|null = null;
     protected scrollContainer: HTMLElement;
 
     protected mouseTracker: MouseDragTracker|null = null;
     protected activeSelection: string = '';
+    protected loadAbortController = new AbortController();
 
     constructor(context: EditorUiContext) {
         this.context = context;
         this.scrollContainer = context.scrollDOM;
 
         this.onSelectionChange = this.onSelectionChange.bind(this);
+        this.onTargetDOMLoad = this.onTargetDOMLoad.bind(this);
+
         context.manager.onSelectionChange(this.onSelectionChange);
     }
 
@@ -47,56 +51,68 @@ class NodeResizer {
         }
     }
 
+    onTargetDOMLoad(): void {
+        this.updateResizerPosition();
+    }
+
     teardown() {
         this.context.manager.offSelectionChange(this.onSelectionChange);
         this.hide();
     }
 
-    protected showForNode(node: NodeHasSize&LexicalNode, dom: HTMLElement) {
-        this.dom = this.buildDOM();
+    protected showForNode(node: NodeHasSize&LexicalNode, targetDOM: HTMLElement) {
+        this.resizerDOM = this.buildDOM();
+        this.targetDOM = targetDOM;
 
         let ghost = el('span', {class: 'editor-node-resizer-ghost'});
         if ($isImageNode(node)) {
-            ghost = el('img', {src: dom.getAttribute('src'), class: 'editor-node-resizer-ghost'});
+            ghost = el('img', {src: targetDOM.getAttribute('src'), class: 'editor-node-resizer-ghost'});
         }
-        this.dom.append(ghost);
+        this.resizerDOM.append(ghost);
 
-        this.context.scrollDOM.append(this.dom);
-        this.updateDOMPosition(dom);
+        this.context.scrollDOM.append(this.resizerDOM);
+        this.updateResizerPosition();
 
-        this.mouseTracker = this.setupTracker(this.dom, node, dom);
+        this.mouseTracker = this.setupTracker(this.resizerDOM, node, targetDOM);
         this.activeSelection = node.getKey();
+
+        if (targetDOM.matches('img, embed, iframe, object')) {
+            this.loadAbortController = new AbortController();
+            targetDOM.addEventListener('load', this.onTargetDOMLoad, { signal: this.loadAbortController.signal });
+        }
     }
 
-    protected updateDOMPosition(nodeDOM: HTMLElement) {
-        if (!this.dom) {
+    protected updateResizerPosition() {
+        if (!this.resizerDOM || !this.targetDOM) {
             return;
         }
 
         const scrollAreaRect = this.scrollContainer.getBoundingClientRect();
-        const nodeRect = nodeDOM.getBoundingClientRect();
+        const nodeRect = this.targetDOM.getBoundingClientRect();
         const top = nodeRect.top - (scrollAreaRect.top - this.scrollContainer.scrollTop);
         const left = nodeRect.left - scrollAreaRect.left;
 
-        this.dom.style.top = `${top}px`;
-        this.dom.style.left = `${left}px`;
-        this.dom.style.width = nodeRect.width + 'px';
-        this.dom.style.height = nodeRect.height + 'px';
+        this.resizerDOM.style.top = `${top}px`;
+        this.resizerDOM.style.left = `${left}px`;
+        this.resizerDOM.style.width = nodeRect.width + 'px';
+        this.resizerDOM.style.height = nodeRect.height + 'px';
     }
 
     protected updateDOMSize(width: number, height: number): void {
-        if (!this.dom) {
+        if (!this.resizerDOM) {
             return;
         }
 
-        this.dom.style.width = width + 'px';
-        this.dom.style.height = height + 'px';
+        this.resizerDOM.style.width = width + 'px';
+        this.resizerDOM.style.height = height + 'px';
     }
 
     protected hide() {
         this.mouseTracker?.teardown();
-        this.dom?.remove();
+        this.resizerDOM?.remove();
+        this.targetDOM = null;
         this.activeSelection = '';
+        this.loadAbortController.abort();
     }
 
     protected buildDOM() {
@@ -140,7 +156,7 @@ class NodeResizer {
 
         return new MouseDragTracker(container, '.editor-node-resizer-handle', {
             down(event: MouseEvent, handle: HTMLElement) {
-                _this.dom?.classList.add('active');
+                _this.resizerDOM?.classList.add('active');
                 _this.context.editor.getEditorState().read(() => {
                     const domRect = nodeDOM.getBoundingClientRect();
                     startingWidth = node.getWidth() || domRect.width;
@@ -165,10 +181,10 @@ class NodeResizer {
                     node.setHeight(hasHeight ? size.height : 0);
                     _this.context.manager.triggerLayoutUpdate();
                     requestAnimationFrame(() => {
-                        _this.updateDOMPosition(nodeDOM);
+                        _this.updateResizerPosition();
                     })
                 });
-                _this.dom?.classList.remove('active');
+                _this.resizerDOM?.classList.remove('active');
             }
         });
     }