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:
parent
49546cd627
commit
0f8bd869d8
6 changed files with 122 additions and 20 deletions
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
98
resources/js/wysiwyg/nodes/custom-paragraph.ts
Normal file
98
resources/js/wysiwyg/nodes/custom-paragraph.ts
Normal 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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue