]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/blocks/link-field.ts
Merge pull request #5349 from BookStackApp/lexical_reorg
[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             console.log('updating form', id, text);
48             const modal =  this.getContext().manager.getActiveModal('link');
49             if (modal) {
50                 modal.getForm().setValues({
51                     url: `#${id}`,
52                     text: text,
53                     title: text,
54                 });
55             }
56         });
57     }
58
59     getHeaderIdAndText(header: HeadingNode): Promise<{id: string, text: string}> {
60         return new Promise((res) => {
61             this.getContext().editor.update(() => {
62                 let id = header.getId();
63                 console.log('header', id, header.__id);
64                 if (!id) {
65                     id = 'header-' + uniqueIdSmall();
66                     header.setId(id);
67                 }
68
69                 const text = header.getTextContent();
70                 res({id, text});
71             });
72         });
73     }
74
75     updateDataList(listEl: HTMLElement) {
76         this.getContext().editor.getEditorState().read(() => {
77             const headers = $getAllNodesOfType($isHeadingNode) as HeadingNode[];
78
79             this.headerMap.clear();
80             const listEls: HTMLElement[] = [];
81
82             for (const header of headers) {
83                 const key = 'header-' + header.getKey();
84                 this.headerMap.set(key, header);
85                 listEls.push(el('option', {
86                     value: key,
87                     label: header.getTextContent().substring(0, 54),
88                 }));
89             }
90
91             listEl.innerHTML = '';
92             listEl.append(...listEls);
93         });
94     }
95 }