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