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 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 server from "../services/server.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import dialogService from "../services/dialog.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 treeService from "../services/tree.js";
|
||||
import utils from "../services/utils.js";
|
||||
import attributes from "../services/attributes.js";
|
||||
import { executeBulkActions } from "../services/bulk_action.js";
|
||||
import type NoteTreeWidget from "../widgets/note_tree.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.
|
||||
interface ConvertToAttachmentResponse {
|
||||
@ -53,9 +55,93 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
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 branch = froca.getBranch(this.node.data.branchId);
|
||||
const selNodes = this.treeWidget.getSelectedNodes();
|
||||
|
||||
return buildTreeMenuItems({
|
||||
note,
|
||||
branch: froca.getBranch(this.node.data.branchId),
|
||||
selectedNotes: await froca.getNotes(selNodes.map(node => node.data.noteId)),
|
||||
noSelectedNotes: selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node)
|
||||
});
|
||||
}
|
||||
|
||||
async selectMenuItemHandler({ command, type, templateNoteId }: MenuCommandItem<TreeCommandNames>) {
|
||||
const notePath = treeService.getNotePath(this.node);
|
||||
|
||||
if (utils.isMobile()) {
|
||||
this.treeWidget.triggerCommand("setActiveScreen", { screen: "detail" });
|
||||
}
|
||||
|
||||
if (command === "openInTab") {
|
||||
appContext.tabManager.openTabWithNoteWithHoisting(notePath);
|
||||
} else if (command === "insertNoteAfter") {
|
||||
const parentNotePath = treeService.getNotePath(this.node.getParent());
|
||||
const isProtected = treeService.getParentProtectedStatus(this.node);
|
||||
|
||||
noteCreateService.createNote(parentNotePath, {
|
||||
target: "after",
|
||||
targetBranchId: this.node.data.branchId,
|
||||
type,
|
||||
isProtected,
|
||||
templateNoteId
|
||||
});
|
||||
} else if (command === "insertChildNote") {
|
||||
const parentNotePath = treeService.getNotePath(this.node);
|
||||
|
||||
noteCreateService.createNote(parentNotePath, {
|
||||
type,
|
||||
isProtected: this.node.data.isProtected,
|
||||
templateNoteId
|
||||
});
|
||||
} else if (command === "openNoteInSplit") {
|
||||
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
|
||||
const { ntxId } = subContexts?.[subContexts.length - 1] ?? {};
|
||||
|
||||
this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath });
|
||||
} else if (command === "openNoteInPopup") {
|
||||
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
|
||||
} else if (command === "convertNoteToAttachment") {
|
||||
if (!(await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm")))) {
|
||||
return;
|
||||
}
|
||||
|
||||
let converted = 0;
|
||||
|
||||
for (const noteId of this.treeWidget.getSelectedOrActiveNoteIds(this.node)) {
|
||||
const note = await froca.getNote(noteId);
|
||||
|
||||
if (note?.isEligibleForConversionToAttachment()) {
|
||||
const { attachment } = await server.post<ConvertToAttachmentResponse>(`notes/${note.noteId}/convert-to-attachment`);
|
||||
|
||||
if (attachment) {
|
||||
converted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toastService.showMessage(t("tree-context-menu.converted-to-attachments", { count: converted }));
|
||||
} else if (command === "copyNotePathToClipboard") {
|
||||
navigator.clipboard.writeText(`#${ notePath}`);
|
||||
} else if (command) {
|
||||
this.treeWidget.triggerCommand<TreeCommandNames>(command, {
|
||||
node: this.node,
|
||||
notePath,
|
||||
noteId: this.node.data.noteId,
|
||||
selectedOrActiveBranchIds: this.treeWidget.getSelectedOrActiveBranchIds(this.node),
|
||||
selectedOrActiveNoteIds: this.treeWidget.getSelectedOrActiveNoteIds(this.node)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -63,14 +149,10 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
// 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 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);
|
||||
|
||||
const notSearch = note?.type !== "search";
|
||||
const notOptionsOrHelp = !note?.noteId.startsWith("_options") && !note?.noteId.startsWith("_help");
|
||||
const parentNotSearch = !parentNote || parentNote.type !== "search";
|
||||
@ -275,72 +357,3 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
||||
];
|
||||
return items.filter((row) => row !== null) as MenuItem<TreeCommandNames>[];
|
||||
}
|
||||
|
||||
async selectMenuItemHandler({ command, type, templateNoteId }: MenuCommandItem<TreeCommandNames>) {
|
||||
const notePath = treeService.getNotePath(this.node);
|
||||
|
||||
if (utils.isMobile()) {
|
||||
this.treeWidget.triggerCommand("setActiveScreen", { screen: "detail" });
|
||||
}
|
||||
|
||||
if (command === "openInTab") {
|
||||
appContext.tabManager.openTabWithNoteWithHoisting(notePath);
|
||||
} else if (command === "insertNoteAfter") {
|
||||
const parentNotePath = treeService.getNotePath(this.node.getParent());
|
||||
const isProtected = treeService.getParentProtectedStatus(this.node);
|
||||
|
||||
noteCreateService.createNote(parentNotePath, {
|
||||
target: "after",
|
||||
targetBranchId: this.node.data.branchId,
|
||||
type: type,
|
||||
isProtected: isProtected,
|
||||
templateNoteId: templateNoteId
|
||||
});
|
||||
} else if (command === "insertChildNote") {
|
||||
const parentNotePath = treeService.getNotePath(this.node);
|
||||
|
||||
noteCreateService.createNote(parentNotePath, {
|
||||
type: type,
|
||||
isProtected: this.node.data.isProtected,
|
||||
templateNoteId: templateNoteId
|
||||
});
|
||||
} else if (command === "openNoteInSplit") {
|
||||
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
|
||||
const { ntxId } = subContexts?.[subContexts.length - 1] ?? {};
|
||||
|
||||
this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath });
|
||||
} else if (command === "openNoteInPopup") {
|
||||
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
|
||||
} else if (command === "convertNoteToAttachment") {
|
||||
if (!(await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm")))) {
|
||||
return;
|
||||
}
|
||||
|
||||
let converted = 0;
|
||||
|
||||
for (const noteId of this.treeWidget.getSelectedOrActiveNoteIds(this.node)) {
|
||||
const note = await froca.getNote(noteId);
|
||||
|
||||
if (note?.isEligibleForConversionToAttachment()) {
|
||||
const { attachment } = await server.post<ConvertToAttachmentResponse>(`notes/${note.noteId}/convert-to-attachment`);
|
||||
|
||||
if (attachment) {
|
||||
converted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toastService.showMessage(t("tree-context-menu.converted-to-attachments", { count: converted }));
|
||||
} else if (command === "copyNotePathToClipboard") {
|
||||
navigator.clipboard.writeText("#" + notePath);
|
||||
} else if (command) {
|
||||
this.treeWidget.triggerCommand<TreeCommandNames>(command, {
|
||||
node: this.node,
|
||||
notePath: notePath,
|
||||
noteId: this.node.data.noteId,
|
||||
selectedOrActiveBranchIds: this.treeWidget.getSelectedOrActiveBranchIds(this.node),
|
||||
selectedOrActiveNoteIds: this.treeWidget.getSelectedOrActiveNoteIds(this.node)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import NoteContext from "../../components/note_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
import contextMenu from "../../menus/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 froca from "../../services/froca";
|
||||
import hoisted_note from "../../services/hoisted_note";
|
||||
@ -154,14 +155,31 @@ function BreadcrumbItem({ index, notePath, noteContext, notePathLength }: { inde
|
||||
return <NoteLink
|
||||
notePath={notePath}
|
||||
noContextMenu
|
||||
onContextMenu={(e) => {
|
||||
onContextMenu={async (e) => {
|
||||
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({
|
||||
items: [
|
||||
{
|
||||
title: "Foo"
|
||||
}
|
||||
],
|
||||
items,
|
||||
x: e.pageX,
|
||||
y: e.pageY
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user