]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/buttons.ts
Lexical: Added view/edit source code button/form/action
[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 EditorButtonDefinition {
6     label: string;
7     action: (context: EditorUiContext) => void;
8     isActive: (selection: BaseSelection|null) => boolean;
9 }
10
11 export class EditorButton extends EditorUiElement {
12     protected definition: EditorButtonDefinition;
13     protected active: boolean = false;
14
15     constructor(definition: EditorButtonDefinition) {
16         super();
17         this.definition = definition;
18     }
19
20     protected buildDOM(): HTMLButtonElement {
21         const button = el('button', {
22             type: 'button',
23             class: 'editor-button',
24         }, [this.getLabel()]) as HTMLButtonElement;
25
26         button.addEventListener('click', this.onClick.bind(this));
27
28         return button;
29     }
30
31     protected onClick() {
32         this.definition.action(this.getContext());
33     }
34
35     updateActiveState(selection: BaseSelection|null) {
36         this.active = this.definition.isActive(selection);
37         this.dom?.classList.toggle('editor-button-active', this.active);
38     }
39
40     updateState(state: EditorUiStateUpdate): void {
41         this.updateActiveState(state.selection);
42     }
43
44     isActive(): boolean {
45         return this.active;
46     }
47
48     getLabel(): string {
49         return this.trans(this.definition.label);
50     }
51 }
52
53 export class FormatPreviewButton extends EditorButton {
54     protected previewSampleElement: HTMLElement;
55
56     constructor(previewSampleElement: HTMLElement,definition: EditorButtonDefinition) {
57         super(definition);
58         this.previewSampleElement = previewSampleElement;
59     }
60
61     protected buildDOM(): HTMLButtonElement {
62         const button = super.buildDOM();
63         button.innerHTML = '';
64
65         const preview = el('span', {
66             class: 'editor-button-format-preview'
67         }, [this.getLabel()]);
68
69         const stylesToApply = this.getStylesFromPreview();
70         for (const style of Object.keys(stylesToApply)) {
71             preview.style.setProperty(style, stylesToApply[style]);
72         }
73
74         button.append(preview);
75         return button;
76     }
77
78     protected getStylesFromPreview(): Record<string, string> {
79         const wrap = el('div', {style: 'display: none', hidden: 'true', class: 'page-content'});
80         const sampleClone = this.previewSampleElement.cloneNode() as HTMLElement;
81         sampleClone.textContent = this.getLabel();
82         wrap.append(sampleClone);
83         document.body.append(wrap);
84
85         const propertiesToFetch = ['color', 'font-size', 'background-color', 'border-inline-start'];
86         const propertiesToReturn: Record<string, string> = {};
87
88         const computed = window.getComputedStyle(sampleClone);
89         for (const property of propertiesToFetch) {
90             propertiesToReturn[property] = computed.getPropertyValue(property);
91         }
92         wrap.remove();
93
94         return propertiesToReturn;
95     }
96 }