]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/buttons.ts
0e1cab0f569a7d64a96c7b18669dd6985ae8c514
[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;
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 !== false) {
88             this.emitEvent('button-action');
89         }
90     }
91
92     protected updateActiveState(selection: BaseSelection|null) {
93         const isActive = this.definition.isActive(selection, this.getContext());
94         this.setActiveState(isActive);
95     }
96
97     protected updateDisabledState(selection: BaseSelection|null) {
98         if (this.definition.isDisabled) {
99             const isDisabled = this.definition.isDisabled(selection, this.getContext());
100             this.toggleDisabled(isDisabled);
101         }
102     }
103
104     setActiveState(active: boolean) {
105         this.active = active;
106         this.dom?.classList.toggle('editor-button-active', this.active);
107     }
108
109     updateState(state: EditorUiStateUpdate): void {
110         this.updateActiveState(state.selection);
111         this.updateDisabledState(state.selection);
112     }
113
114     isActive(): boolean {
115         return this.active;
116     }
117
118     getLabel(): string {
119         return this.trans(this.definition.label);
120     }
121
122     toggleDisabled(disabled: boolean) {
123         this.disabled = disabled;
124         if (disabled) {
125             this.dom?.setAttribute('disabled', 'true');
126         } else {
127             this.dom?.removeAttribute('disabled');
128         }
129     }
130 }