--- /dev/null
+import {EditorContainerUiElement} from "../core";
+import {el} from "../../../utils/dom";
+import {EditorFormField} from "../forms";
+import {CustomHeadingNode} from "../../../nodes/custom-heading";
+import {$getAllNodesOfType} from "../../../utils/nodes";
+import {$isHeadingNode} from "@lexical/rich-text";
+import {uniqueIdSmall} from "../../../../services/util";
+
+export class LinkField extends EditorContainerUiElement {
+ protected input: EditorFormField;
+ protected headerMap = new Map<string, CustomHeadingNode>();
+
+ constructor(input: EditorFormField) {
+ super([input]);
+
+ this.input = input;
+ }
+
+ buildDOM(): HTMLElement {
+ const listId = 'editor-form-datalist-' + this.input.getName() + '-' + Date.now();
+ const inputOuterDOM = this.input.getDOMElement();
+ const inputFieldDOM = inputOuterDOM.querySelector('input');
+ inputFieldDOM?.setAttribute('list', listId);
+ inputFieldDOM?.setAttribute('autocomplete', 'off');
+ const datalist = el('datalist', {id: listId});
+
+ const container = el('div', {
+ class: 'editor-link-field-container',
+ }, [inputOuterDOM, datalist]);
+
+ inputFieldDOM?.addEventListener('focusin', () => {
+ this.updateDataList(datalist);
+ });
+
+ inputFieldDOM?.addEventListener('input', () => {
+ const value = inputFieldDOM.value;
+ const header = this.headerMap.get(value);
+ if (header) {
+ this.updateFormFromHeader(header);
+ }
+ });
+
+ return container;
+ }
+
+ updateFormFromHeader(header: CustomHeadingNode) {
+ this.getHeaderIdAndText(header).then(({id, text}) => {
+ console.log('updating form', id, text);
+ const modal = this.getContext().manager.getActiveModal('link');
+ if (modal) {
+ modal.getForm().setValues({
+ url: `#${id}`,
+ text: text,
+ title: text,
+ });
+ }
+ });
+ }
+
+ getHeaderIdAndText(header: CustomHeadingNode): Promise<{id: string, text: string}> {
+ return new Promise((res) => {
+ this.getContext().editor.update(() => {
+ let id = header.getId();
+ console.log('header', id, header.__id);
+ if (!id) {
+ id = 'header-' + uniqueIdSmall();
+ header.setId(id);
+ }
+
+ const text = header.getTextContent();
+ res({id, text});
+ });
+ });
+ }
+
+ updateDataList(listEl: HTMLElement) {
+ this.getContext().editor.getEditorState().read(() => {
+ const headers = $getAllNodesOfType($isHeadingNode) as CustomHeadingNode[];
+
+ this.headerMap.clear();
+ const listEls: HTMLElement[] = [];
+
+ for (const header of headers) {
+ const key = 'header-' + header.getKey();
+ this.headerMap.set(key, header);
+ listEls.push(el('option', {
+ value: key,
+ label: header.getTextContent().substring(0, 54),
+ }));
+ }
+
+ listEl.innerHTML = '';
+ listEl.append(...listEls);
+ });
+ }
+}
-import {$getRoot, $isTextNode, LexicalEditor, LexicalNode} from "lexical";
+import {$getRoot, $isElementNode, $isTextNode, ElementNode, LexicalEditor, LexicalNode} from "lexical";
import {LexicalNodeMatcher} from "../nodes";
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
import {$generateNodesFromDOM} from "@lexical/html";
return null;
}
+export function $getAllNodesOfType(matcher: LexicalNodeMatcher, root?: ElementNode): LexicalNode[] {
+ if (!root) {
+ root = $getRoot();
+ }
+
+ const matches = [];
+
+ for (const child of root.getChildren()) {
+ if (matcher(child)) {
+ matches.push(child);
+ }
+
+ if ($isElementNode(child)) {
+ matches.push(...$getAllNodesOfType(matcher, child));
+ }
+ }
+
+ return matches;
+}
+
/**
* Get the nearest root/block level node for the given position.
*/