]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/core.ts
Lexical: Started comment implementation
[bookstack] / resources / js / wysiwyg / ui / framework / core.ts
1 import {BaseSelection, LexicalEditor} from "lexical";
2 import {EditorUIManager} from "./manager";
3
4 import {el} from "../../utils/dom";
5
6 export type EditorUiStateUpdate = {
7     editor: LexicalEditor;
8     selection: BaseSelection|null;
9 };
10
11 export type EditorUiContext = {
12     editor: LexicalEditor; // Lexical editor instance
13     editorDOM: HTMLElement; // DOM element the editor is bound to
14     containerDOM: HTMLElement; // DOM element which contains all editor elements
15     scrollDOM: HTMLElement; // DOM element which is the main content scroll container
16     translate: (text: string) => string; // Translate function
17     error: (text: string|Error) => void; // Error reporting function
18     manager: EditorUIManager; // UI Manager instance for this editor
19     options: Record<string, any>; // General user options which may be used by sub elements
20 };
21
22 export interface EditorUiBuilderDefinition {
23     build: () => EditorUiElement;
24 }
25
26 export function isUiBuilderDefinition(object: any): object is EditorUiBuilderDefinition {
27     return 'build' in object;
28 }
29
30 export abstract class EditorUiElement {
31     protected dom: HTMLElement|null = null;
32     private context: EditorUiContext|null = null;
33
34     protected abstract buildDOM(): HTMLElement;
35
36     setContext(context: EditorUiContext): void {
37         this.context = context;
38     }
39
40     getContext(): EditorUiContext {
41         if (this.context === null) {
42             throw new Error('Attempted to use EditorUIContext before it has been set');
43         }
44
45         return this.context;
46     }
47
48     getDOMElement(): HTMLElement {
49         if (!this.dom) {
50             this.dom = this.buildDOM();
51         }
52
53         return this.dom;
54     }
55
56     rebuildDOM(): HTMLElement {
57         const newDOM = this.buildDOM();
58         this.dom?.replaceWith(newDOM);
59         this.dom = newDOM;
60         return this.dom;
61     }
62
63     trans(text: string) {
64         return this.getContext().translate(text);
65     }
66
67     updateState(state: EditorUiStateUpdate): void {
68         return;
69     }
70
71     emitEvent(name: string, data: object = {}): void {
72         if (this.dom) {
73             this.dom.dispatchEvent(new CustomEvent('editor::' + name, {detail: data, bubbles: true}));
74         }
75     }
76
77     onEvent(name: string, callback: (data: object) => any, listenTarget: HTMLElement|null = null): void {
78         const target = listenTarget || this.dom;
79         if (target) {
80             target.addEventListener('editor::' + name, ((event: CustomEvent) => {
81                 callback(event.detail);
82             }) as EventListener);
83         }
84     }
85 }
86
87 export class EditorContainerUiElement extends EditorUiElement {
88     protected children : EditorUiElement[] = [];
89
90     constructor(children: EditorUiElement[]) {
91         super();
92         this.children.push(...children);
93     }
94
95     protected buildDOM(): HTMLElement {
96         return el('div', {}, this.getChildren().map(child => child.getDOMElement()));
97     }
98
99     getChildren(): EditorUiElement[] {
100         return this.children;
101     }
102
103     protected addChildren(...children: EditorUiElement[]): void {
104         this.children.push(...children);
105     }
106
107     protected removeChildren(...children: EditorUiElement[]): void {
108         for (const child of children) {
109             this.removeChild(child);
110         }
111     }
112
113     protected removeChild(child: EditorUiElement) {
114         const index = this.children.indexOf(child);
115         if (index !== -1) {
116             this.children.splice(index, 1);
117         }
118     }
119
120     updateState(state: EditorUiStateUpdate): void {
121         for (const child of this.children) {
122             child.updateState(state);
123         }
124     }
125
126     setContext(context: EditorUiContext) {
127         super.setContext(context);
128         for (const child of this.getChildren()) {
129             child.setContext(context);
130         }
131     }
132 }
133
134 export class EditorSimpleClassContainer extends EditorContainerUiElement {
135     protected className;
136
137     constructor(className: string, children: EditorUiElement[]) {
138         super(children);
139         this.className = className;
140     }
141
142     protected buildDOM(): HTMLElement {
143         return el('div', {
144             class: this.className,
145         }, this.getChildren().map(child => child.getDOMElement()));
146     }
147 }
148