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";
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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"});
|
||||||
}
|
}
|
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 {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');
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user