mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Added common events support
This commit is contained in:
parent
2cab778f19
commit
76b0d2d5d8
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
root.append(...wrapTextNodes(nodes));
|
||||
});
|
||||
}
|
||||
|
||||
root.append(...nodes);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -39,3 +90,7 @@ export function getEditorContentAsHtml(editor: LexicalEditor): Promise<string> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function focusEditor(editor: LexicalEditor) {
|
||||
editor.focus(() => {}, {defaultSelection: "rootStart"});
|
||||
}
|
43
resources/js/wysiwyg/common-events.ts
Normal file
43
resources/js/wysiwyg/common-events.ts
Normal 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);
|
||||
});
|
||||
}
|
@ -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');
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user