]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/forms.ts
Lexical: Added view/edit source code button/form/action
[bookstack] / resources / js / wysiwyg / ui / framework / forms.ts
1 import {EditorUiContext, EditorUiElement} from "./core";
2 import {EditorContainerUiElement} from "./containers";
3 import {el} from "../../helpers";
4
5 export interface EditorFormFieldDefinition {
6     label: string;
7     name: string;
8     type: 'text' | 'select' | 'textarea';
9 }
10
11 export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition {
12     type: 'select',
13     valuesByLabel: Record<string, string>
14 }
15
16 export interface EditorFormDefinition {
17     submitText: string;
18     action: (formData: FormData, context: EditorUiContext) => boolean;
19     fields: EditorFormFieldDefinition[];
20 }
21
22 export class EditorFormField extends EditorUiElement {
23     protected definition: EditorFormFieldDefinition;
24
25     constructor(definition: EditorFormFieldDefinition) {
26         super();
27         this.definition = definition;
28     }
29
30     setValue(value: string) {
31         const input = this.getDOMElement().querySelector('input,select,textarea') as HTMLInputElement;
32         input.value = value;
33     }
34
35     getName(): string {
36         return this.definition.name;
37     }
38
39     protected buildDOM(): HTMLElement {
40         const id = `editor-form-field-${this.definition.name}-${Date.now()}`;
41         let input: HTMLElement;
42
43         if (this.definition.type === 'select') {
44             const options = (this.definition as EditorSelectFormFieldDefinition).valuesByLabel
45             const labels = Object.keys(options);
46             const optionElems = labels.map(label => el('option', {value: options[label]}, [label]));
47             input = el('select', {id, name: this.definition.name, class: 'editor-form-field-input'}, optionElems);
48         } else if (this.definition.type === 'textarea') {
49             input = el('textarea', {id, name: this.definition.name, class: 'editor-form-field-input'});
50         } else {
51             input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'});
52         }
53
54         return el('div', {class: 'editor-form-field-wrapper'}, [
55             el('label', {class: 'editor-form-field-label', for: id}, [this.trans(this.definition.label)]),
56             input,
57         ]);
58     }
59 }
60
61 export class EditorForm extends EditorContainerUiElement {
62     protected definition: EditorFormDefinition;
63     protected onCancel: null|(() => void) = null;
64
65     constructor(definition: EditorFormDefinition) {
66         super(definition.fields.map(fieldDefinition => new EditorFormField(fieldDefinition)));
67         this.definition = definition;
68     }
69
70     setValues(values: Record<string, string>) {
71         for (const name of Object.keys(values)) {
72             const field = this.getFieldByName(name);
73             if (field) {
74                 field.setValue(values[name]);
75             }
76         }
77     }
78
79     setOnCancel(callback: () => void) {
80         this.onCancel = callback;
81     }
82
83     protected getFieldByName(name: string): EditorFormField|null {
84         for (const child of this.children as EditorFormField[]) {
85             if (child.getName() === name) {
86                 return child;
87             }
88         }
89
90         return null;
91     }
92
93     protected buildDOM(): HTMLElement {
94         const cancelButton = el('button', {type: 'button', class: 'editor-form-action-secondary'}, [this.trans('Cancel')]);
95         const form = el('form', {}, [
96             ...this.children.map(child => child.getDOMElement()),
97             el('div', {class: 'editor-form-actions'}, [
98                 cancelButton,
99                 el('button', {type: 'submit', class: 'editor-form-action-primary'}, [this.trans(this.definition.submitText)]),
100             ])
101         ]);
102
103         form.addEventListener('submit', (event) => {
104             event.preventDefault();
105             const formData = new FormData(form as HTMLFormElement);
106             this.definition.action(formData, this.getContext());
107         });
108
109         cancelButton.addEventListener('click', (event) => {
110             if (this.onCancel) {
111                 this.onCancel();
112             }
113         });
114
115         return form;
116     }
117 }