From 5c343638b67497cc1229aa4acdedff199553a7e4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 6 Jun 2024 14:43:50 +0100 Subject: [PATCH] Added base node/button for details/summary --- resources/js/wysiwyg/nodes/details.ts | 120 ++++++++++++++++++ resources/js/wysiwyg/nodes/index.ts | 2 + .../wysiwyg/ui/defaults/button-definitions.ts | 33 ++++- resources/js/wysiwyg/ui/toolbars.ts | 3 +- .../pages/parts/wysiwyg-editor.blade.php | 7 + 5 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 resources/js/wysiwyg/nodes/details.ts diff --git a/resources/js/wysiwyg/nodes/details.ts b/resources/js/wysiwyg/nodes/details.ts new file mode 100644 index 000000000..a18c4d858 --- /dev/null +++ b/resources/js/wysiwyg/nodes/details.ts @@ -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; +} diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes/index.ts index 1d492a87a..f47575bc5 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes/index.ts @@ -4,6 +4,7 @@ import {ElementNode, KlassConstructor, LexicalNode, LexicalNodeReplacement, Para import {CustomParagraphNode} from "./custom-paragraph"; import {LinkNode} from "@lexical/link"; import {ImageNode} from "./image"; +import {DetailsNode, SummaryNode} from "./details"; /** * Load the nodes for lexical. @@ -14,6 +15,7 @@ export function getNodesForPageEditor(): (KlassConstructor | HeadingNode, // Todo - Create custom QuoteNode, // Todo - Create custom ImageNode, + DetailsNode, SummaryNode, CustomParagraphNode, { replace: ParagraphNode, diff --git a/resources/js/wysiwyg/ui/defaults/button-definitions.ts b/resources/js/wysiwyg/ui/defaults/button-definitions.ts index 92f0cfc81..e549e69a2 100644 --- a/resources/js/wysiwyg/ui/defaults/button-definitions.ts +++ b/resources/js/wysiwyg/ui/defaults/button-definitions.ts @@ -1,9 +1,9 @@ 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 @@ -26,6 +26,8 @@ import { 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', @@ -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); + } +} + diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index 2802b1ca7..b5d151fc1 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -1,7 +1,7 @@ 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, @@ -41,5 +41,6 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { new EditorButton(link), new EditorButton(image), + new EditorButton(details), ]); } \ No newline at end of file diff --git a/resources/views/pages/parts/wysiwyg-editor.blade.php b/resources/views/pages/parts/wysiwyg-editor.blade.php index c0ceddc45..641402769 100644 --- a/resources/views/pages/parts/wysiwyg-editor.blade.php +++ b/resources/views/pages/parts/wysiwyg-editor.blade.php @@ -16,6 +16,13 @@
  • Hello
  • +
    + Collapsible details/summary block +

    Inner text here

    +

    Inner Header

    +

    More text with bold in it

    +
    +

    Hello there, this is an info callout