]> BookStack Code Mirror - bookstack/blobdiff - resources/js/wysiwyg/ui/framework/forms.ts
Lexical: Started adding editor shortcuts
[bookstack] / resources / js / wysiwyg / ui / framework / forms.ts
index b641f993bd1a4bca881884e2aa48cf892e6bb68f..a2db34dd7a12ade9f94b6a5a4deedbb68e41e7b4 100644 (file)
@@ -1,5 +1,12 @@
-import {EditorUiContext, EditorUiElement, EditorContainerUiElement} from "./core";
-import {el} from "../../helpers";
+import {
+    EditorUiContext,
+    EditorUiElement,
+    EditorContainerUiElement,
+    EditorUiBuilderDefinition,
+    isUiBuilderDefinition
+} from "./core";
+import {uniqueId} from "../../../services/util";
+import {el} from "../../utils/dom";
 
 export interface EditorFormFieldDefinition {
     label: string;
@@ -12,10 +19,15 @@ export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefiniti
     valuesByLabel: Record<string, string>
 }
 
+interface EditorFormTabDefinition {
+    label: string;
+    contents: EditorFormFieldDefinition[];
+}
+
 export interface EditorFormDefinition {
     submitText: string;
     action: (formData: FormData, context: EditorUiContext) => Promise<boolean>;
-    fields: EditorFormFieldDefinition[];
+    fields: (EditorFormFieldDefinition|EditorUiBuilderDefinition)[];
 }
 
 export class EditorFormField extends EditorUiElement {
@@ -62,7 +74,14 @@ export class EditorForm extends EditorContainerUiElement {
     protected onCancel: null|(() => void) = null;
 
     constructor(definition: EditorFormDefinition) {
-        super(definition.fields.map(fieldDefinition => new EditorFormField(fieldDefinition)));
+        let children: (EditorFormField|EditorUiElement)[] = definition.fields.map(fieldDefinition => {
+            if (isUiBuilderDefinition(fieldDefinition)) {
+                return fieldDefinition.build();
+            }
+            return new EditorFormField(fieldDefinition)
+        });
+
+        super(children);
         this.definition = definition;
     }
 
@@ -80,13 +99,23 @@ export class EditorForm extends EditorContainerUiElement {
     }
 
     protected getFieldByName(name: string): EditorFormField|null {
-        for (const child of this.children as EditorFormField[]) {
-            if (child.getName() === name) {
-                return child;
+
+        const search = (children: EditorUiElement[]): EditorFormField|null => {
+            for (const child of children) {
+                if (child instanceof EditorFormField && child.getName() === name) {
+                    return child;
+                } else if (child instanceof EditorContainerUiElement) {
+                    const matchingChild = search(child.getChildren());
+                    if (matchingChild) {
+                        return matchingChild;
+                    }
+                }
             }
-        }
 
-        return null;
+            return null;
+        };
+
+        return search(this.getChildren());
     }
 
     protected buildDOM(): HTMLElement {
@@ -113,4 +142,91 @@ export class EditorForm extends EditorContainerUiElement {
 
         return form;
     }
+}
+
+export class EditorFormTab extends EditorContainerUiElement {
+
+    protected definition: EditorFormTabDefinition;
+    protected fields: EditorFormField[];
+    protected id: string;
+
+    constructor(definition: EditorFormTabDefinition) {
+        const fields = definition.contents.map(fieldDef => new EditorFormField(fieldDef));
+        super(fields);
+
+        this.definition = definition;
+        this.fields = fields;
+        this.id = uniqueId();
+    }
+
+    public getLabel(): string {
+        return this.getContext().translate(this.definition.label);
+    }
+
+    public getId(): string {
+        return this.id;
+    }
+
+    protected buildDOM(): HTMLElement {
+        return el(
+            'div',
+            {
+                class: 'editor-form-tab-content',
+                role: 'tabpanel',
+                id: `editor-tabpanel-${this.id}`,
+                'aria-labelledby': `editor-tab-${this.id}`,
+            },
+            this.fields.map(f => f.getDOMElement())
+        );
+    }
+}
+export class EditorFormTabs extends EditorContainerUiElement {
+
+    protected definitions: EditorFormTabDefinition[] = [];
+    protected tabs: EditorFormTab[] = [];
+
+    constructor(definitions: EditorFormTabDefinition[]) {
+        const tabs: EditorFormTab[] = definitions.map(d => new EditorFormTab(d));
+        super(tabs);
+
+        this.definitions = definitions;
+        this.tabs = tabs;
+    }
+
+    protected buildDOM(): HTMLElement {
+        const controls: HTMLElement[] = [];
+        const contents: HTMLElement[] = [];
+
+        const selectTab = (tabIndex: number) => {
+            for (let i = 0; i < controls.length; i++) {
+                controls[i].setAttribute('aria-selected', (i === tabIndex) ? 'true' : 'false');
+            }
+            for (let i = 0; i < contents.length; i++) {
+                contents[i].hidden = !(i === tabIndex);
+            }
+        };
+
+        for (const tab of this.tabs) {
+            const button = el('button', {
+                class: 'editor-form-tab-control',
+                type: 'button',
+                role: 'tab',
+                id: `editor-tab-${tab.getId()}`,
+                'aria-controls': `editor-tabpanel-${tab.getId()}`
+            }, [tab.getLabel()]);
+            contents.push(tab.getDOMElement());
+            controls.push(button);
+
+            button.addEventListener('click', event => {
+                selectTab(controls.indexOf(button));
+            });
+        }
+
+        selectTab(0);
+
+        return el('div', {class: 'editor-form-tab-container'}, [
+            el('div', {class: 'editor-form-tab-controls'}, controls),
+            el('div', {class: 'editor-form-tab-contents'}, contents),
+        ]);
+    }
 }
\ No newline at end of file