diff --git a/resources/icons/editor/details-toggle.svg b/resources/icons/editor/details-toggle.svg
new file mode 100644
index 000000000..37194e059
--- /dev/null
+++ b/resources/icons/editor/details-toggle.svg
@@ -0,0 +1 @@
+
diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts
index 18d471103..3c845359a 100644
--- a/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts
+++ b/resources/js/wysiwyg/lexical/rich-text/LexicalDetailsNode.ts
@@ -18,6 +18,7 @@ export type SerializedDetailsNode = Spread<{
export class DetailsNode extends ElementNode {
__id: string = '';
__summary: string = '';
+ __open: boolean = false;
static getType() {
return 'details';
@@ -43,11 +44,22 @@ export class DetailsNode extends ElementNode {
return self.__summary;
}
+ setOpen(open: boolean) {
+ const self = this.getWritable();
+ self.__open = open;
+ }
+
+ getOpen(): boolean {
+ const self = this.getLatest();
+ return self.__open;
+ }
+
static clone(node: DetailsNode): DetailsNode {
const newNode = new DetailsNode(node.__key);
newNode.__id = node.__id;
newNode.__dir = node.__dir;
newNode.__summary = node.__summary;
+ newNode.__open = node.__open;
return newNode;
}
@@ -61,17 +73,34 @@ export class DetailsNode extends ElementNode {
el.setAttribute('dir', this.__dir);
}
+ if (this.__open) {
+ el.setAttribute('open', 'true');
+ }
+
const summary = document.createElement('summary');
summary.textContent = this.__summary;
summary.setAttribute('contenteditable', 'false');
+ summary.addEventListener('click', event => {
+ event.preventDefault();
+ _editor.update(() => {
+ this.select();
+ })
+ });
+
el.append(summary);
return el;
}
updateDOM(prevNode: DetailsNode, dom: HTMLElement) {
+
+ if (prevNode.__open !== this.__open) {
+ dom.toggleAttribute('open', this.__open);
+ }
+
return prevNode.__id !== this.__id
- || prevNode.__dir !== this.__dir;
+ || prevNode.__dir !== this.__dir
+ || prevNode.__summary !== this.__summary;
}
static importDOM(): DOMConversionMap|null {
@@ -114,6 +143,8 @@ export class DetailsNode extends ElementNode {
elem.removeAttribute('contenteditable');
}
+ element.removeAttribute('open');
+
return {element};
}
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts
index f9c029ff1..6612c0dc4 100644
--- a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts
+++ b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts
@@ -19,6 +19,9 @@ import editIcon from "@icons/edit.svg";
import diagramIcon from "@icons/editor/diagram.svg";
import {$createDiagramNode, DiagramNode} from "@lexical/rich-text/LexicalDiagramNode";
import detailsIcon from "@icons/editor/details.svg";
+import detailsToggleIcon from "@icons/editor/details-toggle.svg";
+import tableDeleteIcon from "@icons/editor/table-delete.svg";
+import tagIcon from "@icons/tag.svg";
import mediaIcon from "@icons/editor/media.svg";
import {$createDetailsNode, $isDetailsNode} from "@lexical/rich-text/LexicalDetailsNode";
import {$isMediaNode, MediaNode} from "@lexical/rich-text/LexicalMediaNode";
@@ -29,7 +32,7 @@ import {
} from "../../../utils/selection";
import {$isDiagramNode, $openDrawingEditorForNode, showDiagramManagerForInsert} from "../../../utils/diagrams";
import {$createLinkedImageNodeFromImageData, showImageManager} from "../../../utils/images";
-import {$showImageForm, $showLinkForm} from "../forms/objects";
+import {$showDetailsForm, $showImageForm, $showLinkForm} from "../forms/objects";
import {formatCodeBlock} from "../../../utils/formats";
export const link: EditorButtonDefinition = {
@@ -216,4 +219,58 @@ export const details: EditorButtonDefinition = {
isActive(selection: BaseSelection | null): boolean {
return $selectionContainsNodeType(selection, $isDetailsNode);
}
+}
+
+export const detailsEditLabel: EditorButtonDefinition = {
+ label: 'Edit label',
+ icon: tagIcon,
+ action(context: EditorUiContext) {
+ context.editor.getEditorState().read(() => {
+ const details = $getNodeFromSelection($getSelection(), $isDetailsNode);
+ if ($isDetailsNode(details)) {
+ $showDetailsForm(details, context);
+ }
+ })
+ },
+ isActive(selection: BaseSelection | null): boolean {
+ return false;
+ }
+}
+
+export const detailsToggle: EditorButtonDefinition = {
+ label: 'Toggle open/closed',
+ icon: detailsToggleIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ const details = $getNodeFromSelection($getSelection(), $isDetailsNode);
+ if ($isDetailsNode(details)) {
+ details.setOpen(!details.getOpen());
+ context.manager.triggerLayoutUpdate();
+ }
+ })
+ },
+ isActive(selection: BaseSelection | null): boolean {
+ return false;
+ }
+}
+
+export const detailsUnwrap: EditorButtonDefinition = {
+ label: 'Unwrap',
+ icon: tableDeleteIcon,
+ action(context: EditorUiContext) {
+ context.editor.update(() => {
+ const details = $getNodeFromSelection($getSelection(), $isDetailsNode);
+ if ($isDetailsNode(details)) {
+ const children = details.getChildren();
+ for (const child of children) {
+ details.insertBefore(child);
+ }
+ details.remove();
+ context.manager.triggerLayoutUpdate();
+ }
+ })
+ },
+ isActive(selection: BaseSelection | null): boolean {
+ return false;
+ }
}
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/defaults/forms/objects.ts b/resources/js/wysiwyg/ui/defaults/forms/objects.ts
index f00a08bb5..21d333c3a 100644
--- a/resources/js/wysiwyg/ui/defaults/forms/objects.ts
+++ b/resources/js/wysiwyg/ui/defaults/forms/objects.ts
@@ -19,6 +19,7 @@ import searchIcon from "@icons/search.svg";
import {showLinkSelector} from "../../../utils/links";
import {LinkField} from "../../framework/blocks/link-field";
import {insertOrUpdateLink} from "../../../utils/formats";
+import {$isDetailsNode, DetailsNode} from "@lexical/rich-text/LexicalDetailsNode";
export function $showImageForm(image: ImageNode, context: EditorUiContext) {
const imageModal: EditorFormModal = context.manager.createModal('image');
@@ -262,4 +263,37 @@ export const media: EditorFormDefinition = {
}
},
],
+};
+
+export function $showDetailsForm(details: DetailsNode|null, context: EditorUiContext) {
+ const linkModal = context.manager.createModal('details');
+ if (!details) {
+ return;
+ }
+
+ linkModal.show({
+ summary: details.getSummary()
+ });
+}
+
+export const details: EditorFormDefinition = {
+ submitText: 'Save',
+ async action(formData, context: EditorUiContext) {
+ context.editor.update(() => {
+ const node = $getNodeFromSelection($getSelection(), $isDetailsNode);
+ const summary = (formData.get('summary') || '').toString().trim();
+ if ($isDetailsNode(node)) {
+ node.setSummary(summary);
+ }
+ });
+
+ return true;
+ },
+ fields: [
+ {
+ label: 'Toggle label',
+ name: 'summary',
+ type: 'text',
+ },
+ ],
};
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/defaults/modals.ts b/resources/js/wysiwyg/ui/defaults/modals.ts
index c43923778..da3859266 100644
--- a/resources/js/wysiwyg/ui/defaults/modals.ts
+++ b/resources/js/wysiwyg/ui/defaults/modals.ts
@@ -1,5 +1,5 @@
import {EditorFormModalDefinition} from "../framework/modals";
-import {image, link, media} from "./forms/objects";
+import {details, image, link, media} from "./forms/objects";
import {source} from "./forms/controls";
import {cellProperties, rowProperties, tableProperties} from "./forms/tables";
@@ -32,4 +32,8 @@ export const modals: Record = {
title: 'Table Properties',
form: tableProperties,
},
+ details: {
+ title: 'Edit collapsible block',
+ form: details,
+ }
};
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/index.ts b/resources/js/wysiwyg/ui/index.ts
index 3811f44b9..40df43347 100644
--- a/resources/js/wysiwyg/ui/index.ts
+++ b/resources/js/wysiwyg/ui/index.ts
@@ -1,6 +1,6 @@
import {LexicalEditor} from "lexical";
import {
- getCodeToolbarContent,
+ getCodeToolbarContent, getDetailsToolbarContent,
getImageToolbarContent,
getLinkToolbarContent,
getMainEditorFullToolbar, getTableToolbarContent
@@ -56,7 +56,6 @@ export function buildEditorUI(container: HTMLElement, element: HTMLElement, scro
selector: '.editor-code-block-wrap',
content: getCodeToolbarContent(),
});
-
manager.registerContextToolbar('table', {
selector: 'td,th',
content: getTableToolbarContent(),
@@ -64,6 +63,10 @@ export function buildEditorUI(container: HTMLElement, element: HTMLElement, scro
return originalTarget.closest('table') as HTMLTableElement;
}
});
+ manager.registerContextToolbar('details', {
+ selector: 'details',
+ content: getDetailsToolbarContent(),
+ });
// Register image decorator listener
manager.registerDecoratorType('code', CodeBlockDecorator);
diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts
index 886e1394b..1230cbdd2 100644
--- a/resources/js/wysiwyg/ui/toolbars.ts
+++ b/resources/js/wysiwyg/ui/toolbars.ts
@@ -68,7 +68,7 @@ import {
} from "./defaults/buttons/lists";
import {
codeBlock,
- details,
+ details, detailsEditLabel, detailsToggle, detailsUnwrap,
diagram, diagramManager,
editCodeBlock,
horizontalRule,
@@ -253,4 +253,12 @@ export function getTableToolbarContent(): EditorUiElement[] {
new EditorButton(deleteColumn),
]),
];
+}
+
+export function getDetailsToolbarContent(): EditorUiElement[] {
+ return [
+ new EditorButton(detailsEditLabel),
+ new EditorButton(detailsToggle),
+ new EditorButton(detailsUnwrap),
+ ];
}
\ No newline at end of file