Added base node/button for details/summary

This commit is contained in:
Dan Brown 2024-06-06 14:43:50 +01:00
parent 0722960260
commit 5c343638b6
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
5 changed files with 162 additions and 3 deletions

View File

@ -0,0 +1,120 @@
import {
DOMConversion,
DOMConversionMap, DOMConversionOutput,
ElementNode,
LexicalEditor,
LexicalNode,
SerializedElementNode,
} from 'lexical';
import type {EditorConfig} from "lexical/LexicalEditor";
import {el} from "../helpers";
export class DetailsNode extends ElementNode {
static getType() {
return 'details';
}
static clone(node: DetailsNode) {
return new DetailsNode(node.__key);
}
createDOM(_config: EditorConfig, _editor: LexicalEditor) {
return el('details');
}
updateDOM(prevNode: DetailsNode, dom: HTMLElement) {
return false;
}
static importDOM(): DOMConversionMap|null {
return {
details(node: HTMLElement): DOMConversion|null {
return {
conversion: (element: HTMLElement): DOMConversionOutput|null => {
return {
node: new DetailsNode(),
};
},
priority: 3,
};
},
};
}
exportJSON(): SerializedElementNode {
return {
...super.exportJSON(),
type: 'details',
version: 1,
};
}
static importJSON(serializedNode: SerializedElementNode): DetailsNode {
return $createDetailsNode();
}
}
export function $createDetailsNode() {
return new DetailsNode();
}
export function $isDetailsNode(node: LexicalNode | null | undefined) {
return node instanceof DetailsNode;
}
export class SummaryNode extends ElementNode {
static getType() {
return 'summary';
}
static clone(node: SummaryNode) {
return new SummaryNode(node.__key);
}
createDOM(_config: EditorConfig, _editor: LexicalEditor) {
return el('summary');
}
updateDOM(prevNode: DetailsNode, dom: HTMLElement) {
return false;
}
static importDOM(): DOMConversionMap|null {
return {
summary(node: HTMLElement): DOMConversion|null {
return {
conversion: (element: HTMLElement): DOMConversionOutput|null => {
return {
node: new SummaryNode(),
};
},
priority: 3,
};
},
};
}
exportJSON(): SerializedElementNode {
return {
...super.exportJSON(),
type: 'summary',
version: 1,
};
}
static importJSON(serializedNode: SerializedElementNode): DetailsNode {
return $createSummaryNode();
}
}
export function $createSummaryNode() {
return new SummaryNode();
}
export function $isSummaryNode(node: LexicalNode | null | undefined) {
return node instanceof SummaryNode;
}

View File

@ -4,6 +4,7 @@ import {ElementNode, KlassConstructor, LexicalNode, LexicalNodeReplacement, Para
import {CustomParagraphNode} from "./custom-paragraph"; import {CustomParagraphNode} from "./custom-paragraph";
import {LinkNode} from "@lexical/link"; import {LinkNode} from "@lexical/link";
import {ImageNode} from "./image"; import {ImageNode} from "./image";
import {DetailsNode, SummaryNode} from "./details";
/** /**
* Load the nodes for lexical. * Load the nodes for lexical.
@ -14,6 +15,7 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
HeadingNode, // Todo - Create custom HeadingNode, // Todo - Create custom
QuoteNode, // Todo - Create custom QuoteNode, // Todo - Create custom
ImageNode, ImageNode,
DetailsNode, SummaryNode,
CustomParagraphNode, CustomParagraphNode,
{ {
replace: ParagraphNode, replace: ParagraphNode,

View File

@ -1,9 +1,9 @@
import {EditorButtonDefinition} from "../framework/buttons"; import {EditorButtonDefinition} from "../framework/buttons";
import { import {
$createNodeSelection, $createNodeSelection,
$createParagraphNode, $getSelection, $createParagraphNode, $getRoot, $getSelection, $insertNodes,
$isParagraphNode, $setSelection, $isParagraphNode, $setSelection,
BaseSelection, FORMAT_TEXT_COMMAND, BaseSelection, ElementNode, FORMAT_TEXT_COMMAND,
LexicalNode, LexicalNode,
REDO_COMMAND, TextFormatType, REDO_COMMAND, TextFormatType,
UNDO_COMMAND UNDO_COMMAND
@ -26,6 +26,8 @@ import {
import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link"; import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link";
import {EditorUiContext} from "../framework/core"; import {EditorUiContext} from "../framework/core";
import {$isImageNode, ImageNode} from "../../nodes/image"; import {$isImageNode, ImageNode} from "../../nodes/image";
import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
import {$insertNodeToNearestRoot} from "@lexical/utils";
export const undo: EditorButtonDefinition = { export const undo: EditorButtonDefinition = {
label: 'Undo', label: 'Undo',
@ -201,3 +203,30 @@ export const image: EditorButtonDefinition = {
} }
}; };
export const details: EditorButtonDefinition = {
label: 'Insert collapsible block',
action(context: EditorUiContext) {
context.editor.update(() => {
const selection = $getSelection();
const detailsNode = $createDetailsNode();
const selectionNodes = selection?.getNodes() || [];
const topLevels = selectionNodes.map(n => n.getTopLevelElement())
.filter(n => n !== null) as ElementNode[];
const uniqueTopLevels = [...new Set(topLevels)];
if (uniqueTopLevels.length > 0) {
uniqueTopLevels[0].insertAfter(detailsNode);
} else {
$getRoot().append(detailsNode);
}
for (const node of uniqueTopLevels) {
detailsNode.append(node);
}
});
},
isActive(selection: BaseSelection|null): boolean {
return selectionContainsNodeType(selection, $isDetailsNode);
}
}

View File

@ -1,7 +1,7 @@
import {EditorButton, FormatPreviewButton} from "./framework/buttons"; import {EditorButton, FormatPreviewButton} from "./framework/buttons";
import { import {
blockquote, bold, code, blockquote, bold, code,
dangerCallout, dangerCallout, details,
h2, h3, h4, h5, image, h2, h3, h4, h5, image,
infoCallout, italic, link, paragraph, infoCallout, italic, link, paragraph,
redo, strikethrough, subscript, redo, strikethrough, subscript,
@ -41,5 +41,6 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
new EditorButton(link), new EditorButton(link),
new EditorButton(image), new EditorButton(image),
new EditorButton(details),
]); ]);
} }

View File

@ -16,6 +16,13 @@
<li>Hello</li> <li>Hello</li>
</ul> </ul>
<details>
<summary>Collapsible details/summary block</summary>
<p>Inner text here</p>
<h4>Inner Header</h4>
<p>More text <strong>with bold in</strong> it</p>
</details>
<p class="callout info"> <p class="callout info">
Hello there, this is an info callout Hello there, this is an info callout
</p> </p>