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