From 0039f893ccb6d43c7cd5bacde7163ecdd32dc3d9 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 17 Aug 2024 10:48:34 +0100 Subject: [PATCH] Lexical: Integrated diagram manager, added menu split button --- resources/icons/caret-down-large.svg | 1 + resources/js/wysiwyg/todo.md | 1 - .../js/wysiwyg/ui/defaults/buttons/objects.ts | 12 ++++++- .../ui/framework/blocks/button-with-menu.ts | 31 +++++++++++++++++++ resources/js/wysiwyg/ui/toolbars.ts | 10 +++--- resources/js/wysiwyg/utils/diagrams.ts | 29 +++++++++++++++-- resources/js/wysiwyg/utils/images.ts | 3 +- resources/sass/_editor.scss | 25 +++++++++++++++ 8 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 resources/icons/caret-down-large.svg create mode 100644 resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts diff --git a/resources/icons/caret-down-large.svg b/resources/icons/caret-down-large.svg new file mode 100644 index 000000000..a15ec42c8 --- /dev/null +++ b/resources/icons/caret-down-large.svg @@ -0,0 +1 @@ + diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index 70a3744f3..f3a8da404 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -10,7 +10,6 @@ - Alignments: Handle inline block content (image, video) - Image paste upload - Keyboard shortcuts support -- Drawing gallery integration - Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts) - Media resize support (like images) - Table caption text support diff --git a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts index f4075a740..96a92ff22 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts @@ -30,7 +30,7 @@ import { $insertNewBlockNodeAtSelection, $selectionContainsNodeType } from "../../../utils/selection"; -import {$isDiagramNode, $openDrawingEditorForNode} from "../../../utils/diagrams"; +import {$isDiagramNode, $openDrawingEditorForNode, showDiagramManagerForInsert} from "../../../utils/diagrams"; import {$createLinkedImageNodeFromImageData, showImageManager} from "../../../utils/images"; import {$showImageForm} from "../forms/objects"; @@ -184,6 +184,16 @@ export const diagram: EditorButtonDefinition = { } }; +export const diagramManager: EditorButtonDefinition = { + label: 'Drawing manager', + action(context: EditorUiContext) { + showDiagramManagerForInsert(context); + }, + isActive(): boolean { + return false; + } +}; + export const media: EditorButtonDefinition = { label: 'Insert/edit Media', icon: mediaIcon, diff --git a/resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts b/resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts new file mode 100644 index 000000000..30dd237f6 --- /dev/null +++ b/resources/js/wysiwyg/ui/framework/blocks/button-with-menu.ts @@ -0,0 +1,31 @@ +import {EditorContainerUiElement, EditorUiElement} from "../core"; +import {el} from "../../../utils/dom"; +import {EditorButton} from "../buttons"; +import {EditorDropdownButton} from "./dropdown-button"; +import caretDownIcon from "@icons/caret-down-large.svg"; + +export class EditorButtonWithMenu extends EditorContainerUiElement { + protected button: EditorButton; + protected dropdownButton: EditorDropdownButton; + + constructor(button: EditorButton, menuItems: EditorUiElement[]) { + super([button]); + + this.button = button; + this.dropdownButton = new EditorDropdownButton({ + button: {label: 'Menu', icon: caretDownIcon}, + showOnHover: false, + direction: 'vertical', + }, menuItems); + this.addChildren(this.dropdownButton); + } + + buildDOM(): HTMLElement { + return el('div', { + class: 'editor-button-with-menu-container', + }, [ + this.button.getDOMElement(), + this.dropdownButton.getDOMElement() + ]); + } +} diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index 48e11837c..87ecae03e 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -56,16 +56,15 @@ import {bulletList, numberList, taskList} from "./defaults/buttons/lists"; import { codeBlock, details, - diagram, + diagram, diagramManager, editCodeBlock, horizontalRule, image, link, media, unlink } from "./defaults/buttons/objects"; -import {$isTableNode} from "@lexical/table"; -import {$selectionContainsNodeType} from "../utils/selection"; import {el} from "../utils/dom"; +import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu"; export function getMainEditorFullToolbar(): EditorContainerUiElement { return new EditorSimpleClassContainer('editor-toolbar-main', [ @@ -166,7 +165,10 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { new EditorButton(image), new EditorButton(horizontalRule), new EditorButton(codeBlock), - new EditorButton(diagram), + new EditorButtonWithMenu( + new EditorButton(diagram), + [new EditorButton(diagramManager)], + ), new EditorButton(media), new EditorButton(details), ]), diff --git a/resources/js/wysiwyg/utils/diagrams.ts b/resources/js/wysiwyg/utils/diagrams.ts index 50d7d5b3f..2dee3ab6b 100644 --- a/resources/js/wysiwyg/utils/diagrams.ts +++ b/resources/js/wysiwyg/utils/diagrams.ts @@ -1,8 +1,11 @@ -import {LexicalEditor, LexicalNode} from "lexical"; +import {$getSelection, $insertNodes, LexicalEditor, LexicalNode} from "lexical"; import {HttpError} from "../../services/http"; import {EditorUiContext} from "../ui/framework/core"; import * as DrawIO from "../../services/drawio"; -import {DiagramNode} from "../nodes/diagram"; +import {$createDiagramNode, DiagramNode} from "../nodes/diagram"; +import {ImageManager} from "../../components"; +import {EditorImageData} from "./images"; +import {$getNodeFromSelection} from "./selection"; export function $isDiagramNode(node: LexicalNode | null | undefined): node is DiagramNode { return node instanceof DiagramNode; @@ -67,4 +70,26 @@ export function $openDrawingEditorForNode(context: EditorUiContext, node: Diagra }, async (pngData: string) => { return updateDrawingNodeFromData(context, node, pngData, isNew); }); +} + +export function showDiagramManager(callback: (image: EditorImageData) => any) { + const imageManager: ImageManager = window.$components.first('image-manager') as ImageManager; + imageManager.show((image: EditorImageData) => { + callback(image); + }, 'drawio'); +} + +export function showDiagramManagerForInsert(context: EditorUiContext) { + const selection = context.lastSelection; + showDiagramManager((image: EditorImageData) => { + context.editor.update(() => { + const diagramNode = $createDiagramNode(image.id, image.url); + const selectedDiagram = $getNodeFromSelection(selection, $isDiagramNode); + if ($isDiagramNode(selectedDiagram)) { + selectedDiagram.replace(diagramNode); + } else { + $insertNodes([diagramNode]); + } + }); + }); } \ No newline at end of file diff --git a/resources/js/wysiwyg/utils/images.ts b/resources/js/wysiwyg/utils/images.ts index 89a4a60f0..a83d55418 100644 --- a/resources/js/wysiwyg/utils/images.ts +++ b/resources/js/wysiwyg/utils/images.ts @@ -2,7 +2,8 @@ import {ImageManager} from "../../components"; import {$createImageNode} from "../nodes/image"; import {$createLinkNode, LinkNode} from "@lexical/link"; -type EditorImageData = { +export type EditorImageData = { + id: string; url: string; thumbs?: {display: string}; name: string; diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index 379c436f4..78e518bd5 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -82,6 +82,31 @@ body.editor-is-fullscreen { fill: currentColor; display: block; } +.editor-button-with-menu-container { + display: flex; + flex-direction: row; + gap: 0; + align-items: stretch; + border-radius: 4px; + .editor-dropdown-menu-container { + display: flex; + } + .editor-dropdown-menu-container > .editor-dropdown-menu { + top: 100%; + } + .editor-dropdown-menu-container > .editor-button { + padding-inline: 4px; + margin-inline-start: -3px; + svg { + width: 12px; + height: 12px; + } + } + &:hover { + outline: 1px solid #DDD; + outline-offset: -3px; + } +} // Containers .editor-dropdown-menu-container {