6 LexicalEditor, LexicalNode,
10 import type {EditorConfig} from "lexical/LexicalEditor";
11 import {EditorDecoratorAdapter} from "../ui/framework/decorator";
12 import * as DrawIO from '../../services/drawio';
13 import {EditorUiContext} from "../ui/framework/core";
14 import {HttpError} from "../../services/http";
15 import {el} from "../utils/dom";
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 const newNode = new DiagramNode(node.__drawingId, node.__drawingUrl);
34 newNode.__id = node.__id;
38 constructor(drawingId: string, drawingUrl: string, key?: string) {
40 this.__drawingId = drawingId;
41 this.__drawingUrl = drawingUrl;
44 setDrawingIdAndUrl(drawingId: string, drawingUrl: string): void {
45 const self = this.getWritable();
46 self.__drawingUrl = drawingUrl;
47 self.__drawingId = drawingId;
50 getDrawingIdAndUrl(): { id: string, url: string } {
51 const self = this.getLatest();
54 url: self.__drawingUrl,
59 const self = this.getWritable();
64 const self = this.getLatest();
68 decorate(editor: LexicalEditor, config: EditorConfig): EditorDecoratorAdapter {
83 createDOM(_config: EditorConfig, _editor: LexicalEditor) {
85 id: this.__id || null,
86 'drawio-diagram': this.__drawingId,
88 el('img', {src: this.__drawingUrl}),
92 updateDOM(prevNode: DiagramNode, dom: HTMLElement) {
93 const img = dom.querySelector('img');
94 if (!img) return false;
96 if (prevNode.__id !== this.__id) {
97 dom.setAttribute('id', this.__id);
100 if (prevNode.__drawingUrl !== this.__drawingUrl) {
101 img.setAttribute('src', this.__drawingUrl);
104 if (prevNode.__drawingId !== this.__drawingId) {
105 dom.setAttribute('drawio-diagram', this.__drawingId);
111 static importDOM(): DOMConversionMap | null {
113 div(node: HTMLElement): DOMConversion | null {
115 if (!node.hasAttribute('drawio-diagram')) {
120 conversion: (element: HTMLElement): DOMConversionOutput | null => {
122 const img = element.querySelector('img');
123 const drawingUrl = img?.getAttribute('src') || '';
124 const drawingId = element.getAttribute('drawio-diagram') || '';
125 const node = $createDiagramNode(drawingId, drawingUrl);
128 node.setId(element.id);
139 exportJSON(): SerializedDiagramNode {
144 drawingId: this.__drawingId,
145 drawingUrl: this.__drawingUrl,
149 static importJSON(serializedNode: SerializedDiagramNode): DiagramNode {
150 const node = $createDiagramNode(serializedNode.drawingId, serializedNode.drawingUrl);
151 node.setId(serializedNode.id || '');
156 export function $createDiagramNode(drawingId: string = '', drawingUrl: string = ''): DiagramNode {
157 return new DiagramNode(drawingId, drawingUrl);
160 export function $isDiagramNode(node: LexicalNode | null | undefined): node is DiagramNode {
161 return node instanceof DiagramNode;
165 function handleUploadError(error: HttpError, context: EditorUiContext): void {
166 if (error.status === 413) {
167 window.$events.emit('error', context.options.translations.serverUploadLimitText || '');
169 window.$events.emit('error', context.options.translations.imageUploadErrorText || '');
171 console.error(error);
174 async function loadDiagramIdFromNode(editor: LexicalEditor, node: DiagramNode): Promise<string> {
175 const drawingId = await new Promise<string>((res, rej) => {
176 editor.getEditorState().read(() => {
177 const {id: drawingId} = node.getDrawingIdAndUrl();
182 return drawingId || '';
185 async function updateDrawingNodeFromData(context: EditorUiContext, node: DiagramNode, pngData: string, isNew: boolean): Promise<void> {
189 const loadingImage: string = window.baseUrl('/loading.gif');
190 context.editor.update(() => {
191 node.setDrawingIdAndUrl('', loadingImage);
196 const img = await DrawIO.upload(pngData, context.options.pageId);
197 context.editor.update(() => {
198 node.setDrawingIdAndUrl(String(img.id), img.url);
201 if (err instanceof HttpError) {
202 handleUploadError(err, context);
206 context.editor.update(() => {
211 throw new Error(`Failed to save image with error: ${err}`);
215 export function $openDrawingEditorForNode(context: EditorUiContext, node: DiagramNode): void {
217 DrawIO.show(context.options.drawioUrl, async () => {
218 const drawingId = await loadDiagramIdFromNode(context.editor, node);
220 return isNew ? '' : DrawIO.load(drawingId);
221 }, async (pngData: string) => {
222 return updateDrawingNodeFromData(context, node, pngData, isNew);