1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-07-21 09:22:09 +03:00
Files
bookstack/resources/js/wysiwyg/index.ts
Dan Brown b80992ca59 Comments: Switched to lexical editor
Required a lot of changes to provide at least a decent attempt at proper
editor teardown control.
Also updates HtmlDescriptionFilter and testing to address issue with bad
child iteration which could lead to missed items.
Renamed editor version from comments to basic as it'll also be used for
item descriptions.
2025-06-25 14:16:01 +01:00

143 lines
5.5 KiB
TypeScript

import {createEditor, LexicalEditor} from 'lexical';
import {createEmptyHistoryState, registerHistory} from '@lexical/history';
import {registerRichText} from '@lexical/rich-text';
import {mergeRegister} from '@lexical/utils';
import {getNodesForBasicEditor, getNodesForPageEditor, registerCommonNodeMutationListeners} from './nodes';
import {buildEditorUI} from "./ui";
import {focusEditor, getEditorContentAsHtml, setEditorContentFromHtml} from "./utils/actions";
import {registerTableResizer} from "./ui/framework/helpers/table-resizer";
import {EditorUiContext} from "./ui/framework/core";
import {listen as listenToCommonEvents} from "./services/common-events";
import {registerDropPasteHandling} from "./services/drop-paste-handling";
import {registerTaskListHandler} from "./ui/framework/helpers/task-list-handler";
import {registerTableSelectionHandler} from "./ui/framework/helpers/table-selection-handler";
import {registerShortcuts} from "./services/shortcuts";
import {registerNodeResizer} from "./ui/framework/helpers/node-resizer";
import {registerKeyboardHandling} from "./services/keyboard-handling";
import {registerAutoLinks} from "./services/auto-links";
import {contextToolbars, getBasicEditorToolbar, getMainEditorFullToolbar} from "./ui/defaults/toolbars";
import {modals} from "./ui/defaults/modals";
import {CodeBlockDecorator} from "./ui/decorators/code-block";
import {DiagramDecorator} from "./ui/decorators/diagram";
const theme = {
text: {
bold: 'editor-theme-bold',
code: 'editor-theme-code',
italic: 'editor-theme-italic',
strikethrough: 'editor-theme-strikethrough',
subscript: 'editor-theme-subscript',
superscript: 'editor-theme-superscript',
underline: 'editor-theme-underline',
underlineStrikethrough: 'editor-theme-underline-strikethrough',
}
};
export function createPageEditorInstance(container: HTMLElement, htmlContent: string, options: Record<string, any> = {}): SimpleWysiwygEditorInterface {
const editor = createEditor({
namespace: 'BookStackPageEditor',
nodes: getNodesForPageEditor(),
onError: console.error,
theme: theme,
});
const context: EditorUiContext = buildEditorUI(container, editor, {
...options,
editorClass: 'page-content',
});
editor.setRootElement(context.editorDOM);
mergeRegister(
registerRichText(editor),
registerHistory(editor, createEmptyHistoryState(), 300),
registerShortcuts(context),
registerKeyboardHandling(context),
registerTableResizer(editor, context.scrollDOM),
registerTableSelectionHandler(editor),
registerTaskListHandler(editor, context.editorDOM),
registerDropPasteHandling(context),
registerNodeResizer(context),
registerAutoLinks(editor),
);
// Register toolbars, modals & decorators
context.manager.setToolbar(getMainEditorFullToolbar(context));
for (const key of Object.keys(contextToolbars)) {
context.manager.registerContextToolbar(key, contextToolbars[key]);
}
for (const key of Object.keys(modals)) {
context.manager.registerModal(key, modals[key]);
}
context.manager.registerDecoratorType('code', CodeBlockDecorator);
context.manager.registerDecoratorType('diagram', DiagramDecorator);
listenToCommonEvents(editor);
setEditorContentFromHtml(editor, htmlContent);
const debugView = document.getElementById('lexical-debug');
if (debugView) {
debugView.hidden = true;
editor.registerUpdateListener(({dirtyElements, dirtyLeaves, editorState, prevEditorState}) => {
// Debug logic
// console.log('editorState', editorState.toJSON());
debugView.textContent = JSON.stringify(editorState.toJSON(), null, 2);
});
}
// @ts-ignore
window.debugEditorState = () => {
return editor.getEditorState().toJSON();
};
registerCommonNodeMutationListeners(context);
return new SimpleWysiwygEditorInterface(context);
}
export function createBasicEditorInstance(container: HTMLElement, htmlContent: string, options: Record<string, any> = {}): SimpleWysiwygEditorInterface {
const editor = createEditor({
namespace: 'BookStackBasicEditor',
nodes: getNodesForBasicEditor(),
onError: console.error,
theme: theme,
});
const context: EditorUiContext = buildEditorUI(container, editor, options);
editor.setRootElement(context.editorDOM);
const editorTeardown = mergeRegister(
registerRichText(editor),
registerHistory(editor, createEmptyHistoryState(), 300),
registerShortcuts(context),
registerAutoLinks(editor),
);
// Register toolbars, modals & decorators
context.manager.setToolbar(getBasicEditorToolbar(context));
context.manager.registerContextToolbar('link', contextToolbars.link);
context.manager.registerModal('link', modals.link);
context.manager.onTeardown(editorTeardown);
setEditorContentFromHtml(editor, htmlContent);
return new SimpleWysiwygEditorInterface(context);
}
export class SimpleWysiwygEditorInterface {
protected context: EditorUiContext;
constructor(context: EditorUiContext) {
this.context = context;
}
async getContentAsHtml(): Promise<string> {
return await getEditorContentAsHtml(this.context.editor);
}
focus(): void {
focusEditor(this.context.editor);
}
remove() {
this.context.editorDOM.remove();
this.context.manager.teardown();
}
}