Lexical: Added common events support

This commit is contained in:
Dan Brown 2024-07-23 15:35:18 +01:00
parent 2cab778f19
commit 76b0d2d5d8
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
5 changed files with 116 additions and 16 deletions

View File

@ -1,7 +1,7 @@
import {HttpError} from "./http"; import {HttpError} from "./http";
export class EventManager { export class EventManager {
protected listeners: Record<string, ((data: {}) => void)[]> = {}; protected listeners: Record<string, ((data: any) => void)[]> = {};
protected stack: {name: string, data: {}}[] = []; protected stack: {name: string, data: {}}[] = [];
/** /**
@ -19,7 +19,7 @@ export class EventManager {
/** /**
* Listen to a custom event and run the given callback when that event occurs. * Listen to a custom event and run the given callback when that event occurs.
*/ */
listen(eventName: string, callback: (data: {}) => void): void { listen<T>(eventName: string, callback: (data: T) => void): void {
if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = []; if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = [];
this.listeners[eventName].push(callback); this.listeners[eventName].push(callback);
} }

View File

@ -1,13 +1,30 @@
import {$createParagraphNode, $getRoot, $isTextNode, LexicalEditor} from "lexical"; import {$getRoot, $getSelection, $isTextNode, LexicalEditor, LexicalNode, RootNode} from "lexical";
import {$generateHtmlFromNodes, $generateNodesFromDOM} from "@lexical/html"; import {$generateHtmlFromNodes, $generateNodesFromDOM} from "@lexical/html";
import {$createCustomParagraphNode} from "./nodes/custom-paragraph"; import {$createCustomParagraphNode} from "./nodes/custom-paragraph";
function htmlToDom(html: string): Document {
const parser = new DOMParser();
return parser.parseFromString(html, 'text/html');
}
function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] {
return nodes.map(node => {
if ($isTextNode(node)) {
const paragraph = $createCustomParagraphNode();
paragraph.append(node);
return paragraph;
}
return node;
});
}
function appendNodesToRoot(root: RootNode, nodes: LexicalNode[]) {
root.append(...wrapTextNodes(nodes));
}
export function setEditorContentFromHtml(editor: LexicalEditor, html: string) { export function setEditorContentFromHtml(editor: LexicalEditor, html: string) {
const parser = new DOMParser(); const dom = htmlToDom(html);
const dom = parser.parseFromString(html, 'text/html');
console.log(html);
editor.update(() => { editor.update(() => {
// Empty existing // Empty existing
const root = $getRoot(); const root = $getRoot();
@ -16,18 +33,52 @@ export function setEditorContentFromHtml(editor: LexicalEditor, html: string) {
} }
const nodes = $generateNodesFromDOM(editor, dom); const nodes = $generateNodesFromDOM(editor, dom);
root.append(...wrapTextNodes(nodes));
});
}
// Wrap top-level text nodes export function appendHtmlToEditor(editor: LexicalEditor, html: string) {
for (let i = 0; i < nodes.length; i++) { const dom = htmlToDom(html);
const node = nodes[i];
if ($isTextNode(node)) { editor.update(() => {
const paragraph = $createCustomParagraphNode(); const root = $getRoot();
paragraph.append(node); const nodes = $generateNodesFromDOM(editor, dom);
nodes[i] = paragraph; root.append(...wrapTextNodes(nodes));
});
}
export function prependHtmlToEditor(editor: LexicalEditor, html: string) {
const dom = htmlToDom(html);
editor.update(() => {
const root = $getRoot();
const nodes = wrapTextNodes($generateNodesFromDOM(editor, dom));
let reference = root.getChildren()[0];
for (let i = nodes.length - 1; i >= 0; i--) {
if (reference) {
reference.insertBefore(nodes[i]);
} else {
root.append(nodes[i])
}
reference = nodes[i];
}
});
}
export function insertHtmlIntoEditor(editor: LexicalEditor, html: string) {
const dom = htmlToDom(html);
editor.update(() => {
const selection = $getSelection();
const nodes = wrapTextNodes($generateNodesFromDOM(editor, dom));
const reference = selection?.getNodes()[0];
const referencesParents = reference?.getParents() || [];
const topLevel = referencesParents[referencesParents.length - 1];
if (topLevel && reference) {
for (let i = nodes.length - 1; i >= 0; i--) {
reference.insertAfter(nodes[i]);
} }
} }
root.append(...nodes);
}); });
} }
@ -38,4 +89,8 @@ export function getEditorContentAsHtml(editor: LexicalEditor): Promise<string> {
resolve(html); resolve(html);
}); });
}); });
}
export function focusEditor(editor: LexicalEditor) {
editor.focus(() => {}, {defaultSelection: "rootStart"});
} }

View File

@ -0,0 +1,43 @@
import {LexicalEditor} from "lexical";
import {
appendHtmlToEditor,
focusEditor,
insertHtmlIntoEditor,
prependHtmlToEditor,
setEditorContentFromHtml
} from "./actions";
type EditorEventContent = {
html: string;
markdown: string;
};
function getContentToInsert(eventContent: EditorEventContent): string {
return eventContent.html || '';
}
export function listen(editor: LexicalEditor): void {
window.$events.listen<EditorEventContent>('editor::replace', eventContent => {
const html = getContentToInsert(eventContent);
setEditorContentFromHtml(editor, html);
});
window.$events.listen<EditorEventContent>('editor::append', eventContent => {
const html = getContentToInsert(eventContent);
appendHtmlToEditor(editor, html);
});
window.$events.listen<EditorEventContent>('editor::prepend', eventContent => {
const html = getContentToInsert(eventContent);
prependHtmlToEditor(editor, html);
});
window.$events.listen<EditorEventContent>('editor::insert', eventContent => {
const html = getContentToInsert(eventContent);
insertHtmlIntoEditor(editor, html);
});
window.$events.listen<EditorEventContent>('editor::focus', () => {
focusEditor(editor);
});
}

View File

@ -8,6 +8,7 @@ import {getEditorContentAsHtml, setEditorContentFromHtml} from "./actions";
import {registerTableResizer} from "./ui/framework/helpers/table-resizer"; import {registerTableResizer} from "./ui/framework/helpers/table-resizer";
import {el} from "./helpers"; import {el} from "./helpers";
import {EditorUiContext} from "./ui/framework/core"; import {EditorUiContext} from "./ui/framework/core";
import {listen as listenToCommonEvents} from "./common-events";
export function createPageEditorInstance(container: HTMLElement, htmlContent: string, options: Record<string, any> = {}): SimpleWysiwygEditorInterface { export function createPageEditorInstance(container: HTMLElement, htmlContent: string, options: Record<string, any> = {}): SimpleWysiwygEditorInterface {
const config: CreateEditorArgs = { const config: CreateEditorArgs = {
@ -47,6 +48,8 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
registerTableResizer(editor, editWrap), registerTableResizer(editor, editWrap),
); );
listenToCommonEvents(editor);
setEditorContentFromHtml(editor, htmlContent); setEditorContentFromHtml(editor, htmlContent);
const debugView = document.getElementById('lexical-debug'); const debugView = document.getElementById('lexical-debug');

View File

@ -21,7 +21,6 @@
- Table features - Table features
- Image paste upload - Image paste upload
- Keyboard shortcuts support - Keyboard shortcuts support
- Global/shared editor events support
- Draft/change management (connect with page editor component) - Draft/change management (connect with page editor component)
- Add ID support to all block types - Add ID support to all block types
- Template drag & drop / insert - Template drag & drop / insert