diff --git a/apps/client/src/menus/context_menu.ts b/apps/client/src/menus/context_menu.ts index 983c0a48c..574d3091c 100644 --- a/apps/client/src/menus/context_menu.ts +++ b/apps/client/src/menus/context_menu.ts @@ -2,6 +2,7 @@ import { KeyboardActionNames } from "@triliumnext/commons"; import keyboardActionService, { getActionSync } from "../services/keyboard_actions.js"; import note_tooltip from "../services/note_tooltip.js"; import utils from "../services/utils.js"; +import { should } from "vitest"; export interface ContextMenuOptions { x: number; @@ -14,8 +15,13 @@ export interface ContextMenuOptions { onHide?: () => void; } -interface MenuSeparatorItem { - title: "----"; +export interface MenuSeparatorItem { + kind: "separator"; +} + +export interface MenuHeader { + title: string; + kind: "header"; } export interface MenuItemBadge { @@ -45,7 +51,7 @@ export interface MenuCommandItem { columns?: number; } -export type MenuItem = MenuCommandItem | MenuSeparatorItem; +export type MenuItem = MenuCommandItem | MenuSeparatorItem | MenuHeader; export type MenuHandler = (item: MenuCommandItem, e: JQuery.MouseDownEvent) => void; export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent; @@ -150,14 +156,51 @@ class ContextMenu { .addClass("show"); } - addItems($parent: JQuery, items: MenuItem[]) { - for (const item of items) { + addItems($parent: JQuery, items: MenuItem[], multicolumn = false) { + let $group = $parent; // The current group or parent element to which items are being appended + let shouldStartNewGroup = false; // If true, the next item will start a new group + let shouldResetGroup = false; // If true, the next item will be the last one from the group + + for (let index = 0; index < items.length; index++) { + const item = items[index]; if (!item) { continue; } - if (item.title === "----") { - $parent.append($("
").addClass("dropdown-divider")); + // If the current item is a header, start a new group. This group will contain the + // header and the next item that follows the header. + if ("kind" in item && item.kind === "header") { + if (multicolumn && !shouldResetGroup) { + shouldStartNewGroup = true; + } + } + + // If the next item is a separator, start a new group. This group will contain the + // current item, the separator, and the next item after the separator. + const nextItem = (index < items.length - 1) ? items[index + 1] : null; + if (multicolumn && nextItem && "kind" in nextItem && nextItem.kind === "separator") { + if (!shouldResetGroup) { + shouldStartNewGroup = true; + } else { + shouldResetGroup = true; // Continue the current group + } + } + + // Create a new group to avoid column breaks before and after the seaparator / header. + // This is a workaround for Firefox not supporting break-before / break-after: avoid + // for columns. + if (shouldStartNewGroup) { + $group = $("