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