mirror of
https://github.com/zadam/trilium.git
synced 2025-12-21 14:54: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,9 +55,93 @@ 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 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 isNotRoot = note?.noteId !== "root";
|
||||||
const isHoisted = note?.noteId === appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
const isHoisted = note?.noteId === appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||||
const parentNote = isNotRoot && branch ? await froca.getNote(branch.parentNoteId) : null;
|
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,
|
// 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
|
// 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.
|
// 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);
|
if (note && !selectedNotes.includes(note)) selectedNotes.push(note);
|
||||||
const isArchived = selectedNotes.every(note => note.isArchived);
|
const isArchived = selectedNotes.every(note => note.isArchived);
|
||||||
const canToggleArchived = !selectedNotes.some(note => note.isArchived !== 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 notSearch = note?.type !== "search";
|
||||||
const notOptionsOrHelp = !note?.noteId.startsWith("_options") && !note?.noteId.startsWith("_help");
|
const notOptionsOrHelp = !note?.noteId.startsWith("_options") && !note?.noteId.startsWith("_help");
|
||||||
const parentNotSearch = !parentNote || parentNote.type !== "search";
|
const parentNotSearch = !parentNote || parentNote.type !== "search";
|
||||||
@ -274,73 +356,4 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
return items.filter((row) => row !== null) as MenuItem<TreeCommandNames>[];
|
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 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