BookStackApp/BookStack

View on GitHub
resources/js/wysiwyg/nodes/diagram.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import {
    DecoratorNode,
    DOMConversion,
    DOMConversionMap,
    DOMConversionOutput,
    LexicalEditor,
    SerializedLexicalNode,
    Spread
} from "lexical";
import type {EditorConfig} from "lexical/LexicalEditor";
import {EditorDecoratorAdapter} from "../ui/framework/decorator";
import {el} from "../utils/dom";

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 {
        const newNode = new DiagramNode(node.__drawingId, node.__drawingUrl);
        newNode.__id = node.__id;
        return newNode;
    }

    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.__drawingId,
            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') || '';
                        const node = $createDiagramNode(drawingId, drawingUrl);

                        if (element.id) {
                            node.setId(element.id);
                        }

                        return { node };
                    },
                    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);
}