0
0
Fork 0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-04-13 08:21:47 +00:00

Lexical: Added custom id-supporting paragraph blocks

This commit is contained in:
Dan Brown 2024-05-28 15:09:50 +01:00
parent 49546cd627
commit 0f8bd869d8
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
6 changed files with 122 additions and 20 deletions

View file

@ -5,7 +5,7 @@
"build:css:watch": "sass ./resources/sass:./public/dist --watch --embed-sources",
"build:css:production": "sass ./resources/sass:./public/dist -s compressed",
"build:js:dev": "node dev/build/esbuild.js",
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" \"./resources/**/*.mjs\" -c \"npm run build:js:dev\"",
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" \"./resources/**/*.mjs\" \"./resources/**/*.ts\" -c \"npm run build:js:dev\"",
"build:js:production": "node dev/build/esbuild.js production",
"build": "npm-run-all --parallel build:*:dev",
"production": "npm-run-all --parallel build:*:production",

View file

@ -45,10 +45,6 @@ export function createPageEditorInstance(editArea: HTMLElement) {
debugView.textContent = JSON.stringify(editorState.toJSON(), null, 2);
});
// Todo - How can we store things like IDs and alignment?
// Node overrides?
// https://lexical.dev/docs/concepts/node-replacement
// Example of creating, registering and using a custom command
const SET_BLOCK_CALLOUT_COMMAND = createCommand();

View file

@ -16,7 +16,7 @@ export type SerializedCalloutNode = Spread<{
category: CalloutCategory;
}, SerializedElementNode>
export class Callout extends ElementNode {
export class CalloutNode extends ElementNode {
__category: CalloutCategory = 'info';
@ -24,8 +24,8 @@ export class Callout extends ElementNode {
return 'callout';
}
static clone(node: Callout) {
return new Callout(node.__category, node.__key);
static clone(node: CalloutNode) {
return new CalloutNode(node.__category, node.__key);
}
constructor(category: CalloutCategory, key?: string) {
@ -45,7 +45,7 @@ export class Callout extends ElementNode {
return false;
}
insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): Callout|ParagraphNode {
insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): CalloutNode|ParagraphNode {
const anchorOffset = selection ? selection.anchor.offset : 0;
const newElement = anchorOffset === this.getTextContentSize() || !selection
? $createParagraphNode() : $createCalloutNode(this.__category);
@ -79,7 +79,7 @@ export class Callout extends ElementNode {
}
return {
node: new Callout(category),
node: new CalloutNode(category),
};
},
priority: 3,
@ -99,16 +99,16 @@ export class Callout extends ElementNode {
};
}
static importJSON(serializedNode: SerializedCalloutNode): Callout {
static importJSON(serializedNode: SerializedCalloutNode): CalloutNode {
return $createCalloutNode(serializedNode.category);
}
}
export function $createCalloutNode(category: CalloutCategory = 'info') {
return new Callout(category);
return new CalloutNode(category);
}
export function $isCalloutNode(node: LexicalNode | null | undefined) {
return node instanceof Callout;
return node instanceof CalloutNode;
}

View file

@ -0,0 +1,98 @@
import {
DOMConversion,
DOMConversionMap,
DOMConversionOutput, ElementFormatType,
LexicalNode,
ParagraphNode,
SerializedParagraphNode,
Spread
} from "lexical";
import {EditorConfig} from "lexical/LexicalEditor";
export type SerializedCustomParagraphNode = Spread<{
id: string;
}, SerializedParagraphNode>
export class CustomParagraphNode extends ParagraphNode {
__id: string = '';
static getType() {
return 'custom-paragraph';
}
setId(id: string) {
const self = this.getWritable();
self.__id = id;
}
getId(): string {
const self = this.getLatest();
return self.__id;
}
static clone(node: CustomParagraphNode) {
const newNode = new CustomParagraphNode(node.__key);
newNode.__id = node.__id;
return newNode;
}
createDOM(config: EditorConfig): HTMLElement {
const dom = super.createDOM(config);
const id = this.getId();
if (id) {
dom.setAttribute('id', id);
}
return dom;
}
exportJSON(): SerializedCustomParagraphNode {
return {
...super.exportJSON(),
type: 'custom-paragraph',
version: 1,
id: this.__id,
};
}
static importJSON(serializedNode: SerializedCustomParagraphNode): CustomParagraphNode {
const node = $createCustomParagraphNode();
node.setId(serializedNode.id);
return node;
}
static importDOM(): DOMConversionMap|null {
return {
p(node: HTMLElement): DOMConversion|null {
return {
conversion: (element: HTMLElement): DOMConversionOutput|null => {
const node = $createCustomParagraphNode();
if (element.style) {
node.setFormat(element.style.textAlign as ElementFormatType);
const indent = parseInt(element.style.textIndent, 10) / 20;
if (indent > 0) {
node.setIndent(indent);
}
}
if (element.id) {
node.setId(element.id);
}
return {node};
},
priority: 1,
};
},
};
}
}
export function $createCustomParagraphNode() {
return new CustomParagraphNode();
}
export function $isCustomParagraphNode(node: LexicalNode | null | undefined) {
return node instanceof CustomParagraphNode;
}

View file

@ -1,14 +1,22 @@
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
import {Callout} from './callout';
import {KlassConstructor, LexicalNode} from "lexical";
import {CalloutNode} from './callout';
import {KlassConstructor, LexicalNode, LexicalNodeReplacement, ParagraphNode} from "lexical";
import {CustomParagraphNode} from "./custom-paragraph";
/**
* Load the nodes for lexical.
*/
export function getNodesForPageEditor(): KlassConstructor<typeof LexicalNode>[] {
export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> | LexicalNodeReplacement)[] {
return [
Callout,
HeadingNode,
QuoteNode,
CalloutNode, // Todo - Create custom
HeadingNode, // Todo - Create custom
QuoteNode, // Todo - Create custom
CustomParagraphNode,
{
replace: ParagraphNode,
with: (node: ParagraphNode) => {
return new CustomParagraphNode();
}
}
];
}

View file

@ -11,7 +11,7 @@
</div>
<div refs="wysiwyg-editor@edit-area" contenteditable="true">
<p>Some content here</p>
<p id="Content!">Some <strong>content</strong> here</p>
<h2>List below this h2 header</h2>
<ul>
<li>Hello</li>