From d1820a6bc37a47edb298183045feec76b9f6243c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 16 Dec 2025 10:39:22 +0200 Subject: [PATCH] feat(breadcrumb): color selector --- apps/client/src/menus/link_context_menu.ts | 6 +- apps/client/src/widgets/layout/Breadcrumb.tsx | 115 ++++++++++-------- 2 files changed, 66 insertions(+), 55 deletions(-) diff --git a/apps/client/src/menus/link_context_menu.ts b/apps/client/src/menus/link_context_menu.ts index 2cba3aab4..b9c99c612 100644 --- a/apps/client/src/menus/link_context_menu.ts +++ b/apps/client/src/menus/link_context_menu.ts @@ -16,7 +16,7 @@ function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewS }); } -function getItems(e: ContextMenuEvent | LeafletMouseEvent): MenuItem[] { +function getItems(e: ContextMenuEvent | LeafletMouseEvent): MenuItem<"openNoteInNewTab" | "openNoteInNewSplit" | "openNoteInNewWindow" | "openNoteInPopup">[] { const ntxId = getNtxId(e); const isMobileSplitOpen = isMobile() && appContext.tabManager.getNoteContextById(ntxId).getMainContext().getSubContexts().length > 1; @@ -59,9 +59,9 @@ function getNtxId(e: ContextMenuEvent | LeafletMouseEvent) { return subContexts[subContexts.length - 1].ntxId; } else if (e.target instanceof HTMLElement) { return getClosestNtxId(e.target); - } + } return null; - + } export default { diff --git a/apps/client/src/widgets/layout/Breadcrumb.tsx b/apps/client/src/widgets/layout/Breadcrumb.tsx index 000698252..b00119f2e 100644 --- a/apps/client/src/widgets/layout/Breadcrumb.tsx +++ b/apps/client/src/widgets/layout/Breadcrumb.tsx @@ -3,11 +3,13 @@ import "./Breadcrumb.css"; import { useContext, useRef, useState } from "preact/hooks"; import { Fragment } from "preact/jsx-runtime"; -import appContext from "../../components/app_context"; +import appContext, { CommandNames } from "../../components/app_context"; import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; -import contextMenu from "../../menus/context_menu"; +import contextMenu, { MenuItem } from "../../menus/context_menu"; +import NoteColorPicker from "../../menus/custom-items/NoteColorPicker"; import link_context_menu from "../../menus/link_context_menu"; +import { TreeCommandNames } from "../../menus/tree_context_menu"; import attributes from "../../services/attributes"; import branches from "../../services/branches"; import { executeBulkActions } from "../../services/bulk_action"; @@ -186,59 +188,68 @@ function BreadcrumbItem({ index, notePath, noteContext, notePathLength }: { inde const parentNote = isNotRoot && branch ? await froca.getNote(branch.parentNoteId) : null; const parentNotSearch = !parentNote || parentNote.type !== "search"; - contextMenu.show({ - items: [ - ...link_context_menu.getItems(e), - { - title: `${t("tree-context-menu.hoist-note")}`, - command: "toggleNoteHoisting", - uiIcon: "bx bxs-chevrons-up", - enabled: notSearch - }, - { kind: "separator" }, - { - title: t("tree-context-menu.move-to"), - command: "moveNotesTo", - uiIcon: "bx bx-transfer", - enabled: isNotRoot && !isHoisted && parentNotSearch - }, - { - title: t("tree-context-menu.clone-to"), - command: "cloneNotesTo", - uiIcon: "bx bx-duplicate", - enabled: isNotRoot && !isHoisted - }, - { kind: "separator" }, - { title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-directions", enabled: true }, - { title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: notOptionsOrHelp }, - { kind: "separator" }, - { - title: t("tree-context-menu.duplicate"), - command: "duplicateSubtree", - uiIcon: "bx bx-outline", - enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp && note.isContentAvailable(), - handler: () => note_create.duplicateSubtree(noteId, branch.parentNoteId) - }, + const items = [ + ...link_context_menu.getItems(e), + { + title: `${t("tree-context-menu.hoist-note")}`, + command: "toggleNoteHoisting", + uiIcon: "bx bxs-chevrons-up", + enabled: notSearch + }, + { kind: "separator" }, + { + title: t("tree-context-menu.move-to"), + command: "moveNotesTo", + uiIcon: "bx bx-transfer", + enabled: isNotRoot && !isHoisted && parentNotSearch + }, + { + title: t("tree-context-menu.clone-to"), + command: "cloneNotesTo", + uiIcon: "bx bx-duplicate", + enabled: isNotRoot && !isHoisted + }, + { kind: "separator" }, + { title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-directions", enabled: true }, + { title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: notOptionsOrHelp }, + { kind: "separator" }, + { + title: t("tree-context-menu.duplicate"), + command: "duplicateSubtree", + uiIcon: "bx bx-outline", + enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp && note.isContentAvailable(), + handler: () => note_create.duplicateSubtree(noteId, branch.parentNoteId) + }, - { - title: !isArchived ? t("tree-context-menu.archive") : t("tree-context-menu.unarchive"), - uiIcon: !isArchived ? "bx bx-archive" : "bx bx-archive-out", - handler: () => { - if (!isArchived) { - attributes.addLabel(note.noteId, "archived"); - } else { - attributes.removeOwnedLabelByName(note, "archived"); - } + { + title: !isArchived ? t("tree-context-menu.archive") : t("tree-context-menu.unarchive"), + uiIcon: !isArchived ? "bx bx-archive" : "bx bx-archive-out", + handler: () => { + if (!isArchived) { + attributes.addLabel(note.noteId, "archived"); + } else { + attributes.removeOwnedLabelByName(note, "archived"); } - }, - { - title: t("tree-context-menu.delete"), - command: "deleteNotes", - uiIcon: "bx bx-trash destructive-action-icon", - enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp, - handler: () => branches.deleteNotes([ branchId ]) } - ], + }, + { + title: t("tree-context-menu.delete"), + command: "deleteNotes", + uiIcon: "bx bx-trash destructive-action-icon", + enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp, + handler: () => branches.deleteNotes([ branchId ]) + }, + { kind: "separator"}, + (notOptionsOrHelp ? { + kind: "custom", + componentFn: () => { + return NoteColorPicker({note}); + } + } : null), + ]; + + contextMenu.show({ + items: items.filter(Boolean) as MenuItem[], x: e.pageX, y: e.pageY, selectMenuItemHandler: ({ command }) => {