]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Started table menu options
authorDan Brown <redacted>
Fri, 2 Aug 2024 10:16:54 +0000 (11:16 +0100)
committerDan Brown <redacted>
Fri, 2 Aug 2024 10:16:54 +0000 (11:16 +0100)
Updated UI elements to handle new scenarios needed in more complex table
menu

resources/js/wysiwyg/nodes/custom-list-item.ts
resources/js/wysiwyg/todo.md
resources/js/wysiwyg/ui/defaults/buttons/tables.ts
resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts
resources/js/wysiwyg/ui/framework/blocks/format-menu.ts
resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts
resources/js/wysiwyg/ui/framework/buttons.ts
resources/js/wysiwyg/ui/toolbars.ts
resources/sass/_editor.scss

index 53467e10bc90b323f198b37ca44a460b69f6c254..21b9f6d5f932b004e0d7b4f198e467cd3be3c418 100644 (file)
@@ -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";
index dda05f1da8069524d53428974d65dd96f6cb5ba1..1a367d0dd1a7c94a03f119bb6dbe3bbac203a55d 100644 (file)
@@ -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
index 32fa49f88f38c3f68696f501b7b4db6282fba1e7..b6b92e1970f4c4eca75dcd9780fe166331431b94 100644 (file)
@@ -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 {
+    $getNodeFromSelection,
+    $selectionContainsNodeType
+} from "../../../helpers";
 import {$getSelection} from "lexical";
-import {$isCustomTableNode, CustomTableNode} from "../../../nodes/custom-table";
+import {$isCustomTableNode} from "../../../nodes/custom-table";
 import {
-    $deleteTableColumn, $deleteTableColumn__EXPERIMENTAL,
+    $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,
index a75cf64fef53cc711b24fb3031fd14aba338ee3c..24659b5469cd0da30fb180b057c0b7053e2d7cfe 100644 (file)
@@ -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);
index 52a9c380986f8bf2766d3f377549d85b18c9d336..b0834fe4d48777ecc9f2584ac1111d775d3c54f7 100644 (file)
@@ -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);
 
index 83f394d9d869ca1169f10bef38512e6211701114..108992db889cd4fbf290e668ddff7535f93f5931 100644 (file)
@@ -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);
     }
 
index 4418be6237c2853d73d58d52e5241447d9242238..9a23edfb7f68108b66508c273eefc3a041b5df58 100644 (file)
@@ -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 {
index ae6a292a2710025e5cbde3385a1c4d464b6f2e4a..d2b179eb6ce46c904c1b6d1d03249efafd8deb4a 100644 (file)
@@ -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),
index 4ffff3cc0c79f6576a2e3ddbe43ae3d4f9e3c6a8..0cf14555977b1e5d91d33e9732be7b4030623f2c 100644 (file)
@@ -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 {
Morty Proxy This is a proxified and sanitized view of the page, visit original site.