6 LexicalEditor, LexicalNode,
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import {el} from "../helpers";
12 import {EditorDecoratorAdapter} from "../ui/framework/decorator";
13 import * as DrawIO from '../../services/drawio';
14 import {EditorUiContext} from "../ui/framework/core";
15 import {HttpError} from "../../services/http";
17 export type SerializedDiagramNode = Spread<{
21 }, SerializedLexicalNode>
23 export class DiagramNode extends DecoratorNode<EditorDecoratorAdapter> {
25 __drawingId: string = '';
26 __drawingUrl: string = '';
28 static getType(): string {
32 static clone(node: DiagramNode): DiagramNode {
33 return new DiagramNode(node.__drawingId, node.__drawingUrl);
36 constructor(drawingId: string, drawingUrl: string, key?: string) {
38 this.__drawingId = drawingId;
39 this.__drawingUrl = drawingUrl;
42 setDrawingIdAndUrl(drawingId: string, drawingUrl: string): void {
43 const self = this.getWritable();
44 self.__drawingUrl = drawingUrl;
45 self.__drawingId = drawingId;
48 getDrawingIdAndUrl(): { id: string, url: string } {
49 const self = this.getLatest();
52 url: self.__drawingUrl,
57 const self = this.getWritable();
62 const self = this.getLatest();
66 decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
81 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
83 id: this.__id || null,
84 'drawio-diagram': this.__drawingId,
86 el('img', {src: this.__drawingUrl}),
90 updateDOM(prevNode: DiagramNode, dom: HTMLElement) {
91 const img = dom.querySelector('img');
92 if (!img) return false;
94 if (prevNode.__id !== this.__id) {
95 dom.setAttribute('id', this.__id);
98 if (prevNode.__drawingUrl !== this.__drawingUrl) {
99 img.setAttribute('src', this.__drawingUrl);
102 if (prevNode.__drawingId !== this.__drawingId) {
103 dom.setAttribute('drawio-diagram', this.__drawingId);
109 static importDOM(): DOMConversionMap | null {
111 div(node: HTMLElement): DOMConversion | null {
113 if (!node.hasAttribute('drawio-diagram')) {
118 conversion: (element: HTMLElement): DOMConversionOutput | null => {
120 const img = element.querySelector('img');
121 const drawingUrl = img?.getAttribute('src') || '';
122 const drawingId = element.getAttribute('drawio-diagram') || '';
125 node: $createDiagramNode(drawingId, drawingUrl),
134 exportJSON(): SerializedDiagramNode {
139 drawingId: this.__drawingId,
140 drawingUrl: this.__drawingUrl,
144 static importJSON(serializedNode: SerializedDiagramNode): DiagramNode {
145 const node = $createDiagramNode(serializedNode.drawingId, serializedNode.drawingUrl);
146 node.setId(serializedNode.id || '');
151 export function $createDiagramNode(drawingId: string = '', drawingUrl: string = ''): DiagramNode {
152 return new DiagramNode(drawingId, drawingUrl);
155 export function $isDiagramNode(node: LexicalNode | null | undefined) {
156 return node instanceof DiagramNode;
160 function handleUploadError(error: HttpError, context: EditorUiContext): void {
161 if (error.status === 413) {
162 window.$events.emit('error', context.options.translations.serverUploadLimitText || '');
164 window.$events.emit('error', context.options.translations.imageUploadErrorText || '');
166 console.error(error);
169 async function loadDiagramIdFromNode(editor: LexicalEditor, node: DiagramNode): Promise<string> {
170 const drawingId = await new Promise<string>((res, rej) => {
171 editor.getEditorState().read(() => {
172 const {id: drawingId} = node.getDrawingIdAndUrl();
177 return drawingId || '';
180 async function updateDrawingNodeFromData(context: EditorUiContext, node: DiagramNode, pngData: string, isNew: boolean): Promise<void> {
184 const loadingImage: string = window.baseUrl('/loading.gif');
185 context.editor.update(() => {
186 node.setDrawingIdAndUrl('', loadingImage);
191 const img = await DrawIO.upload(pngData, context.options.pageId);
192 context.editor.update(() => {
193 node.setDrawingIdAndUrl(String(img.id), img.url);
196 if (err instanceof HttpError) {
197 handleUploadError(err, context);
201 context.editor.update(() => {
206 throw new Error(`Failed to save image with error: ${err}`);
210 export function $openDrawingEditorForNode(context: EditorUiContext, node: DiagramNode): void {
212 DrawIO.show(context.options.drawioUrl, async () => {
213 const drawingId = await loadDiagramIdFromNode(context.editor, node);
215 return isNew ? '' : DrawIO.load(drawingId);
216 }, async (pngData: string) => {
217 return updateDrawingNodeFromData(context, node, pngData, isNew);