]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/nodes/diagram.ts
Lexical: Started diagram support
[bookstack] / resources / js / wysiwyg / nodes / diagram.ts
1 import {
2     DecoratorNode,
3     DOMConversion,
4     DOMConversionMap,
5     DOMConversionOutput,
6     LexicalEditor, LexicalNode,
7     SerializedLexicalNode,
8     Spread
9 } from "lexical";
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import {el} from "../helpers";
12 import {EditorDecoratorAdapter} from "../ui/framework/decorator";
13
14 export type SerializedDiagramNode = Spread<{
15     id: string;
16     drawingId: string;
17     drawingUrl: string;
18 }, SerializedLexicalNode>
19
20 export class DiagramNode extends DecoratorNode<EditorDecoratorAdapter> {
21     __id: string = '';
22     __drawingId: string = '';
23     __drawingUrl: string = '';
24
25     static getType(): string {
26         return 'diagram';
27     }
28
29     static clone(node: DiagramNode): DiagramNode {
30         return new DiagramNode(node.__drawingId, node.__drawingUrl);
31     }
32
33     constructor(drawingId: string, drawingUrl: string, key?: string) {
34         super(key);
35         this.__drawingId = drawingId;
36         this.__drawingUrl = drawingUrl;
37     }
38
39     setDrawingIdAndUrl(drawingId: string, drawingUrl: string): void {
40         const self = this.getWritable();
41         self.__drawingUrl = drawingUrl;
42         self.__drawingId = drawingId;
43     }
44
45     getDrawingIdAndUrl(): {id: string, url: string} {
46         const self = this.getLatest();
47         return {
48             id: self.__drawingUrl,
49             url: self.__drawingUrl,
50         };
51     }
52
53     setId(id: string) {
54         const self = this.getWritable();
55         self.__id = id;
56     }
57
58     getId(): string {
59         const self = this.getLatest();
60         return self.__id;
61     }
62
63     decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
64         return {
65             type: 'diagram',
66             getNode: () => this,
67         };
68     }
69
70     isInline(): boolean {
71         return false;
72     }
73
74     isIsolated() {
75         return true;
76     }
77
78     createDOM(_config: EditorConfig, _editor: LexicalEditor) {
79         return el('div', {
80             id: this.__id || null,
81             'drawio-diagram': this.__drawingId,
82         }, [
83             el('img', {src: this.__drawingUrl}),
84         ]);
85     }
86
87     updateDOM(prevNode: DiagramNode, dom: HTMLElement) {
88         const img = dom.querySelector('img');
89         if (!img) return false;
90
91         if (prevNode.__id !== this.__id) {
92             dom.setAttribute('id', this.__id);
93         }
94
95         if (prevNode.__drawingUrl !== this.__drawingUrl) {
96             img.setAttribute('src', this.__drawingUrl);
97         }
98
99         if (prevNode.__drawingId !== this.__drawingId) {
100             dom.setAttribute('drawio-diagram', this.__drawingId);
101         }
102
103         return false;
104     }
105
106     static importDOM(): DOMConversionMap|null {
107         return {
108             div(node: HTMLElement): DOMConversion|null {
109
110                 if (!node.hasAttribute('drawio-diagram')) {
111                     return null;
112                 }
113
114                 return {
115                     conversion: (element: HTMLElement): DOMConversionOutput|null => {
116
117                         const img = element.querySelector('img');
118                         const drawingUrl = img?.getAttribute('src') || '';
119                         const drawingId = element.getAttribute('drawio-diagram') || '';
120
121                         return {
122                             node: $createDiagramNode(drawingId, drawingUrl),
123                         };
124                     },
125                     priority: 3,
126                 };
127             },
128         };
129     }
130
131     exportJSON(): SerializedDiagramNode {
132         return {
133             type: 'diagram',
134             version: 1,
135             id: this.__id,
136             drawingId: this.__drawingId,
137             drawingUrl: this.__drawingUrl,
138         };
139     }
140
141     static importJSON(serializedNode: SerializedDiagramNode): DiagramNode {
142         const node = $createDiagramNode(serializedNode.drawingId, serializedNode.drawingUrl);
143         node.setId(serializedNode.id || '');
144         return node;
145     }
146 }
147
148 export function $createDiagramNode(drawingId: string = '', drawingUrl: string = ''): DiagramNode {
149     return new DiagramNode(drawingId, drawingUrl);
150 }
151
152 export function $isDiagramNode(node: LexicalNode | null | undefined) {
153     return node instanceof DiagramNode;
154 }
155
156 export function $openDrawingEditorForNode(editor: LexicalEditor, node: DiagramNode): void {
157     // Todo
158 }