]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/core.ts
90ce4ebf93cb4fc8a1ad44497206370c279b8b6f
[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
72 export class EditorContainerUiElement extends EditorUiElement {
73     protected children : EditorUiElement[] = [];
74
75     constructor(children: EditorUiElement[]) {
76         super();
77         this.children.push(...children);
78     }
79
80     protected buildDOM(): HTMLElement {
81         return el('div', {}, this.getChildren().map(child => child.getDOMElement()));
82     }
83
84     getChildren(): EditorUiElement[] {
85         return this.children;
86     }
87
88     protected addChildren(...children: EditorUiElement[]): void {
89         this.children.push(...children);
90     }
91
92     protected removeChildren(...children: EditorUiElement[]): void {
93         for (const child of children) {
94             this.removeChild(child);
95         }
96     }
97
98     protected removeChild(child: EditorUiElement) {
99         const index = this.children.indexOf(child);
100         if (index !== -1) {
101             this.children.splice(index, 1);
102         }
103     }
104
105     updateState(state: EditorUiStateUpdate): void {
106         for (const child of this.children) {
107             child.updateState(state);
108         }
109     }
110
111     setContext(context: EditorUiContext) {
112         super.setContext(context);
113         for (const child of this.getChildren()) {
114             child.setContext(context);
115         }
116     }
117 }
118
119 export class EditorSimpleClassContainer extends EditorContainerUiElement {
120     protected className;
121
122     constructor(className: string, children: EditorUiElement[]) {
123         super(children);
124         this.className = className;
125     }
126
127     protected buildDOM(): HTMLElement {
128         return el('div', {
129             class: this.className,
130         }, this.getChildren().map(child => child.getDOMElement()));
131     }
132 }
133