0
0
Fork 0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-05-04 00:09:58 +00:00

Lexical: Switched to ts for new editor build

This commit is contained in:
Dan Brown 2024-05-27 23:50:28 +01:00
parent 6e852d2e65
commit 49546cd627
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
6 changed files with 62 additions and 30 deletions

View file

@ -14,7 +14,7 @@ const entryPoints = {
code: path.join(__dirname, '../../resources/js/code/index.mjs'), code: path.join(__dirname, '../../resources/js/code/index.mjs'),
'legacy-modes': path.join(__dirname, '../../resources/js/code/legacy-modes.mjs'), 'legacy-modes': path.join(__dirname, '../../resources/js/code/legacy-modes.mjs'),
markdown: path.join(__dirname, '../../resources/js/markdown/index.mjs'), markdown: path.join(__dirname, '../../resources/js/markdown/index.mjs'),
wysiwyg: path.join(__dirname, '../../resources/js/wysiwyg/index.mjs'), wysiwyg: path.join(__dirname, '../../resources/js/wysiwyg/index.ts'),
}; };
// Locate our output directory // Locate our output directory

16
package-lock.json generated
View file

@ -43,7 +43,8 @@
"eslint-plugin-import": "^2.29.0", "eslint-plugin-import": "^2.29.0",
"livereload": "^0.9.3", "livereload": "^0.9.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"sass": "^1.69.5" "sass": "^1.69.5",
"typescript": "^5.4.5"
} }
}, },
"node_modules/@aashutoshrathi/word-wrap": { "node_modules/@aashutoshrathi/word-wrap": {
@ -4099,6 +4100,19 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/uc.micro": { "node_modules/uc.micro": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",

View file

@ -25,7 +25,8 @@
"eslint-plugin-import": "^2.29.0", "eslint-plugin-import": "^2.29.0",
"livereload": "^0.9.3", "livereload": "^0.9.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"sass": "^1.69.5" "sass": "^1.69.5",
"typescript": "^5.4.5"
}, },
"dependencies": { "dependencies": {
"@codemirror/commands": "^6.3.2", "@codemirror/commands": "^6.3.2",
@ -65,7 +66,8 @@
}, },
"extends": "airbnb-base", "extends": "airbnb-base",
"ignorePatterns": [ "ignorePatterns": [
"resources/**/*-stub.js" "resources/**/*-stub.js",
"resources/**/*.ts"
], ],
"overrides": [], "overrides": [],
"parserOptions": { "parserOptions": {

View file

@ -4,18 +4,18 @@ import {
$getSelection, $getSelection,
COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_LOW,
createCommand, createCommand,
createEditor createEditor, CreateEditorArgs,
} from 'lexical'; } from 'lexical';
import {createEmptyHistoryState, registerHistory} from '@lexical/history'; import {createEmptyHistoryState, registerHistory} from '@lexical/history';
import {registerRichText} from '@lexical/rich-text'; import {registerRichText} from '@lexical/rich-text';
import {$getNearestBlockElementAncestorOrThrow, mergeRegister} from '@lexical/utils'; import {$getNearestBlockElementAncestorOrThrow, mergeRegister} from '@lexical/utils';
import {$generateNodesFromDOM} from '@lexical/html'; import {$generateNodesFromDOM} from '@lexical/html';
import {getNodesForPageEditor} from "./nodes/index.js"; import {$setBlocksType} from '@lexical/selection';
import {$createCalloutNode, $isCalloutNode} from "./nodes/callout.js"; import {getNodesForPageEditor} from './nodes';
import {$setBlocksType} from "@lexical/selection"; import {$createCalloutNode, $isCalloutNode, CalloutCategory} from './nodes/callout';
export function createPageEditorInstance(editArea) { export function createPageEditorInstance(editArea: HTMLElement) {
const config = { const config: CreateEditorArgs = {
namespace: 'BookStackPageEditor', namespace: 'BookStackPageEditor',
nodes: getNodesForPageEditor(), nodes: getNodesForPageEditor(),
onError: console.error, onError: console.error,
@ -52,7 +52,7 @@ export function createPageEditorInstance(editArea) {
// Example of creating, registering and using a custom command // Example of creating, registering and using a custom command
const SET_BLOCK_CALLOUT_COMMAND = createCommand(); const SET_BLOCK_CALLOUT_COMMAND = createCommand();
editor.registerCommand(SET_BLOCK_CALLOUT_COMMAND, (category = 'info') => { editor.registerCommand(SET_BLOCK_CALLOUT_COMMAND, (category: CalloutCategory = 'info') => {
const selection = $getSelection(); const selection = $getSelection();
const blockElement = $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]); const blockElement = $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]);
if ($isCalloutNode(blockElement)) { if ($isCalloutNode(blockElement)) {
@ -67,4 +67,4 @@ export function createPageEditorInstance(editArea) {
button.addEventListener('click', event => { button.addEventListener('click', event => {
editor.dispatchCommand(SET_BLOCK_CALLOUT_COMMAND, 'info'); editor.dispatchCommand(SET_BLOCK_CALLOUT_COMMAND, 'info');
}); });
} }

View file

@ -1,35 +1,51 @@
import {$createParagraphNode, ElementNode} from 'lexical'; import {
$createParagraphNode,
DOMConversion,
DOMConversionMap, DOMConversionOutput,
ElementNode,
LexicalEditor,
LexicalNode,
ParagraphNode, SerializedElementNode, Spread
} from 'lexical';
import type {EditorConfig} from "lexical/LexicalEditor";
import type {RangeSelection} from "lexical/LexicalSelection";
export type CalloutCategory = 'info' | 'danger' | 'warning' | 'success';
export type SerializedCalloutNode = Spread<{
category: CalloutCategory;
}, SerializedElementNode>
export class Callout extends ElementNode { export class Callout extends ElementNode {
__category = 'info'; __category: CalloutCategory = 'info';
static getType() { static getType() {
return 'callout'; return 'callout';
} }
static clone(node) { static clone(node: Callout) {
return new Callout(node.__category, node.__key); return new Callout(node.__category, node.__key);
} }
constructor(category, key) { constructor(category: CalloutCategory, key?: string) {
super(key); super(key);
this.__category = category; this.__category = category;
} }
createDOM(_config, _editor) { createDOM(_config: EditorConfig, _editor: LexicalEditor) {
const element = document.createElement('p'); const element = document.createElement('p');
element.classList.add('callout', this.__category || ''); element.classList.add('callout', this.__category || '');
return element; return element;
} }
updateDOM(prevNode, dom) { updateDOM(prevNode: unknown, dom: HTMLElement) {
// Returning false tells Lexical that this node does not need its // Returning false tells Lexical that this node does not need its
// DOM element replacing with a new copy from createDOM. // DOM element replacing with a new copy from createDOM.
return false; return false;
} }
insertNewAfter(selection, restoreSelection) { insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): Callout|ParagraphNode {
const anchorOffset = selection ? selection.anchor.offset : 0; const anchorOffset = selection ? selection.anchor.offset : 0;
const newElement = anchorOffset === this.getTextContentSize() || !selection const newElement = anchorOffset === this.getTextContentSize() || !selection
? $createParagraphNode() : $createCalloutNode(this.__category); ? $createParagraphNode() : $createCalloutNode(this.__category);
@ -46,14 +62,14 @@ export class Callout extends ElementNode {
return newElement; return newElement;
} }
static importDOM() { static importDOM(): DOMConversionMap|null {
return { return {
p: node => { p(node: HTMLElement): DOMConversion|null {
if (node.classList.contains('callout')) { if (node.classList.contains('callout')) {
return { return {
conversion: element => { conversion: (element: HTMLElement): DOMConversionOutput|null => {
let category = 'info'; let category: CalloutCategory = 'info';
const categories = ['info', 'success', 'warning', 'danger']; const categories: CalloutCategory[] = ['info', 'success', 'warning', 'danger'];
for (const c of categories) { for (const c of categories) {
if (element.classList.contains(c)) { if (element.classList.contains(c)) {
@ -74,7 +90,7 @@ export class Callout extends ElementNode {
}; };
} }
exportJSON() { exportJSON(): SerializedCalloutNode {
return { return {
...super.exportJSON(), ...super.exportJSON(),
type: 'callout', type: 'callout',
@ -83,16 +99,16 @@ export class Callout extends ElementNode {
}; };
} }
static importJSON(serializedNode) { static importJSON(serializedNode: SerializedCalloutNode): Callout {
return $createCalloutNode(serializedNode.category); return $createCalloutNode(serializedNode.category);
} }
} }
export function $createCalloutNode(category = 'info') { export function $createCalloutNode(category: CalloutCategory = 'info') {
return new Callout(category); return new Callout(category);
} }
export function $isCalloutNode(node) { export function $isCalloutNode(node: LexicalNode | null | undefined) {
return node instanceof Callout; return node instanceof Callout;
} }

View file

@ -1,11 +1,11 @@
import {HeadingNode, QuoteNode} from '@lexical/rich-text'; import {HeadingNode, QuoteNode} from '@lexical/rich-text';
import {Callout} from './callout'; import {Callout} from './callout';
import {KlassConstructor, LexicalNode} from "lexical";
/** /**
* Load the nodes for lexical. * Load the nodes for lexical.
* @returns {LexicalNode[]}
*/ */
export function getNodesForPageEditor() { export function getNodesForPageEditor(): KlassConstructor<typeof LexicalNode>[] {
return [ return [
Callout, Callout,
HeadingNode, HeadingNode,