]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/buttons.ts
Lexical: Fixed issues with recent changes
[bookstack] / resources / js / wysiwyg / ui / framework / buttons.ts
1 import {BaseSelection} from "lexical";
2 import {EditorUiContext, EditorUiElement, EditorUiStateUpdate} from "./core";
3
4 import {el} from "../../utils/dom";
5
6 export interface EditorBasicButtonDefinition {
7     label: string;
8     icon?: string|undefined;
9     format?: 'small' | 'long';
10 }
11
12 export interface EditorButtonDefinition extends EditorBasicButtonDefinition {
13     /**
14      * The action to perform when the button is used.
15      * This can return false to indicate that the completion of the action should
16      * NOT be communicated to parent UI elements, which is what occurs by default.
17      */
18     action: (context: EditorUiContext, button: EditorButton) => void|false|Promise<void|boolean>;
19     isActive: (selection: BaseSelection|null, context: EditorUiContext) => boolean;
20     isDisabled?: (selection: BaseSelection|null, context: EditorUiContext) => boolean;
21     setup?: (context: EditorUiContext, button: EditorButton) => void;
22 }
23
24 export class EditorButton extends EditorUiElement {
25     protected definition: EditorButtonDefinition;
26     protected active: boolean = false;
27     protected completedSetup: boolean = false;
28     protected disabled: boolean = false;
29
30     constructor(definition: EditorButtonDefinition|EditorBasicButtonDefinition) {
31         super();
32
33         if ((definition as EditorButtonDefinition).action !== undefined) {
34             this.definition = definition as EditorButtonDefinition;
35         } else {
36             this.definition = {
37                 ...definition,
38                 action() {
39                     return false;
40                 },
41                 isActive: () => {
42                     return false;
43                 }
44             };
45         }
46     }
47
48     setContext(context: EditorUiContext) {
49         super.setContext(context);
50
51         if (this.definition.setup && !this.completedSetup) {
52             this.definition.setup(context, this);
53             this.completedSetup = true;
54         }
55     }
56
57     protected buildDOM(): HTMLButtonElement {
58         const label = this.getLabel();
59         const format = this.definition.format || 'small';
60         const children: (string|HTMLElement)[] = [];
61
62         if (this.definition.icon || format === 'long') {
63             const icon = el('div', {class: 'editor-button-icon'});
64             icon.innerHTML = this.definition.icon || '';
65             children.push(icon);
66         }
67
68         if (!this.definition.icon ||format === 'long') {
69             const text = el('div', {class: 'editor-button-text'}, [label]);
70             children.push(text);
71         }
72
73         const button = el('button', {
74             type: 'button',
75             class: `editor-button editor-button-${format}`,
76             title: this.definition.icon ? label : null,
77             disabled: this.disabled ? 'true' : null,
78         }, children) as HTMLButtonElement;
79
80         button.addEventListener('click', this.onClick.bind(this));
81
82         return button;
83     }
84
85     protected onClick() {
86         const result = this.definition.action(this.getContext(), this);
87         if (result instanceof Promise) {
88             result.then(result => {
89                 if (result === false) {
90                     this.emitEvent('button-action');
91                 }
92             });
93         } else if (result !== false) {
94             this.emitEvent('button-action');
95         }
96     }
97
98     protected updateActiveState(selection: BaseSelection|null) {
99         const isActive = this.definition.isActive(selection, this.getContext());
100         this.setActiveState(isActive);
101     }
102
103     protected updateDisabledState(selection: BaseSelection|null) {
104         if (this.definition.isDisabled) {
105             const isDisabled = this.definition.isDisabled(selection, this.getContext());
106             this.toggleDisabled(isDisabled);
107         }
108     }
109
110     setActiveState(active: boolean) {
111         this.active = active;
112         this.dom?.classList.toggle('editor-button-active', this.active);
113     }
114
115     updateState(state: EditorUiStateUpdate): void {
116         this.updateActiveState(state.selection);
117         this.updateDisabledState(state.selection);
118     }
119
120     isActive(): boolean {
121         return this.active;
122     }
123
124     getLabel(): string {
125         return this.trans(this.definition.label);
126     }
127
128     toggleDisabled(disabled: boolean) {
129         this.disabled = disabled;
130         if (disabled) {
131             this.dom?.setAttribute('disabled', 'true');
132         } else {
133             this.dom?.removeAttribute('disabled');
134         }
135     }
136 }