mirror of
https://github.com/zadam/trilium.git
synced 2025-12-21 06:44:24 +01:00
feat(breadcrumb): get tree menu to show
This commit is contained in:
parent
587ea42700
commit
96a6ea4c7a
@ -1,21 +1,23 @@
|
|||||||
import NoteColorPicker from "./custom-items/NoteColorPicker.jsx";
|
|
||||||
import treeService from "../services/tree.js";
|
|
||||||
import froca from "../services/froca.js";
|
|
||||||
import clipboard from "../services/clipboard.js";
|
|
||||||
import noteCreateService from "../services/note_create.js";
|
|
||||||
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
|
|
||||||
import appContext, { type ContextMenuCommandData, type FilteredCommandNames } from "../components/app_context.js";
|
import appContext, { type ContextMenuCommandData, type FilteredCommandNames } from "../components/app_context.js";
|
||||||
|
import type { SelectMenuItemEventListener } from "../components/events.js";
|
||||||
|
import type FAttachment from "../entities/fattachment.js";
|
||||||
|
import FBranch from "../entities/fbranch.js";
|
||||||
|
import FNote from "../entities/fnote.js";
|
||||||
|
import attributes from "../services/attributes.js";
|
||||||
|
import { executeBulkActions } from "../services/bulk_action.js";
|
||||||
|
import clipboard from "../services/clipboard.js";
|
||||||
|
import dialogService from "../services/dialog.js";
|
||||||
|
import froca from "../services/froca.js";
|
||||||
|
import { t } from "../services/i18n.js";
|
||||||
|
import noteCreateService from "../services/note_create.js";
|
||||||
import noteTypesService from "../services/note_types.js";
|
import noteTypesService from "../services/note_types.js";
|
||||||
import server from "../services/server.js";
|
import server from "../services/server.js";
|
||||||
import toastService from "../services/toast.js";
|
import toastService from "../services/toast.js";
|
||||||
import dialogService from "../services/dialog.js";
|
import treeService from "../services/tree.js";
|
||||||
import { t } from "../services/i18n.js";
|
|
||||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
|
||||||
import type FAttachment from "../entities/fattachment.js";
|
|
||||||
import type { SelectMenuItemEventListener } from "../components/events.js";
|
|
||||||
import utils from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import attributes from "../services/attributes.js";
|
import type NoteTreeWidget from "../widgets/note_tree.js";
|
||||||
import { executeBulkActions } from "../services/bulk_action.js";
|
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
|
||||||
|
import NoteColorPicker from "./custom-items/NoteColorPicker.jsx";
|
||||||
|
|
||||||
// TODO: Deduplicate once client/server is well split.
|
// TODO: Deduplicate once client/server is well split.
|
||||||
interface ConvertToAttachmentResponse {
|
interface ConvertToAttachmentResponse {
|
||||||
@ -53,227 +55,16 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
lastTargetNode.classList.add('fancytree-menu-target');
|
lastTargetNode.classList.add('fancytree-menu-target');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMenuItems(): Promise<MenuItem<TreeCommandNames>[]> {
|
async getMenuItems() {
|
||||||
const note = this.node.data.noteId ? await froca.getNote(this.node.data.noteId) : null;
|
const note = this.node.data.noteId ? await froca.getNote(this.node.data.noteId) : null;
|
||||||
const branch = froca.getBranch(this.node.data.branchId);
|
|
||||||
const isNotRoot = note?.noteId !== "root";
|
|
||||||
const isHoisted = note?.noteId === appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
|
||||||
const parentNote = isNotRoot && branch ? await froca.getNote(branch.parentNoteId) : null;
|
|
||||||
|
|
||||||
// some actions don't support multi-note, so they are disabled when notes are selected,
|
|
||||||
// the only exception is when the only selected note is the one that was right-clicked, then
|
|
||||||
// it's clear what the user meant to do.
|
|
||||||
const selNodes = this.treeWidget.getSelectedNodes();
|
const selNodes = this.treeWidget.getSelectedNodes();
|
||||||
const selectedNotes = await froca.getNotes(selNodes.map(node => node.data.noteId));
|
|
||||||
if (note && !selectedNotes.includes(note)) selectedNotes.push(note);
|
|
||||||
const isArchived = selectedNotes.every(note => note.isArchived);
|
|
||||||
const canToggleArchived = !selectedNotes.some(note => note.isArchived !== isArchived);
|
|
||||||
|
|
||||||
const noSelectedNotes = selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node);
|
return buildTreeMenuItems({
|
||||||
|
note,
|
||||||
const notSearch = note?.type !== "search";
|
branch: froca.getBranch(this.node.data.branchId),
|
||||||
const notOptionsOrHelp = !note?.noteId.startsWith("_options") && !note?.noteId.startsWith("_help");
|
selectedNotes: await froca.getNotes(selNodes.map(node => node.data.noteId)),
|
||||||
const parentNotSearch = !parentNote || parentNote.type !== "search";
|
noSelectedNotes: selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node)
|
||||||
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
|
});
|
||||||
|
|
||||||
const items: (MenuItem<TreeCommandNames> | null)[] = [
|
|
||||||
{ title: t("tree-context-menu.open-in-a-new-tab"), command: "openInTab", shortcut: "Ctrl+Click", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
|
|
||||||
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
|
|
||||||
{ title: t("tree-context-menu.open-in-popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit", enabled: noSelectedNotes },
|
|
||||||
|
|
||||||
isHoisted
|
|
||||||
? null
|
|
||||||
: {
|
|
||||||
title: `${t("tree-context-menu.hoist-note")}`,
|
|
||||||
command: "toggleNoteHoisting",
|
|
||||||
keyboardShortcut: "toggleNoteHoisting",
|
|
||||||
uiIcon: "bx bxs-chevrons-up",
|
|
||||||
enabled: noSelectedNotes && notSearch
|
|
||||||
},
|
|
||||||
!isHoisted || !isNotRoot
|
|
||||||
? null
|
|
||||||
: { title: t("tree-context-menu.unhoist-note"), command: "toggleNoteHoisting", keyboardShortcut: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
|
|
||||||
|
|
||||||
{ kind: "separator" },
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.insert-note-after"),
|
|
||||||
command: "insertNoteAfter",
|
|
||||||
keyboardShortcut: "createNoteAfter",
|
|
||||||
uiIcon: "bx bx-plus",
|
|
||||||
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
|
|
||||||
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptionsOrHelp,
|
|
||||||
columns: 2
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.insert-child-note"),
|
|
||||||
command: "insertChildNote",
|
|
||||||
keyboardShortcut: "createNoteInto",
|
|
||||||
uiIcon: "bx bx-plus",
|
|
||||||
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
|
|
||||||
enabled: notSearch && noSelectedNotes && notOptionsOrHelp,
|
|
||||||
columns: 2
|
|
||||||
},
|
|
||||||
|
|
||||||
{ kind: "separator" },
|
|
||||||
|
|
||||||
{ title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
|
|
||||||
|
|
||||||
{ title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
|
|
||||||
|
|
||||||
{ kind: "separator" },
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.advanced"),
|
|
||||||
uiIcon: "bx bxs-wrench",
|
|
||||||
enabled: true,
|
|
||||||
items: [
|
|
||||||
{ title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus", enabled: true },
|
|
||||||
|
|
||||||
{ kind: "separator" },
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.edit-branch-prefix"),
|
|
||||||
command: "editBranchPrefix",
|
|
||||||
keyboardShortcut: "editBranchPrefix",
|
|
||||||
uiIcon: "bx bx-rename",
|
|
||||||
enabled: isNotRoot && parentNotSearch && notOptionsOrHelp
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title:
|
|
||||||
t("tree-context-menu.convert-to-attachment"),
|
|
||||||
command: "convertNoteToAttachment",
|
|
||||||
uiIcon: "bx bx-paperclip",
|
|
||||||
enabled: isNotRoot && !isHoisted && notOptionsOrHelp && selectedNotes.some(note => note.isEligibleForConversionToAttachment())
|
|
||||||
},
|
|
||||||
|
|
||||||
{ kind: "separator" },
|
|
||||||
|
|
||||||
{ title: t("tree-context-menu.expand-subtree"), command: "expandSubtree", keyboardShortcut: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
|
|
||||||
{ title: t("tree-context-menu.collapse-subtree"), command: "collapseSubtree", keyboardShortcut: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.sort-by"),
|
|
||||||
command: "sortChildNotes",
|
|
||||||
keyboardShortcut: "sortChildNotes",
|
|
||||||
uiIcon: "bx bx-sort-down",
|
|
||||||
enabled: noSelectedNotes && notSearch
|
|
||||||
},
|
|
||||||
|
|
||||||
{ 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: noSelectedNotes && notOptionsOrHelp }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{ kind: "separator" },
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.cut"),
|
|
||||||
command: "cutNotesToClipboard",
|
|
||||||
keyboardShortcut: "cutNotesToClipboard",
|
|
||||||
uiIcon: "bx bx-cut",
|
|
||||||
enabled: isNotRoot && !isHoisted && parentNotSearch
|
|
||||||
},
|
|
||||||
|
|
||||||
{ title: t("tree-context-menu.copy-clone"), command: "copyNotesToClipboard", keyboardShortcut: "copyNotesToClipboard", uiIcon: "bx bx-copy", enabled: isNotRoot && !isHoisted },
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.paste-into"),
|
|
||||||
command: "pasteNotesFromClipboard",
|
|
||||||
keyboardShortcut: "pasteNotesFromClipboard",
|
|
||||||
uiIcon: "bx bx-paste",
|
|
||||||
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.paste-after"),
|
|
||||||
command: "pasteNotesAfterFromClipboard",
|
|
||||||
uiIcon: "bx bx-paste",
|
|
||||||
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.move-to"),
|
|
||||||
command: "moveNotesTo",
|
|
||||||
keyboardShortcut: "moveNotesTo",
|
|
||||||
uiIcon: "bx bx-transfer",
|
|
||||||
enabled: isNotRoot && !isHoisted && parentNotSearch
|
|
||||||
},
|
|
||||||
|
|
||||||
{ title: t("tree-context-menu.clone-to"), command: "cloneNotesTo", keyboardShortcut: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted },
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.duplicate"),
|
|
||||||
command: "duplicateSubtree",
|
|
||||||
keyboardShortcut: "duplicateSubtree",
|
|
||||||
uiIcon: "bx bx-outline",
|
|
||||||
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: !isArchived ? t("tree-context-menu.archive") : t("tree-context-menu.unarchive"),
|
|
||||||
uiIcon: !isArchived ? "bx bx-archive" : "bx bx-archive-out",
|
|
||||||
enabled: canToggleArchived,
|
|
||||||
handler: () => {
|
|
||||||
if (!selectedNotes.length) return;
|
|
||||||
|
|
||||||
if (selectedNotes.length == 1) {
|
|
||||||
const note = selectedNotes[0];
|
|
||||||
if (!isArchived) {
|
|
||||||
attributes.addLabel(note.noteId, "archived");
|
|
||||||
} else {
|
|
||||||
attributes.removeOwnedLabelByName(note, "archived");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const noteIds = selectedNotes.map(note => note.noteId);
|
|
||||||
if (!isArchived) {
|
|
||||||
executeBulkActions(noteIds, [{
|
|
||||||
name: "addLabel", labelName: "archived"
|
|
||||||
}]);
|
|
||||||
} else {
|
|
||||||
executeBulkActions(noteIds, [{
|
|
||||||
name: "deleteLabel", labelName: "archived"
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.delete"),
|
|
||||||
command: "deleteNotes",
|
|
||||||
keyboardShortcut: "deleteNotes",
|
|
||||||
uiIcon: "bx bx-trash destructive-action-icon",
|
|
||||||
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp
|
|
||||||
},
|
|
||||||
|
|
||||||
{ kind: "separator"},
|
|
||||||
|
|
||||||
(notOptionsOrHelp && selectedNotes.length === 1) ? {
|
|
||||||
kind: "custom",
|
|
||||||
componentFn: () => {
|
|
||||||
return NoteColorPicker({note});
|
|
||||||
}
|
|
||||||
} : null,
|
|
||||||
|
|
||||||
{ kind: "separator" },
|
|
||||||
|
|
||||||
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-import", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
|
|
||||||
|
|
||||||
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-export", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
|
|
||||||
|
|
||||||
{ kind: "separator" },
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("tree-context-menu.search-in-subtree"),
|
|
||||||
command: "searchInSubtree",
|
|
||||||
keyboardShortcut: "searchInSubtree",
|
|
||||||
uiIcon: "bx bx-search",
|
|
||||||
enabled: notSearch && noSelectedNotes
|
|
||||||
}
|
|
||||||
];
|
|
||||||
return items.filter((row) => row !== null) as MenuItem<TreeCommandNames>[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectMenuItemHandler({ command, type, templateNoteId }: MenuCommandItem<TreeCommandNames>) {
|
async selectMenuItemHandler({ command, type, templateNoteId }: MenuCommandItem<TreeCommandNames>) {
|
||||||
@ -292,17 +83,17 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
noteCreateService.createNote(parentNotePath, {
|
noteCreateService.createNote(parentNotePath, {
|
||||||
target: "after",
|
target: "after",
|
||||||
targetBranchId: this.node.data.branchId,
|
targetBranchId: this.node.data.branchId,
|
||||||
type: type,
|
type,
|
||||||
isProtected: isProtected,
|
isProtected,
|
||||||
templateNoteId: templateNoteId
|
templateNoteId
|
||||||
});
|
});
|
||||||
} else if (command === "insertChildNote") {
|
} else if (command === "insertChildNote") {
|
||||||
const parentNotePath = treeService.getNotePath(this.node);
|
const parentNotePath = treeService.getNotePath(this.node);
|
||||||
|
|
||||||
noteCreateService.createNote(parentNotePath, {
|
noteCreateService.createNote(parentNotePath, {
|
||||||
type: type,
|
type,
|
||||||
isProtected: this.node.data.isProtected,
|
isProtected: this.node.data.isProtected,
|
||||||
templateNoteId: templateNoteId
|
templateNoteId
|
||||||
});
|
});
|
||||||
} else if (command === "openNoteInSplit") {
|
} else if (command === "openNoteInSplit") {
|
||||||
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
|
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
|
||||||
@ -310,7 +101,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
|
|
||||||
this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath });
|
this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath });
|
||||||
} else if (command === "openNoteInPopup") {
|
} else if (command === "openNoteInPopup") {
|
||||||
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
|
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
|
||||||
} else if (command === "convertNoteToAttachment") {
|
} else if (command === "convertNoteToAttachment") {
|
||||||
if (!(await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm")))) {
|
if (!(await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm")))) {
|
||||||
return;
|
return;
|
||||||
@ -332,11 +123,11 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
|
|
||||||
toastService.showMessage(t("tree-context-menu.converted-to-attachments", { count: converted }));
|
toastService.showMessage(t("tree-context-menu.converted-to-attachments", { count: converted }));
|
||||||
} else if (command === "copyNotePathToClipboard") {
|
} else if (command === "copyNotePathToClipboard") {
|
||||||
navigator.clipboard.writeText("#" + notePath);
|
navigator.clipboard.writeText(`#${ notePath}`);
|
||||||
} else if (command) {
|
} else if (command) {
|
||||||
this.treeWidget.triggerCommand<TreeCommandNames>(command, {
|
this.treeWidget.triggerCommand<TreeCommandNames>(command, {
|
||||||
node: this.node,
|
node: this.node,
|
||||||
notePath: notePath,
|
notePath,
|
||||||
noteId: this.node.data.noteId,
|
noteId: this.node.data.noteId,
|
||||||
selectedOrActiveBranchIds: this.treeWidget.getSelectedOrActiveBranchIds(this.node),
|
selectedOrActiveBranchIds: this.treeWidget.getSelectedOrActiveBranchIds(this.node),
|
||||||
selectedOrActiveNoteIds: this.treeWidget.getSelectedOrActiveNoteIds(this.node)
|
selectedOrActiveNoteIds: this.treeWidget.getSelectedOrActiveNoteIds(this.node)
|
||||||
@ -344,3 +135,225 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function buildTreeMenuItems({ note, branch, selectedNotes, noSelectedNotes }: {
|
||||||
|
note: FNote | null;
|
||||||
|
branch: FBranch | undefined;
|
||||||
|
selectedNotes: FNote[];
|
||||||
|
noSelectedNotes: boolean;
|
||||||
|
}): Promise<MenuItem<TreeCommandNames>[]> {
|
||||||
|
const isNotRoot = note?.noteId !== "root";
|
||||||
|
const isHoisted = note?.noteId === appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||||
|
const parentNote = isNotRoot && branch ? await froca.getNote(branch.parentNoteId) : null;
|
||||||
|
|
||||||
|
// some actions don't support multi-note, so they are disabled when notes are selected,
|
||||||
|
// the only exception is when the only selected note is the one that was right-clicked, then
|
||||||
|
// it's clear what the user meant to do.
|
||||||
|
if (note && !selectedNotes.includes(note)) selectedNotes.push(note);
|
||||||
|
const isArchived = selectedNotes.every(note => note.isArchived);
|
||||||
|
const canToggleArchived = !selectedNotes.some(note => note.isArchived !== isArchived);
|
||||||
|
|
||||||
|
const notSearch = note?.type !== "search";
|
||||||
|
const notOptionsOrHelp = !note?.noteId.startsWith("_options") && !note?.noteId.startsWith("_help");
|
||||||
|
const parentNotSearch = !parentNote || parentNote.type !== "search";
|
||||||
|
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
|
||||||
|
|
||||||
|
const items: (MenuItem<TreeCommandNames> | null)[] = [
|
||||||
|
{ title: t("tree-context-menu.open-in-a-new-tab"), command: "openInTab", shortcut: "Ctrl+Click", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
|
||||||
|
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
|
||||||
|
{ title: t("tree-context-menu.open-in-popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit", enabled: noSelectedNotes },
|
||||||
|
|
||||||
|
isHoisted
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
title: `${t("tree-context-menu.hoist-note")}`,
|
||||||
|
command: "toggleNoteHoisting",
|
||||||
|
keyboardShortcut: "toggleNoteHoisting",
|
||||||
|
uiIcon: "bx bxs-chevrons-up",
|
||||||
|
enabled: noSelectedNotes && notSearch
|
||||||
|
},
|
||||||
|
!isHoisted || !isNotRoot
|
||||||
|
? null
|
||||||
|
: { title: t("tree-context-menu.unhoist-note"), command: "toggleNoteHoisting", keyboardShortcut: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
|
||||||
|
|
||||||
|
{ kind: "separator" },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.insert-note-after"),
|
||||||
|
command: "insertNoteAfter",
|
||||||
|
keyboardShortcut: "createNoteAfter",
|
||||||
|
uiIcon: "bx bx-plus",
|
||||||
|
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
|
||||||
|
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptionsOrHelp,
|
||||||
|
columns: 2
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.insert-child-note"),
|
||||||
|
command: "insertChildNote",
|
||||||
|
keyboardShortcut: "createNoteInto",
|
||||||
|
uiIcon: "bx bx-plus",
|
||||||
|
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
|
||||||
|
enabled: notSearch && noSelectedNotes && notOptionsOrHelp,
|
||||||
|
columns: 2
|
||||||
|
},
|
||||||
|
|
||||||
|
{ kind: "separator" },
|
||||||
|
|
||||||
|
{ title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
|
||||||
|
|
||||||
|
{ title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
|
||||||
|
|
||||||
|
{ kind: "separator" },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.advanced"),
|
||||||
|
uiIcon: "bx bxs-wrench",
|
||||||
|
enabled: true,
|
||||||
|
items: [
|
||||||
|
{ title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus", enabled: true },
|
||||||
|
|
||||||
|
{ kind: "separator" },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.edit-branch-prefix"),
|
||||||
|
command: "editBranchPrefix",
|
||||||
|
keyboardShortcut: "editBranchPrefix",
|
||||||
|
uiIcon: "bx bx-rename",
|
||||||
|
enabled: isNotRoot && parentNotSearch && notOptionsOrHelp
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
t("tree-context-menu.convert-to-attachment"),
|
||||||
|
command: "convertNoteToAttachment",
|
||||||
|
uiIcon: "bx bx-paperclip",
|
||||||
|
enabled: isNotRoot && !isHoisted && notOptionsOrHelp && selectedNotes.some(note => note.isEligibleForConversionToAttachment())
|
||||||
|
},
|
||||||
|
|
||||||
|
{ kind: "separator" },
|
||||||
|
|
||||||
|
{ title: t("tree-context-menu.expand-subtree"), command: "expandSubtree", keyboardShortcut: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
|
||||||
|
{ title: t("tree-context-menu.collapse-subtree"), command: "collapseSubtree", keyboardShortcut: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.sort-by"),
|
||||||
|
command: "sortChildNotes",
|
||||||
|
keyboardShortcut: "sortChildNotes",
|
||||||
|
uiIcon: "bx bx-sort-down",
|
||||||
|
enabled: noSelectedNotes && notSearch
|
||||||
|
},
|
||||||
|
|
||||||
|
{ 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: noSelectedNotes && notOptionsOrHelp }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{ kind: "separator" },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.cut"),
|
||||||
|
command: "cutNotesToClipboard",
|
||||||
|
keyboardShortcut: "cutNotesToClipboard",
|
||||||
|
uiIcon: "bx bx-cut",
|
||||||
|
enabled: isNotRoot && !isHoisted && parentNotSearch
|
||||||
|
},
|
||||||
|
|
||||||
|
{ title: t("tree-context-menu.copy-clone"), command: "copyNotesToClipboard", keyboardShortcut: "copyNotesToClipboard", uiIcon: "bx bx-copy", enabled: isNotRoot && !isHoisted },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.paste-into"),
|
||||||
|
command: "pasteNotesFromClipboard",
|
||||||
|
keyboardShortcut: "pasteNotesFromClipboard",
|
||||||
|
uiIcon: "bx bx-paste",
|
||||||
|
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.paste-after"),
|
||||||
|
command: "pasteNotesAfterFromClipboard",
|
||||||
|
uiIcon: "bx bx-paste",
|
||||||
|
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.move-to"),
|
||||||
|
command: "moveNotesTo",
|
||||||
|
keyboardShortcut: "moveNotesTo",
|
||||||
|
uiIcon: "bx bx-transfer",
|
||||||
|
enabled: isNotRoot && !isHoisted && parentNotSearch
|
||||||
|
},
|
||||||
|
|
||||||
|
{ title: t("tree-context-menu.clone-to"), command: "cloneNotesTo", keyboardShortcut: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.duplicate"),
|
||||||
|
command: "duplicateSubtree",
|
||||||
|
keyboardShortcut: "duplicateSubtree",
|
||||||
|
uiIcon: "bx bx-outline",
|
||||||
|
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: !isArchived ? t("tree-context-menu.archive") : t("tree-context-menu.unarchive"),
|
||||||
|
uiIcon: !isArchived ? "bx bx-archive" : "bx bx-archive-out",
|
||||||
|
enabled: canToggleArchived,
|
||||||
|
handler: () => {
|
||||||
|
if (!selectedNotes.length) return;
|
||||||
|
|
||||||
|
if (selectedNotes.length == 1) {
|
||||||
|
const note = selectedNotes[0];
|
||||||
|
if (!isArchived) {
|
||||||
|
attributes.addLabel(note.noteId, "archived");
|
||||||
|
} else {
|
||||||
|
attributes.removeOwnedLabelByName(note, "archived");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const noteIds = selectedNotes.map(note => note.noteId);
|
||||||
|
if (!isArchived) {
|
||||||
|
executeBulkActions(noteIds, [{
|
||||||
|
name: "addLabel", labelName: "archived"
|
||||||
|
}]);
|
||||||
|
} else {
|
||||||
|
executeBulkActions(noteIds, [{
|
||||||
|
name: "deleteLabel", labelName: "archived"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.delete"),
|
||||||
|
command: "deleteNotes",
|
||||||
|
keyboardShortcut: "deleteNotes",
|
||||||
|
uiIcon: "bx bx-trash destructive-action-icon",
|
||||||
|
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp
|
||||||
|
},
|
||||||
|
|
||||||
|
{ kind: "separator"},
|
||||||
|
|
||||||
|
(notOptionsOrHelp && selectedNotes.length === 1) ? {
|
||||||
|
kind: "custom",
|
||||||
|
componentFn: () => {
|
||||||
|
return NoteColorPicker({note});
|
||||||
|
}
|
||||||
|
} : null,
|
||||||
|
|
||||||
|
{ kind: "separator" },
|
||||||
|
|
||||||
|
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-import", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
|
||||||
|
|
||||||
|
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-export", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
|
||||||
|
|
||||||
|
{ kind: "separator" },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("tree-context-menu.search-in-subtree"),
|
||||||
|
command: "searchInSubtree",
|
||||||
|
keyboardShortcut: "searchInSubtree",
|
||||||
|
uiIcon: "bx bx-search",
|
||||||
|
enabled: notSearch && noSelectedNotes
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return items.filter((row) => row !== null) as MenuItem<TreeCommandNames>[];
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import NoteContext from "../../components/note_context";
|
|||||||
import FNote from "../../entities/fnote";
|
import FNote from "../../entities/fnote";
|
||||||
import contextMenu from "../../menus/context_menu";
|
import contextMenu from "../../menus/context_menu";
|
||||||
import link_context_menu from "../../menus/link_context_menu";
|
import link_context_menu from "../../menus/link_context_menu";
|
||||||
|
import { buildTreeMenuItems } from "../../menus/tree_context_menu";
|
||||||
import { getReadableTextColor } from "../../services/css_class_manager";
|
import { getReadableTextColor } from "../../services/css_class_manager";
|
||||||
import froca from "../../services/froca";
|
import froca from "../../services/froca";
|
||||||
import hoisted_note from "../../services/hoisted_note";
|
import hoisted_note from "../../services/hoisted_note";
|
||||||
@ -154,14 +155,31 @@ function BreadcrumbItem({ index, notePath, noteContext, notePathLength }: { inde
|
|||||||
return <NoteLink
|
return <NoteLink
|
||||||
notePath={notePath}
|
notePath={notePath}
|
||||||
noContextMenu
|
noContextMenu
|
||||||
onContextMenu={(e) => {
|
onContextMenu={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
const notePathArray = notePath.split("/");
|
||||||
|
const parentNoteId = notePathArray.at(-2);
|
||||||
|
const childNoteId = notePathArray.at(-1);
|
||||||
|
console.log(parentNoteId, childNoteId);
|
||||||
|
if (!parentNoteId || !childNoteId) return;
|
||||||
|
|
||||||
|
const branchId = await froca.getBranchId(parentNoteId, childNoteId);
|
||||||
|
if (!branchId) return;
|
||||||
|
|
||||||
|
const branch = froca.getBranch(branchId);
|
||||||
|
const note = await branch?.getNote();
|
||||||
|
if (!branch || !note) return;
|
||||||
|
|
||||||
|
const items = await buildTreeMenuItems({
|
||||||
|
branch,
|
||||||
|
note,
|
||||||
|
noSelectedNotes: true,
|
||||||
|
selectedNotes: []
|
||||||
|
});
|
||||||
|
|
||||||
contextMenu.show({
|
contextMenu.show({
|
||||||
items: [
|
items,
|
||||||
{
|
|
||||||
title: "Foo"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
x: e.pageX,
|
x: e.pageX,
|
||||||
y: e.pageY
|
y: e.pageY
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user