--- /dev/null
+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;
+}
import {CustomParagraphNode} from "./custom-paragraph";
import {LinkNode} from "@lexical/link";
import {ImageNode} from "./image";
+import {DetailsNode, SummaryNode} from "./details";
/**
* Load the nodes for lexical.
HeadingNode, // Todo - Create custom
QuoteNode, // Todo - Create custom
ImageNode,
+ DetailsNode, SummaryNode,
CustomParagraphNode,
{
replace: ParagraphNode,
import {EditorButtonDefinition} from "../framework/buttons";
import {
$createNodeSelection,
- $createParagraphNode, $getSelection,
+ $createParagraphNode, $getRoot, $getSelection, $insertNodes,
$isParagraphNode, $setSelection,
- BaseSelection, FORMAT_TEXT_COMMAND,
+ BaseSelection, ElementNode, FORMAT_TEXT_COMMAND,
LexicalNode,
REDO_COMMAND, TextFormatType,
UNDO_COMMAND
import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link";
import {EditorUiContext} from "../framework/core";
import {$isImageNode, ImageNode} from "../../nodes/image";
+import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
+import {$insertNodeToNearestRoot} from "@lexical/utils";
export const undo: EditorButtonDefinition = {
label: 'Undo',
}
};
+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);
+ }
+}
+
import {EditorButton, FormatPreviewButton} from "./framework/buttons";
import {
blockquote, bold, code,
- dangerCallout,
+ dangerCallout, details,
h2, h3, h4, h5, image,
infoCallout, italic, link, paragraph,
redo, strikethrough, subscript,
new EditorButton(link),
new EditorButton(image),
+ new EditorButton(details),
]);
}
\ No newline at end of file
<li>Hello</li>
</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">
Hello there, this is an info callout
</p>