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