From b1c489090ecfdbde705de14825e0bd69ce2b5d7d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 30 Jun 2024 19:52:09 +0100 Subject: [PATCH] Lexical: Added context toolbar placement, added link toolbar Also added some basic context toolbar styling --- resources/icons/editor/unlink.svg | 1 + .../wysiwyg/ui/defaults/button-definitions.ts | 30 +++++++++++++++++-- resources/js/wysiwyg/ui/framework/manager.ts | 1 + resources/js/wysiwyg/ui/framework/toolbars.ts | 10 +++++-- resources/js/wysiwyg/ui/index.ts | 6 +++- resources/js/wysiwyg/ui/toolbars.ts | 9 +++++- resources/sass/_editor.scss | 24 +++++++++++++++ 7 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 resources/icons/editor/unlink.svg diff --git a/resources/icons/editor/unlink.svg b/resources/icons/editor/unlink.svg new file mode 100644 index 000000000..28f47fd24 --- /dev/null +++ b/resources/icons/editor/unlink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/defaults/button-definitions.ts b/resources/js/wysiwyg/ui/defaults/button-definitions.ts index aa8b27ec5..cebf25807 100644 --- a/resources/js/wysiwyg/ui/defaults/button-definitions.ts +++ b/resources/js/wysiwyg/ui/defaults/button-definitions.ts @@ -1,7 +1,7 @@ import {EditorBasicButtonDefinition, EditorButton, EditorButtonDefinition} from "../framework/buttons"; import { $createNodeSelection, - $createParagraphNode, $getRoot, $getSelection, + $createParagraphNode, $createTextNode, $getRoot, $getSelection, $isParagraphNode, $isTextNode, $setSelection, BaseSelection, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, ElementNode, FORMAT_TEXT_COMMAND, LexicalNode, @@ -45,12 +45,13 @@ import listBulletIcon from "@icons/editor/list-bullet.svg" import listNumberedIcon from "@icons/editor/list-numbered.svg" import listCheckIcon from "@icons/editor/list-check.svg" import linkIcon from "@icons/editor/link.svg" +import unlinkIcon from "@icons/editor/unlink.svg" import tableIcon from "@icons/editor/table.svg" import imageIcon from "@icons/editor/image.svg" import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg" import detailsIcon from "@icons/editor/details.svg" import sourceIcon from "@icons/editor/source-view.svg" -import {$createHorizontalRuleNode, $isHorizontalRuleNode, HorizontalRuleNode} from "../../nodes/horizontal-rule"; +import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule"; export const undo: EditorButtonDefinition = { label: 'Undo', @@ -258,6 +259,31 @@ export const link: EditorButtonDefinition = { } }; +export const unlink: EditorButtonDefinition = { + label: 'Remove link', + icon: unlinkIcon, + action(context: EditorUiContext) { + context.editor.update(() => { + const selection = context.lastSelection; + const selectedLink = getNodeFromSelection(selection, $isLinkNode) as LinkNode|null; + const selectionPoints = selection?.getStartEndPoints(); + + if (selectedLink) { + const newNode = $createTextNode(selectedLink.getTextContent()); + selectedLink.replace(newNode); + if (selectionPoints?.length === 2) { + newNode.select(selectionPoints[0].offset, selectionPoints[1].offset); + } else { + newNode.select(); + } + } + }); + }, + isActive(selection: BaseSelection|null): boolean { + return false; + } +}; + export const table: EditorBasicButtonDefinition = { label: 'Table', icon: tableIcon, diff --git a/resources/js/wysiwyg/ui/framework/manager.ts b/resources/js/wysiwyg/ui/framework/manager.ts index 685031ff8..4ca90a12a 100644 --- a/resources/js/wysiwyg/ui/framework/manager.ts +++ b/resources/js/wysiwyg/ui/framework/manager.ts @@ -94,6 +94,7 @@ export class EditorUIManager { for (const toolbar of this.activeContextToolbars) { toolbar.updateState(update); } + // console.log('selection update', update.selection); } protected updateContextToolbars(update: EditorUiStateUpdate): void { diff --git a/resources/js/wysiwyg/ui/framework/toolbars.ts b/resources/js/wysiwyg/ui/framework/toolbars.ts index a844161f4..c9db0d6bd 100644 --- a/resources/js/wysiwyg/ui/framework/toolbars.ts +++ b/resources/js/wysiwyg/ui/framework/toolbars.ts @@ -16,8 +16,14 @@ export class EditorContextToolbar extends EditorContainerUiElement { } attachTo(target: HTMLElement) { - // Todo - attach to target position - console.log('attaching context toolbar to', target); + const targetBounds = target.getBoundingClientRect(); + const dom = this.getDOMElement(); + const domBounds = dom.getBoundingClientRect(); + + const targetMid = targetBounds.left + (targetBounds.width / 2); + const targetLeft = targetMid - (domBounds.width / 2); + dom.style.top = (targetBounds.bottom + 6) + 'px'; + dom.style.left = targetLeft + 'px'; } insert(children: EditorUiElement[]) { diff --git a/resources/js/wysiwyg/ui/index.ts b/resources/js/wysiwyg/ui/index.ts index 9f05e211e..1f07fe710 100644 --- a/resources/js/wysiwyg/ui/index.ts +++ b/resources/js/wysiwyg/ui/index.ts @@ -1,5 +1,5 @@ import {LexicalEditor} from "lexical"; -import {getImageToolbarContent, getMainEditorFullToolbar} from "./toolbars"; +import {getImageToolbarContent, getLinkToolbarContent, getMainEditorFullToolbar} from "./toolbars"; import {EditorUIManager} from "./framework/manager"; import {image as imageFormDefinition, link as linkFormDefinition, source as sourceFormDefinition} from "./defaults/form-definitions"; import {ImageDecorator} from "./decorators/image"; @@ -41,6 +41,10 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) { return originalTarget.closest('a') || originalTarget; } }); + manager.registerContextToolbar('link', { + selector: 'a', + content: getLinkToolbarContent(), + }); // Register image decorator listener manager.registerDecoratorType('image', ImageDecorator); diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index 6e822e9c1..bb3b436b9 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -6,7 +6,7 @@ import { infoCallout, italic, link, numberList, paragraph, redo, source, strikethrough, subscript, successCallout, superscript, table, taskList, textColor, underline, - undo, + undo, unlink, warningCallout } from "./defaults/button-definitions"; import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiContext, EditorUiElement} from "./framework/core"; @@ -91,4 +91,11 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { export function getImageToolbarContent(): EditorUiElement[] { return [new EditorButton(image)]; +} + +export function getLinkToolbarContent(): EditorUiElement[] { + return [ + new EditorButton(link), + new EditorButton(unlink), + ]; } \ No newline at end of file diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index e0a2bac90..2fcf4edb3 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -69,6 +69,30 @@ display: flex; } +.editor-context-toolbar { + position: fixed; + background-color: #FFF; + border: 1px solid #DDD; + padding: .2rem; + border-radius: 4px; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12); + &:before { + content: ''; + z-index: -1; + display: block; + width: 8px; + height: 8px; + position: absolute; + background-color: #FFF; + border-top: 1px solid #DDD; + border-left: 1px solid #DDD; + transform: rotate(45deg); + left: 50%; + margin-left: -4px; + top: -5px; + } +} + // Modals .editor-modal-wrapper { position: fixed;