]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Added selection to state for aligned reading
authorDan Brown <redacted>
Wed, 5 Jun 2024 17:43:42 +0000 (18:43 +0100)
committerDan Brown <redacted>
Wed, 5 Jun 2024 17:43:42 +0000 (18:43 +0100)
Connected up to work with image form

resources/js/wysiwyg/nodes/image.ts
resources/js/wysiwyg/ui/decorators/image.ts
resources/js/wysiwyg/ui/defaults/button-definitions.ts
resources/js/wysiwyg/ui/defaults/form-definitions.ts
resources/js/wysiwyg/ui/framework/core.ts
resources/js/wysiwyg/ui/index.ts
resources/js/wysiwyg/ui/toolbars.ts

index 9f017b5fed2eba9921dc71d98d47c7e5870d43f7..92d5518dbc7ccacf814762e3f9fc4ac947d8620e 100644 (file)
@@ -57,6 +57,16 @@ export class ImageNode extends DecoratorNode<EditorDecoratorAdapter> {
         }
     }
 
+    setSrc(src: string): void {
+        const self = this.getWritable();
+        self.__src = src;
+    }
+
+    getSrc(): string {
+        const self = this.getLatest();
+        return self.__src;
+    }
+
     setAltText(altText: string): void {
         const self = this.getWritable();
         self.__alt = altText;
index b3ceee65fa41e13bbbe7dbd6bb028956e5992895..1692d078d0c0c39913d6ba1714470735eacabe40 100644 (file)
@@ -88,6 +88,7 @@ export class ImageDecorator extends EditorDecorator {
         let startingHeight = element.clientHeight;
         let startingRatio = startingWidth / startingHeight;
         let hasHeight = false;
+        let firstChange = true;
         context.editor.getEditorState().read(() => {
             startingWidth = node.getWidth() || startingWidth;
             startingHeight = node.getHeight() || startingHeight;
@@ -109,7 +110,7 @@ export class ImageDecorator extends EditorDecorator {
             if (flipYChange) {
                 yChange = 0 - yChange;
             }
-            const balancedChange = Math.sqrt(Math.pow(xChange, 2) + Math.pow(yChange, 2));
+            const balancedChange = Math.sqrt(Math.pow(Math.abs(xChange), 2) + Math.pow(Math.abs(yChange), 2));
             const increase = xChange + yChange > 0;
             const directedChange = increase ? balancedChange : 0-balancedChange;
             const newWidth = Math.max(5, Math.round(startingWidth + directedChange));
@@ -118,11 +119,13 @@ export class ImageDecorator extends EditorDecorator {
                 newHeight = newWidth * startingRatio;
             }
 
+            const updateOptions = firstChange ? {} : {tag: 'history-merge'};
             context.editor.update(() => {
                 const node = this.getNode() as ImageNode;
                 node.setWidth(newWidth);
                 node.setHeight(newHeight);
-            });
+            }, updateOptions);
+            firstChange = false;
         };
 
         const mouseUpListener = (event: MouseEvent) => {
index f5be8251930652f23cb9cebfbafc4032445efefc..92f0cfc8120104751c1ae50cd931369c85e7601a 100644 (file)
@@ -25,6 +25,7 @@ import {
 } from "@lexical/rich-text";
 import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link";
 import {EditorUiContext} from "../framework/core";
+import {$isImageNode, ImageNode} from "../../nodes/image";
 
 export const undo: EditorButtonDefinition = {
     label: 'Undo',
@@ -168,3 +169,35 @@ export const link: EditorButtonDefinition = {
     }
 };
 
+export const image: EditorButtonDefinition = {
+    label: 'Insert/Edit Image',
+    action(context: EditorUiContext) {
+        const imageModal = context.manager.createModal('image');
+        const selection = context.lastSelection;
+        const selectedImage = getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
+
+        context.editor.getEditorState().read(() => {
+            let formDefaults = {};
+            if (selectedImage) {
+                formDefaults = {
+                    src: selectedImage.getSrc(),
+                    alt: selectedImage.getAltText(),
+                    height: selectedImage.getHeight(),
+                    width: selectedImage.getWidth(),
+                }
+
+                context.editor.update(() => {
+                    const selection = $createNodeSelection();
+                    selection.add(selectedImage.getKey());
+                    $setSelection(selection);
+                });
+            }
+
+            imageModal.show(formDefaults);
+        });
+    },
+    isActive(selection: BaseSelection|null): boolean {
+        return selectionContainsNodeType(selection, $isImageNode);
+    }
+};
+
index 457efa421e7171155d04771d0457e87004e19acd..13e7a9c9fc68125fc914121fc1f3a535871f55f6 100644 (file)
@@ -2,6 +2,7 @@ import {EditorFormDefinition, EditorSelectFormFieldDefinition} from "../framewor
 import {EditorUiContext} from "../framework/core";
 import {$createLinkNode} from "@lexical/link";
 import {$createTextNode, $getSelection} from "lexical";
+import {$createImageNode} from "../../nodes/image";
 
 
 export const link: EditorFormDefinition = {
@@ -47,4 +48,42 @@ export const link: EditorFormDefinition = {
             }
         } as EditorSelectFormFieldDefinition,
     ],
+};
+
+export const image: EditorFormDefinition = {
+    submitText: 'Apply',
+    action(formData, context: EditorUiContext) {
+        context.editor.update(() => {
+            const selection = $getSelection();
+            const imageNode = $createImageNode(formData.get('src')?.toString() || '', {
+                alt: formData.get('alt')?.toString() || '',
+                height: Number(formData.get('height')?.toString() || '0'),
+                width: Number(formData.get('width')?.toString() || '0'),
+            });
+            selection?.insertNodes([imageNode]);
+        });
+        return true;
+    },
+    fields: [
+        {
+            label: 'Source',
+            name: 'src',
+            type: 'text',
+        },
+        {
+            label: 'Alternative description',
+            name: 'alt',
+            type: 'text',
+        },
+        {
+            label: 'Width',
+            name: 'width',
+            type: 'text',
+        },
+        {
+            label: 'Height',
+            name: 'height',
+            type: 'text',
+        },
+    ],
 };
\ No newline at end of file
index 68d845b4249b655054cd2f3f9b7560dbeb5658e0..2fdadcb40c9835678e747fffa1b828de65dc1f5b 100644 (file)
@@ -10,6 +10,7 @@ export type EditorUiContext = {
     editor: LexicalEditor,
     translate: (text: string) => string,
     manager: EditorUIManager,
+    lastSelection: BaseSelection|null,
 };
 
 export abstract class EditorUiElement {
index 19320b2629f992066f86f5628bd0988a6c7810b6..8227dec6838f4cb23077db9c307ccd41a55d02f2 100644 (file)
@@ -6,18 +6,20 @@ import {
 } from "lexical";
 import {getMainEditorFullToolbar} from "./toolbars";
 import {EditorUIManager} from "./framework/manager";
-import {link as linkFormDefinition} from "./defaults/form-definitions";
+import {image as imageFormDefinition, link as linkFormDefinition} from "./defaults/form-definitions";
 import {DecoratorListener} from "lexical/LexicalEditor";
 import type {NodeKey} from "lexical/LexicalNode";
 import {EditorDecoratorAdapter} from "./framework/decorator";
 import {ImageDecorator} from "./decorators/image";
+import {EditorUiContext} from "./framework/core";
 
 export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
     const manager = new EditorUIManager();
-    const context = {
+    const context: EditorUiContext = {
         editor,
         manager,
         translate: (text: string): string => text,
+        lastSelection: null,
     };
     manager.setContext(context);
 
@@ -31,6 +33,10 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
         title: 'Insert/Edit link',
         form: linkFormDefinition,
     });
+    manager.registerModal('image', {
+        title: 'Insert/Edit Image',
+        form: imageFormDefinition
+    })
 
     // Register decorator listener
     // Maybe move to manager?
@@ -54,6 +60,7 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
     editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
         const selection = $getSelection();
         toolbar.updateState({editor, selection});
+        context.lastSelection = selection;
         return false;
     }, COMMAND_PRIORITY_LOW);
 }
\ No newline at end of file
index 2d5063cf4447f48a97dc2bd8e9ef35afb3a8f07a..2802b1ca7ba98c4db7e25bdcb451a30af5567e78 100644 (file)
@@ -2,7 +2,7 @@ import {EditorButton, FormatPreviewButton} from "./framework/buttons";
 import {
     blockquote, bold, code,
     dangerCallout,
-    h2, h3, h4, h5,
+    h2, h3, h4, h5, image,
     infoCallout, italic, link, paragraph,
     redo, strikethrough, subscript,
     successCallout, superscript, underline,
@@ -40,5 +40,6 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
         new EditorButton(code),
 
         new EditorButton(link),
+        new EditorButton(image),
     ]);
 }
\ No newline at end of file