diff --git a/resources/js/wysiwyg/nodes/custom-list-item.ts b/resources/js/wysiwyg/nodes/custom-list-item.ts index 53467e10b..21b9f6d5f 100644 --- a/resources/js/wysiwyg/nodes/custom-list-item.ts +++ b/resources/js/wysiwyg/nodes/custom-list-item.ts @@ -1,4 +1,4 @@ -import {$isListNode, ListItemNode, ListNode, SerializedListItemNode} from "@lexical/list"; +import {$isListNode, ListItemNode, SerializedListItemNode} from "@lexical/list"; import {EditorConfig} from "lexical/LexicalEditor"; import {DOMExportOutput, LexicalEditor, LexicalNode} from "lexical"; import {el} from "../helpers"; diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index dda05f1da..1a367d0dd 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -2,13 +2,14 @@ ## In progress -// +- Table features + - Continued table dropdown menu ## Main Todo - Alignments: Use existing classes for blocks - Alignments: Handle inline block content (image, video) -- Table features + - Image paste upload - Keyboard shortcuts support - Add ID support to all block types diff --git a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts index 32fa49f88..b6b92e197 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts @@ -8,17 +8,18 @@ import insertColumnBeforeIcon from "@icons/editor/table-insert-column-before.svg import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg"; import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg"; import {EditorUiContext} from "../../framework/core"; -import {$getBlockElementNodesInSelection, $getNodeFromSelection, $getParentOfType} from "../../../helpers"; -import {$getSelection} from "lexical"; -import {$isCustomTableNode, CustomTableNode} from "../../../nodes/custom-table"; import { - $deleteTableColumn, $deleteTableColumn__EXPERIMENTAL, + $getNodeFromSelection, + $selectionContainsNodeType +} from "../../../helpers"; +import {$getSelection} from "lexical"; +import {$isCustomTableNode} from "../../../nodes/custom-table"; +import { + $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, - $getTableRowIndexFromTableCellNode, $insertTableColumn, $insertTableColumn__EXPERIMENTAL, - $insertTableRow, $insertTableRow__EXPERIMENTAL, - $isTableCellNode, - $isTableRowNode, - TableCellNode + $insertTableColumn__EXPERIMENTAL, + $insertTableRow__EXPERIMENTAL, + $isTableNode, } from "@lexical/table"; @@ -43,6 +44,14 @@ export const deleteTable: EditorButtonDefinition = { } }; +export const deleteTableMenuAction: EditorButtonDefinition = { + ...deleteTable, + format: 'long', + isDisabled(selection) { + return !$selectionContainsNodeType(selection, $isTableNode); + }, +}; + export const insertRowAbove: EditorButtonDefinition = { label: 'Insert row above', icon: insertRowAboveIcon, diff --git a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts index a75cf64fe..24659b546 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts @@ -3,22 +3,34 @@ import {handleDropdown} from "../helpers/dropdowns"; import {EditorContainerUiElement, EditorUiElement} from "../core"; import {EditorBasicButtonDefinition, EditorButton} from "../buttons"; +export type EditorDropdownButtonOptions = { + showOnHover?: boolean; + direction?: 'vertical'|'horizontal'; + button: EditorBasicButtonDefinition|EditorButton; +}; + +const defaultOptions: EditorDropdownButtonOptions = { + showOnHover: false, + direction: 'horizontal', + button: {label: 'Menu'}, +} + export class EditorDropdownButton extends EditorContainerUiElement { protected button: EditorButton; protected childItems: EditorUiElement[]; protected open: boolean = false; - protected showOnHover: boolean = false; + protected options: EditorDropdownButtonOptions; - constructor(button: EditorBasicButtonDefinition|EditorButton, showOnHover: boolean, children: EditorUiElement[]) { + constructor(options: EditorDropdownButtonOptions, children: EditorUiElement[]) { super(children); this.childItems = children; - this.showOnHover = showOnHover; + this.options = Object.assign(defaultOptions, options); - if (button instanceof EditorButton) { - this.button = button; + if (options.button instanceof EditorButton) { + this.button = options.button; } else { this.button = new EditorButton({ - ...button, + ...options.button, action() { return false; }, @@ -41,7 +53,7 @@ export class EditorDropdownButton extends EditorContainerUiElement { const childElements: HTMLElement[] = this.childItems.map(child => child.getDOMElement()); const menu = el('div', { - class: 'editor-dropdown-menu', + class: `editor-dropdown-menu editor-dropdown-menu-${this.options.direction}`, hidden: 'true', }, childElements); @@ -50,7 +62,7 @@ export class EditorDropdownButton extends EditorContainerUiElement { }, [button, menu]); handleDropdown({toggle : button, menu : menu, - showOnHover: this.showOnHover, + showOnHover: this.options.showOnHover, onOpen : () => { this.open = true; this.getContext().manager.triggerStateUpdateForElement(this.button); diff --git a/resources/js/wysiwyg/ui/framework/blocks/format-menu.ts b/resources/js/wysiwyg/ui/framework/blocks/format-menu.ts index 52a9c3809..b0834fe4d 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/format-menu.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/format-menu.ts @@ -7,7 +7,7 @@ export class EditorFormatMenu extends EditorContainerUiElement { buildDOM(): HTMLElement { const childElements: HTMLElement[] = this.getChildren().map(child => child.getDOMElement()); const menu = el('div', { - class: 'editor-format-menu-dropdown editor-dropdown-menu editor-menu-list', + class: 'editor-format-menu-dropdown editor-dropdown-menu editor-dropdown-menu-vertical', hidden: 'true', }, childElements); diff --git a/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts b/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts index 83f394d9d..108992db8 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts @@ -15,9 +15,11 @@ export class EditorOverflowContainer extends EditorContainerUiElement { this.size = size; this.content = children; this.overflowButton = new EditorDropdownButton({ - label: 'More', - icon: moreHorizontal, - }, false, []); + button: { + label: 'More', + icon: moreHorizontal, + }, + }, []); this.addChildren(this.overflowButton); } diff --git a/resources/js/wysiwyg/ui/framework/buttons.ts b/resources/js/wysiwyg/ui/framework/buttons.ts index 4418be623..9a23edfb7 100644 --- a/resources/js/wysiwyg/ui/framework/buttons.ts +++ b/resources/js/wysiwyg/ui/framework/buttons.ts @@ -5,11 +5,13 @@ import {el} from "../../helpers"; export interface EditorBasicButtonDefinition { label: string; icon?: string|undefined; + format?: 'small' | 'long'; } export interface EditorButtonDefinition extends EditorBasicButtonDefinition { action: (context: EditorUiContext, button: EditorButton) => void; isActive: (selection: BaseSelection|null, context: EditorUiContext) => boolean; + isDisabled?: (selection: BaseSelection|null, context: EditorUiContext) => boolean; setup?: (context: EditorUiContext, button: EditorButton) => void; } @@ -47,20 +49,27 @@ export class EditorButton extends EditorUiElement { } protected buildDOM(): HTMLButtonElement { - const label = this.getLabel(); - let child: string|HTMLElement = label; - if (this.definition.icon) { - child = el('div', {class: 'editor-button-icon'}); - child.innerHTML = this.definition.icon; + const format = this.definition.format || 'small'; + const children: (string|HTMLElement)[] = []; + + if (this.definition.icon || format === 'long') { + const icon = el('div', {class: 'editor-button-icon'}); + icon.innerHTML = this.definition.icon || ''; + children.push(icon); + } + + if (!this.definition.icon ||format === 'long') { + const text = el('div', {class: 'editor-button-text'}, [label]); + children.push(text); } const button = el('button', { type: 'button', - class: 'editor-button', + class: `editor-button editor-button-${format}`, title: this.definition.icon ? label : null, disabled: this.disabled ? 'true' : null, - }, [child]) as HTMLButtonElement; + }, children) as HTMLButtonElement; button.addEventListener('click', this.onClick.bind(this)); @@ -71,11 +80,18 @@ export class EditorButton extends EditorUiElement { this.definition.action(this.getContext(), this); } - updateActiveState(selection: BaseSelection|null) { + protected updateActiveState(selection: BaseSelection|null) { const isActive = this.definition.isActive(selection, this.getContext()); this.setActiveState(isActive); } + protected updateDisabledState(selection: BaseSelection|null) { + if (this.definition.isDisabled) { + const isDisabled = this.definition.isDisabled(selection, this.getContext()); + this.toggleDisabled(isDisabled); + } + } + setActiveState(active: boolean) { this.active = active; this.dom?.classList.toggle('editor-button-active', this.active); @@ -83,6 +99,7 @@ export class EditorButton extends EditorUiElement { updateState(state: EditorUiStateUpdate): void { this.updateActiveState(state.selection); + this.updateDisabledState(state.selection); } isActive(): boolean { diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index ae6a292a2..d2b179eb6 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -1,6 +1,6 @@ import {EditorButton} from "./framework/buttons"; import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiElement} from "./framework/core"; -import {el} from "../helpers"; +import {$selectionContainsNodeType, el} from "../helpers"; import {EditorFormatMenu} from "./framework/blocks/format-menu"; import {FormatPreviewButton} from "./framework/blocks/format-preview-button"; import {EditorDropdownButton} from "./framework/blocks/dropdown-button"; @@ -11,7 +11,7 @@ import {EditorOverflowContainer} from "./framework/blocks/overflow-container"; import { deleteColumn, deleteRow, - deleteTable, insertColumnAfter, + deleteTable, deleteTableMenuAction, insertColumnAfter, insertColumnBefore, insertRowAbove, insertRowBelow, @@ -50,6 +50,7 @@ import { link, media, unlink } from "./defaults/buttons/objects"; +import {$isTableNode} from "@lexical/table"; export function getMainEditorFullToolbar(): EditorContainerUiElement { return new EditorSimpleClassContainer('editor-toolbar-main', [ @@ -68,7 +69,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { new FormatPreviewButton(el('h5'), h5), new FormatPreviewButton(el('blockquote'), blockquote), new FormatPreviewButton(el('p'), paragraph), - new EditorDropdownButton({label: 'Callouts'}, true, [ + new EditorDropdownButton({button: {label: 'Callouts'}, showOnHover: true, direction: 'vertical'}, [ new FormatPreviewButton(el('p', {class: 'callout info'}), infoCallout), new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout), new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout), @@ -81,10 +82,10 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { new EditorButton(bold), new EditorButton(italic), new EditorButton(underline), - new EditorDropdownButton(new EditorColorButton(textColor, 'color'), false, [ + new EditorDropdownButton({ button: new EditorColorButton(textColor, 'color') }, [ new EditorColorPicker('color'), ]), - new EditorDropdownButton(new EditorColorButton(highlightColor, 'background-color'), false, [ + new EditorDropdownButton({button: new EditorColorButton(highlightColor, 'background-color')}, [ new EditorColorPicker('background-color'), ]), new EditorButton(strikethrough), @@ -112,9 +113,14 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement { // Insert types new EditorOverflowContainer(8, [ new EditorButton(link), - new EditorDropdownButton(table, false, [ - new EditorTableCreator(), + + new EditorDropdownButton({button: table, direction: 'vertical'}, [ + new EditorDropdownButton({button: {...table, format: 'long'}, showOnHover: true}, [ + new EditorTableCreator(), + ]), + new EditorButton(deleteTableMenuAction), ]), + new EditorButton(image), new EditorButton(horizontalRule), new EditorButton(codeBlock), diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index 4ffff3cc0..0cf145559 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -59,6 +59,18 @@ body.editor-is-fullscreen { background-color: #ceebff; color: #000; } +.editor-button-long { + display: flex !important; + flex-direction: row; + align-items: center; + justify-content: start; + gap: .5rem; +} +.editor-button-text { + font-weight: 400; + color: #000; + font-size: 12.2px; +} .editor-button-format-preview { padding: 4px 6px; display: block; @@ -84,21 +96,20 @@ body.editor-is-fullscreen { display: flex; flex-direction: row; } -.editor-menu-list { +.editor-dropdown-menu-vertical { display: flex; flex-direction: column; align-items: stretch; } -.editor-menu-list .editor-button { +.editor-dropdown-menu-vertical .editor-button { border-bottom: 0; text-align: start; display: block; width: 100%; } -.editor-menu-list > .editor-dropdown-menu-container .editor-dropdown-menu { +.editor-dropdown-menu-vertical > .editor-dropdown-menu-container .editor-dropdown-menu { inset-inline-start: 100%; top: 0; - flex-direction: column; } .editor-format-menu-toggle {