]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Started diagram support
authorDan Brown <redacted>
Wed, 3 Jul 2024 09:28:04 +0000 (10:28 +0100)
committerDan Brown <redacted>
Wed, 3 Jul 2024 09:28:04 +0000 (10:28 +0100)
resources/js/wysiwyg/nodes/code-block.ts
resources/js/wysiwyg/nodes/diagram.ts [new file with mode: 0644]
resources/js/wysiwyg/nodes/index.ts
resources/js/wysiwyg/ui/decorators/diagram.ts [new file with mode: 0644]
resources/js/wysiwyg/ui/index.ts
tsconfig.json

index 934fe7eddd695cf686b6e9953f1c702d89f9708e..f839501db2ef5c721c997fd2b520f5c9481b39a0 100644 (file)
@@ -10,7 +10,6 @@ import {
 import type {EditorConfig} from "lexical/LexicalEditor";
 import {el} from "../helpers";
 import {EditorDecoratorAdapter} from "../ui/framework/decorator";
-import {code} from "../ui/defaults/button-definitions";
 
 export type SerializedCodeBlockNode = Spread<{
     language: string;
diff --git a/resources/js/wysiwyg/nodes/diagram.ts b/resources/js/wysiwyg/nodes/diagram.ts
new file mode 100644 (file)
index 0000000..1572681
--- /dev/null
@@ -0,0 +1,158 @@
+import {
+    DecoratorNode,
+    DOMConversion,
+    DOMConversionMap,
+    DOMConversionOutput,
+    LexicalEditor, LexicalNode,
+    SerializedLexicalNode,
+    Spread
+} from "lexical";
+import type {EditorConfig} from "lexical/LexicalEditor";
+import {el} from "../helpers";
+import {EditorDecoratorAdapter} from "../ui/framework/decorator";
+
+export type SerializedDiagramNode = Spread<{
+    id: string;
+    drawingId: string;
+    drawingUrl: string;
+}, SerializedLexicalNode>
+
+export class DiagramNode extends DecoratorNode<EditorDecoratorAdapter> {
+    __id: string = '';
+    __drawingId: string = '';
+    __drawingUrl: string = '';
+
+    static getType(): string {
+        return 'diagram';
+    }
+
+    static clone(node: DiagramNode): DiagramNode {
+        return new DiagramNode(node.__drawingId, node.__drawingUrl);
+    }
+
+    constructor(drawingId: string, drawingUrl: string, key?: string) {
+        super(key);
+        this.__drawingId = drawingId;
+        this.__drawingUrl = drawingUrl;
+    }
+
+    setDrawingIdAndUrl(drawingId: string, drawingUrl: string): void {
+        const self = this.getWritable();
+        self.__drawingUrl = drawingUrl;
+        self.__drawingId = drawingId;
+    }
+
+    getDrawingIdAndUrl(): {id: string, url: string} {
+        const self = this.getLatest();
+        return {
+            id: self.__drawingUrl,
+            url: self.__drawingUrl,
+        };
+    }
+
+    setId(id: string) {
+        const self = this.getWritable();
+        self.__id = id;
+    }
+
+    getId(): string {
+        const self = this.getLatest();
+        return self.__id;
+    }
+
+    decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
+        return {
+            type: 'diagram',
+            getNode: () => this,
+        };
+    }
+
+    isInline(): boolean {
+        return false;
+    }
+
+    isIsolated() {
+        return true;
+    }
+
+    createDOM(_config: EditorConfig, _editor: LexicalEditor) {
+        return el('div', {
+            id: this.__id || null,
+            'drawio-diagram': this.__drawingId,
+        }, [
+            el('img', {src: this.__drawingUrl}),
+        ]);
+    }
+
+    updateDOM(prevNode: DiagramNode, dom: HTMLElement) {
+        const img = dom.querySelector('img');
+        if (!img) return false;
+
+        if (prevNode.__id !== this.__id) {
+            dom.setAttribute('id', this.__id);
+        }
+
+        if (prevNode.__drawingUrl !== this.__drawingUrl) {
+            img.setAttribute('src', this.__drawingUrl);
+        }
+
+        if (prevNode.__drawingId !== this.__drawingId) {
+            dom.setAttribute('drawio-diagram', this.__drawingId);
+        }
+
+        return false;
+    }
+
+    static importDOM(): DOMConversionMap|null {
+        return {
+            div(node: HTMLElement): DOMConversion|null {
+
+                if (!node.hasAttribute('drawio-diagram')) {
+                    return null;
+                }
+
+                return {
+                    conversion: (element: HTMLElement): DOMConversionOutput|null => {
+
+                        const img = element.querySelector('img');
+                        const drawingUrl = img?.getAttribute('src') || '';
+                        const drawingId = element.getAttribute('drawio-diagram') || '';
+
+                        return {
+                            node: $createDiagramNode(drawingId, drawingUrl),
+                        };
+                    },
+                    priority: 3,
+                };
+            },
+        };
+    }
+
+    exportJSON(): SerializedDiagramNode {
+        return {
+            type: 'diagram',
+            version: 1,
+            id: this.__id,
+            drawingId: this.__drawingId,
+            drawingUrl: this.__drawingUrl,
+        };
+    }
+
+    static importJSON(serializedNode: SerializedDiagramNode): DiagramNode {
+        const node = $createDiagramNode(serializedNode.drawingId, serializedNode.drawingUrl);
+        node.setId(serializedNode.id || '');
+        return node;
+    }
+}
+
+export function $createDiagramNode(drawingId: string = '', drawingUrl: string = ''): DiagramNode {
+    return new DiagramNode(drawingId, drawingUrl);
+}
+
+export function $isDiagramNode(node: LexicalNode | null | undefined) {
+    return node instanceof DiagramNode;
+}
+
+export function $openDrawingEditorForNode(editor: LexicalEditor, node: DiagramNode): void {
+    // Todo
+}
\ No newline at end of file
index 4cc6bd08b784e831386be71daddc3524b381272d..e2c6902d3380c4c4c9e60f8bab72819771cec299 100644 (file)
@@ -10,6 +10,7 @@ import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
 import {CustomTableNode} from "./custom-table";
 import {HorizontalRuleNode} from "./horizontal-rule";
 import {CodeBlockNode} from "./code-block";
+import {DiagramNode} from "./diagram";
 
 /**
  * Load the nodes for lexical.
@@ -28,6 +29,7 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
         HorizontalRuleNode,
         DetailsNode, SummaryNode,
         CodeBlockNode,
+        DiagramNode,
         CustomParagraphNode,
         LinkNode,
         {
diff --git a/resources/js/wysiwyg/ui/decorators/diagram.ts b/resources/js/wysiwyg/ui/decorators/diagram.ts
new file mode 100644 (file)
index 0000000..2f092bd
--- /dev/null
@@ -0,0 +1,25 @@
+import {EditorDecorator} from "../framework/decorator";
+import {EditorUiContext} from "../framework/core";
+
+
+export class DiagramDecorator extends EditorDecorator {
+    protected completedSetup: boolean = false;
+
+    setup(context: EditorUiContext, element: HTMLElement) {
+        //
+
+        this.completedSetup = true;
+    }
+
+    update() {
+        //
+    }
+
+    render(context: EditorUiContext, element: HTMLElement): void {
+        if (this.completedSetup) {
+            this.update();
+        } else {
+            this.setup(context, element);
+        }
+    }
+}
\ No newline at end of file
index 1ad1395dc7781289d449ba294dfbf96fe7779f61..50307fa611353f78a661b8788e231b7d48c552f8 100644 (file)
@@ -5,6 +5,7 @@ import {image as imageFormDefinition, link as linkFormDefinition, source as sour
 import {ImageDecorator} from "./decorators/image";
 import {EditorUiContext} from "./framework/core";
 import {CodeBlockDecorator} from "./decorators/code-block";
+import {DiagramDecorator} from "./decorators/diagram";
 
 export function buildEditorUI(container: HTMLElement, element: HTMLElement, editor: LexicalEditor) {
     const manager = new EditorUIManager();
@@ -51,4 +52,5 @@ export function buildEditorUI(container: HTMLElement, element: HTMLElement, edit
     // Register image decorator listener
     manager.registerDecoratorType('image', ImageDecorator);
     manager.registerDecoratorType('code', CodeBlockDecorator);
+    manager.registerDecoratorType('diagram', DiagramDecorator);
 }
\ No newline at end of file
index 40d930149a98322012b815282d545f90e723433b..9913c1235a498955a50e4de88dc4a28c9d8de55a 100644 (file)
@@ -46,7 +46,7 @@
     // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
 
     /* JavaScript Support */
-    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+    "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
     // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
     // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */