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:
parent
6e852d2e65
commit
49546cd627
6 changed files with 62 additions and 30 deletions
|
@ -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
16
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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');
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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,
|
Loading…
Add table
Add a link
Reference in a new issue