diff --git a/resources/js/services/events.ts b/resources/js/services/events.ts index 7d72a9f1a..32c70f5a8 100644 --- a/resources/js/services/events.ts +++ b/resources/js/services/events.ts @@ -1,7 +1,7 @@ import {HttpError} from "./http"; export class EventManager { - protected listeners: Record<string, ((data: {}) => void)[]> = {}; + protected listeners: Record<string, ((data: any) => void)[]> = {}; 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(eventName: string, callback: (data: {}) => void): void { + listen<T>(eventName: string, callback: (data: T) => void): void { if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = []; this.listeners[eventName].push(callback); } diff --git a/resources/js/wysiwyg/actions.ts b/resources/js/wysiwyg/actions.ts index 3a32b82d8..0e2202525 100644 --- a/resources/js/wysiwyg/actions.ts +++ b/resources/js/wysiwyg/actions.ts @@ -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 {$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) { - const parser = new DOMParser(); - const dom = parser.parseFromString(html, 'text/html'); + const dom = htmlToDom(html); - console.log(html); editor.update(() => { // Empty existing const root = $getRoot(); @@ -16,18 +33,52 @@ export function setEditorContentFromHtml(editor: LexicalEditor, html: string) { } const nodes = $generateNodesFromDOM(editor, dom); + root.append(...wrapTextNodes(nodes)); + }); +} - // Wrap top-level text nodes - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - if ($isTextNode(node)) { - const paragraph = $createCustomParagraphNode(); - paragraph.append(node); - nodes[i] = paragraph; +export function appendHtmlToEditor(editor: LexicalEditor, html: string) { + const dom = htmlToDom(html); + + editor.update(() => { + const root = $getRoot(); + const nodes = $generateNodesFromDOM(editor, dom); + 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); }); }); +} + +export function focusEditor(editor: LexicalEditor) { + editor.focus(() => {}, {defaultSelection: "rootStart"}); } \ No newline at end of file diff --git a/resources/js/wysiwyg/common-events.ts b/resources/js/wysiwyg/common-events.ts new file mode 100644 index 000000000..7355d977b --- /dev/null +++ b/resources/js/wysiwyg/common-events.ts @@ -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); + }); +} diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index 469738e7f..e53b9b057 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -8,6 +8,7 @@ import {getEditorContentAsHtml, setEditorContentFromHtml} from "./actions"; import {registerTableResizer} from "./ui/framework/helpers/table-resizer"; import {el} from "./helpers"; 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 { const config: CreateEditorArgs = { @@ -47,6 +48,8 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st registerTableResizer(editor, editWrap), ); + listenToCommonEvents(editor); + setEditorContentFromHtml(editor, htmlContent); const debugView = document.getElementById('lexical-debug'); diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index e39a4c655..c62a6e524 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -21,7 +21,6 @@ - Table features - Image paste upload - Keyboard shortcuts support -- Global/shared editor events support - Draft/change management (connect with page editor component) - Add ID support to all block types - Template drag & drop / insert