Lexical: Started diagram support

This commit is contained in:
Dan Brown 2024-07-03 10:28:04 +01:00
parent d0a5a5ef37
commit feca1f0502
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
6 changed files with 188 additions and 2 deletions

View File

@ -10,7 +10,6 @@ import {
import type {EditorConfig} from "lexical/LexicalEditor"; import type {EditorConfig} from "lexical/LexicalEditor";
import {el} from "../helpers"; import {el} from "../helpers";
import {EditorDecoratorAdapter} from "../ui/framework/decorator"; import {EditorDecoratorAdapter} from "../ui/framework/decorator";
import {code} from "../ui/defaults/button-definitions";
export type SerializedCodeBlockNode = Spread<{ export type SerializedCodeBlockNode = Spread<{
language: string; language: string;

View File

@ -0,0 +1,158 @@
import {
DecoratorNode,
DOMConversion,
DOMConversionMap,
DOMConversionOutput,
LexicalEditor, LexicalNode,
SerializedLexicalNode,
Spread
} from "lexical";
import type {EditorConfig} from "lexical/LexicalEditor";
import {el} from "../helpers";
import {EditorDecoratorAdapter} from "../ui/framework/decorator";
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 {
return new DiagramNode(node.__drawingId, node.__drawingUrl);
}
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.__drawingUrl,
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') || '';
return {
node: $createDiagramNode(drawingId, drawingUrl),
};
},
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);
}
export function $isDiagramNode(node: LexicalNode | null | undefined) {
return node instanceof DiagramNode;
}
export function $openDrawingEditorForNode(editor: LexicalEditor, node: DiagramNode): void {
// Todo
}

View File

@ -10,6 +10,7 @@ import {TableCellNode, TableNode, TableRowNode} from "@lexical/table";
import {CustomTableNode} from "./custom-table"; import {CustomTableNode} from "./custom-table";
import {HorizontalRuleNode} from "./horizontal-rule"; import {HorizontalRuleNode} from "./horizontal-rule";
import {CodeBlockNode} from "./code-block"; import {CodeBlockNode} from "./code-block";
import {DiagramNode} from "./diagram";
/** /**
* Load the nodes for lexical. * Load the nodes for lexical.
@ -28,6 +29,7 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
HorizontalRuleNode, HorizontalRuleNode,
DetailsNode, SummaryNode, DetailsNode, SummaryNode,
CodeBlockNode, CodeBlockNode,
DiagramNode,
CustomParagraphNode, CustomParagraphNode,
LinkNode, LinkNode,
{ {

View File

@ -0,0 +1,25 @@
import {EditorDecorator} from "../framework/decorator";
import {EditorUiContext} from "../framework/core";
export class DiagramDecorator extends EditorDecorator {
protected completedSetup: boolean = false;
setup(context: EditorUiContext, element: HTMLElement) {
//
this.completedSetup = true;
}
update() {
//
}
render(context: EditorUiContext, element: HTMLElement): void {
if (this.completedSetup) {
this.update();
} else {
this.setup(context, element);
}
}
}

View File

@ -5,6 +5,7 @@ import {image as imageFormDefinition, link as linkFormDefinition, source as sour
import {ImageDecorator} from "./decorators/image"; import {ImageDecorator} from "./decorators/image";
import {EditorUiContext} from "./framework/core"; import {EditorUiContext} from "./framework/core";
import {CodeBlockDecorator} from "./decorators/code-block"; import {CodeBlockDecorator} from "./decorators/code-block";
import {DiagramDecorator} from "./decorators/diagram";
export function buildEditorUI(container: HTMLElement, element: HTMLElement, editor: LexicalEditor) { export function buildEditorUI(container: HTMLElement, element: HTMLElement, editor: LexicalEditor) {
const manager = new EditorUIManager(); const manager = new EditorUIManager();
@ -51,4 +52,5 @@ export function buildEditorUI(container: HTMLElement, element: HTMLElement, edit
// Register image decorator listener // Register image decorator listener
manager.registerDecoratorType('image', ImageDecorator); manager.registerDecoratorType('image', ImageDecorator);
manager.registerDecoratorType('code', CodeBlockDecorator); manager.registerDecoratorType('code', CodeBlockDecorator);
manager.registerDecoratorType('diagram', DiagramDecorator);
} }

View File

@ -46,7 +46,7 @@
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */ /* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */