]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/buttons.ts
cf114aa021256c9e075591a06331500c88162580
[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     action: (context: EditorUiContext, button: EditorButton) => void;
14     isActive: (selection: BaseSelection|null, context: EditorUiContext) => boolean;
15     isDisabled?: (selection: BaseSelection|null, context: EditorUiContext) => boolean;
16     setup?: (context: EditorUiContext, button: EditorButton) => void;
17 }
18
19 export class EditorButton extends EditorUiElement {
20     protected definition: EditorButtonDefinition;
21     protected active: boolean = false;
22     protected completedSetup: boolean = false;
23     protected disabled: boolean = false;
24
25     constructor(definition: EditorButtonDefinition|EditorBasicButtonDefinition) {
26         super();
27
28         if ((definition as EditorButtonDefinition).action !== undefined) {
29             this.definition = definition as EditorButtonDefinition;
30         } else {
31             this.definition = {
32                 ...definition,
33                 action() {
34                     return false;
35                 },
36                 isActive: () => {
37                     return false;
38                 }
39             };
40         }
41     }
42
43     setContext(context: EditorUiContext) {
44         super.setContext(context);
45
46         if (this.definition.setup && !this.completedSetup) {
47             this.definition.setup(context, this);
48             this.completedSetup = true;
49         }
50     }
51
52     protected buildDOM(): HTMLButtonElement {
53         const label = this.getLabel();
54         const format = this.definition.format || 'small';
55         const children: (string|HTMLElement)[] = [];
56
57         if (this.definition.icon || format === 'long') {
58             const icon = el('div', {class: 'editor-button-icon'});
59             icon.innerHTML = this.definition.icon || '';
60             children.push(icon);
61         }
62
63         if (!this.definition.icon ||format === 'long') {
64             const text = el('div', {class: 'editor-button-text'}, [label]);
65             children.push(text);
66         }
67
68         const button = el('button', {
69             type: 'button',
70             class: `editor-button editor-button-${format}`,
71             title: this.definition.icon ? label : null,
72             disabled: this.disabled ? 'true' : null,
73         }, children) as HTMLButtonElement;
74
75         button.addEventListener('click', this.onClick.bind(this));
76
77         return button;
78     }
79
80     protected onClick() {
81         this.definition.action(this.getContext(), this);
82     }
83
84     protected updateActiveState(selection: BaseSelection|null) {
85         const isActive = this.definition.isActive(selection, this.getContext());
86         this.setActiveState(isActive);
87     }
88
89     protected updateDisabledState(selection: BaseSelection|null) {
90         if (this.definition.isDisabled) {
91             const isDisabled = this.definition.isDisabled(selection, this.getContext());
92             this.toggleDisabled(isDisabled);
93         }
94     }
95
96     setActiveState(active: boolean) {
97         this.active = active;
98         this.dom?.classList.toggle('editor-button-active', this.active);
99     }
100
101     updateState(state: EditorUiStateUpdate): void {
102         this.updateActiveState(state.selection);
103         this.updateDisabledState(state.selection);
104     }
105
106     isActive(): boolean {
107         return this.active;
108     }
109
110     getLabel(): string {
111         return this.trans(this.definition.label);
112     }
113
114     toggleDisabled(disabled: boolean) {
115         this.disabled = disabled;
116         if (disabled) {
117             this.dom?.setAttribute('disabled', 'true');
118         } else {
119             this.dom?.removeAttribute('disabled');
120         }
121     }
122 }