]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/blocks/link-field.ts
Lexical: Added color picker/indicator to form fields
[bookstack] / resources / js / wysiwyg / ui / framework / blocks / link-field.ts
1 import {EditorContainerUiElement} from "../core";
2 import {el} from "../../../utils/dom";
3 import {EditorFormField} from "../forms";
4 import {$getAllNodesOfType} from "../../../utils/nodes";
5 import {uniqueIdSmall} from "../../../../services/util";
6 import {$isHeadingNode, HeadingNode} from "@lexical/rich-text/LexicalHeadingNode";
7
8 export class LinkField extends EditorContainerUiElement {
9     protected input: EditorFormField;
10     protected headerMap = new Map<string, HeadingNode>();
11
12     constructor(input: EditorFormField) {
13         super([input]);
14
15         this.input = input;
16     }
17
18     buildDOM(): HTMLElement {
19         const listId = 'editor-form-datalist-' + this.input.getName() + '-' + Date.now();
20         const inputOuterDOM = this.input.getDOMElement();
21         const inputFieldDOM = inputOuterDOM.querySelector('input');
22         inputFieldDOM?.setAttribute('list', listId);
23         inputFieldDOM?.setAttribute('autocomplete', 'off');
24         const datalist = el('datalist', {id: listId});
25
26         const container = el('div', {
27             class: 'editor-link-field-container',
28         }, [inputOuterDOM, datalist]);
29
30         inputFieldDOM?.addEventListener('focusin', () => {
31             this.updateDataList(datalist);
32         });
33
34         inputFieldDOM?.addEventListener('input', () => {
35             const value = inputFieldDOM.value;
36             const header = this.headerMap.get(value);
37             if (header) {
38                 this.updateFormFromHeader(header);
39             }
40         });
41
42         return container;
43     }
44
45     updateFormFromHeader(header: HeadingNode) {
46         this.getHeaderIdAndText(header).then(({id, text}) => {
47             const modal =  this.getContext().manager.getActiveModal('link');
48             if (modal) {
49                 modal.getForm().setValues({
50                     url: `#${id}`,
51                     text: text,
52                     title: text,
53                 });
54             }
55         });
56     }
57
58     getHeaderIdAndText(header: HeadingNode): Promise<{id: string, text: string}> {
59         return new Promise((res) => {
60             this.getContext().editor.update(() => {
61                 let id = header.getId();
62                 if (!id) {
63                     id = 'header-' + uniqueIdSmall();
64                     header.setId(id);
65                 }
66
67                 const text = header.getTextContent();
68                 res({id, text});
69             });
70         });
71     }
72
73     updateDataList(listEl: HTMLElement) {
74         this.getContext().editor.getEditorState().read(() => {
75             const headers = $getAllNodesOfType($isHeadingNode) as HeadingNode[];
76
77             this.headerMap.clear();
78             const listEls: HTMLElement[] = [];
79
80             for (const header of headers) {
81                 const key = 'header-' + header.getKey();
82                 this.headerMap.set(key, header);
83                 listEls.push(el('option', {
84                     value: key,
85                     label: header.getTextContent().substring(0, 54),
86                 }));
87             }
88
89             listEl.innerHTML = '';
90             listEl.append(...listEls);
91         });
92     }
93 }