mirror of
https://github.com/TriliumNext/Notes.git
synced 2026-04-26 14:49:17 -05:00
replace note tree context menu with bootstrap dropdown, #203
This commit is contained in:
@@ -4,7 +4,6 @@ import linkService from './link.js';
|
||||
import messagingService from './messaging.js';
|
||||
import noteDetailService from './note_detail.js';
|
||||
import protectedSessionHolder from './protected_session_holder.js';
|
||||
import treeChangesService from './branches.js';
|
||||
import treeUtils from './tree_utils.js';
|
||||
import utils from './utils.js';
|
||||
import server from './server.js';
|
||||
@@ -16,6 +15,7 @@ import Branch from '../entities/branch.js';
|
||||
import NoteShort from '../entities/note_short.js';
|
||||
|
||||
const $tree = $("#tree");
|
||||
const $treeContextMenu = $("#tree-context-menu");
|
||||
const $createTopLevelNoteButton = $("#create-top-level-note-button");
|
||||
const $collapseTreeButton = $("#collapse-tree-button");
|
||||
const $scrollToCurrentNoteButton = $("#scroll-to-current-note-button");
|
||||
@@ -378,7 +378,48 @@ function initFancyTree(tree) {
|
||||
}
|
||||
});
|
||||
|
||||
$tree.contextmenu(treeContextMenuService.contextMenuOptions);
|
||||
$treeContextMenu.on('click', '.dropdown-item', function(e) {
|
||||
const cmd = $(e.target).prop("data-cmd");
|
||||
|
||||
treeContextMenuService.selectContextMenuItem(e, cmd);
|
||||
});
|
||||
|
||||
async function openContextMenu(e) {
|
||||
$treeContextMenu.empty();
|
||||
|
||||
const contextMenuItems = await treeContextMenuService.getContextMenuItems(e);
|
||||
|
||||
for (const item of contextMenuItems) {
|
||||
if (item.title === '----') {
|
||||
$treeContextMenu.append($("<div>").addClass("dropdown-divider"));
|
||||
} else {
|
||||
const $item = $("<a>")
|
||||
.addClass("dropdown-item")
|
||||
.prop("data-cmd", item.cmd)
|
||||
.append(item.title);
|
||||
|
||||
if (item.enabled !== undefined && !item.enabled) {
|
||||
$item.addClass("disabled");
|
||||
}
|
||||
|
||||
$treeContextMenu.append($item);
|
||||
}
|
||||
}
|
||||
|
||||
$treeContextMenu.css({
|
||||
display: "block",
|
||||
top: e.pageY - 10,
|
||||
left: e.pageX - 40
|
||||
}).addClass("show");
|
||||
}
|
||||
|
||||
$(document).click(() => $(".context-menu").hide());
|
||||
|
||||
$tree.on('contextmenu', '.fancytree-node', function(e) {
|
||||
openContextMenu(e);
|
||||
|
||||
return false; // blocks default browser right click menu
|
||||
});
|
||||
}
|
||||
|
||||
function getTree() {
|
||||
|
||||
@@ -76,135 +76,137 @@ function cut(nodes) {
|
||||
infoService.showMessage("Note(s) have been cut into clipboard.");
|
||||
}
|
||||
|
||||
const contextMenuOptions = {
|
||||
delegate: "span.fancytree-title",
|
||||
autoFocus: true,
|
||||
menu: [
|
||||
{title: "Insert note here <kbd>Ctrl+O</kbd>", cmd: "insertNoteHere", uiIcon: "ui-icon-plus"},
|
||||
{title: "Insert child note <kbd>Ctrl+P</kbd>", cmd: "insertChildNote", uiIcon: "ui-icon-plus"},
|
||||
{title: "Delete", cmd: "delete", uiIcon: "ui-icon-trash"},
|
||||
{title: "----"},
|
||||
{title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "ui-icon-pencil"},
|
||||
{title: "----"},
|
||||
{title: "Protect subtree", cmd: "protectSubtree", uiIcon: "ui-icon-locked"},
|
||||
{title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "ui-icon-unlocked"},
|
||||
{title: "----"},
|
||||
{title: "Copy / clone <kbd>Ctrl+C</kbd>", cmd: "copy", uiIcon: "ui-icon-copy"},
|
||||
{title: "Cut <kbd>Ctrl+X</kbd>", cmd: "cut", uiIcon: "ui-icon-scissors"},
|
||||
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
|
||||
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
|
||||
{title: "----"},
|
||||
{title: "Export subtree", cmd: "exportSubtree", uiIcon: " ui-icon-arrowthick-1-ne", children: [
|
||||
{title: "Native Tar", cmd: "exportSubtreeToTar"},
|
||||
{title: "OPML", cmd: "exportSubtreeToOpml"},
|
||||
{title: "Markdown", cmd: "exportSubtreeToMarkdown"}
|
||||
]},
|
||||
{title: "Import into note (tar, opml, md, enex)", cmd: "importIntoNote", uiIcon: "ui-icon-arrowthick-1-sw"},
|
||||
{title: "----"},
|
||||
{title: "Collapse subtree <kbd>Alt+-</kbd>", cmd: "collapseSubtree", uiIcon: "ui-icon-minus"},
|
||||
{title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
|
||||
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
|
||||
],
|
||||
beforeOpen: async (event, ui) => {
|
||||
const node = $.ui.fancytree.getNode(ui.target);
|
||||
const branch = await treeCache.getBranch(node.data.branchId);
|
||||
const note = await treeCache.getNote(node.data.noteId);
|
||||
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
||||
const isNotRoot = note.noteId !== 'root';
|
||||
const contextMenuItems = [
|
||||
{title: "Insert note here <kbd>Ctrl+O</kbd>", cmd: "insertNoteHere", uiIcon: "ui-icon-plus"},
|
||||
{title: "Insert child note <kbd>Ctrl+P</kbd>", cmd: "insertChildNote", uiIcon: "ui-icon-plus"},
|
||||
{title: "Delete", cmd: "delete", uiIcon: "ui-icon-trash"},
|
||||
{title: "----"},
|
||||
{title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "ui-icon-pencil"},
|
||||
{title: "----"},
|
||||
{title: "Protect subtree", cmd: "protectSubtree", uiIcon: "ui-icon-locked"},
|
||||
{title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "ui-icon-unlocked"},
|
||||
{title: "----"},
|
||||
{title: "Copy / clone <kbd>Ctrl+C</kbd>", cmd: "copy", uiIcon: "ui-icon-copy"},
|
||||
{title: "Cut <kbd>Ctrl+X</kbd>", cmd: "cut", uiIcon: "ui-icon-scissors"},
|
||||
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
|
||||
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
|
||||
{title: "----"},
|
||||
{title: "Export subtree", cmd: "exportSubtree", uiIcon: " ui-icon-arrowthick-1-ne"},
|
||||
{title: "Import into note (tar, opml, md, enex)", cmd: "importIntoNote", uiIcon: "ui-icon-arrowthick-1-sw"},
|
||||
{title: "----"},
|
||||
{title: "Collapse subtree <kbd>Alt+-</kbd>", cmd: "collapseSubtree", uiIcon: "ui-icon-minus"},
|
||||
{title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
|
||||
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
|
||||
];
|
||||
|
||||
// Modify menu entries depending on node status
|
||||
$tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search');
|
||||
$tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search');
|
||||
$tree.contextmenu("enableEntry", "delete", isNotRoot && parentNote.type !== 'search');
|
||||
$tree.contextmenu("enableEntry", "copy", isNotRoot);
|
||||
$tree.contextmenu("enableEntry", "cut", isNotRoot);
|
||||
$tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search');
|
||||
$tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.type !== 'search');
|
||||
$tree.contextmenu("enableEntry", "importIntoNote", note.type !== 'search');
|
||||
$tree.contextmenu("enableEntry", "exportSubtree", note.type !== 'search');
|
||||
$tree.contextmenu("enableEntry", "editBranchPrefix", isNotRoot && parentNote.type !== 'search');
|
||||
|
||||
// Activate node on right-click
|
||||
node.setActive();
|
||||
|
||||
// right click resets selection to just this node
|
||||
// this is important when e.g. you right click on a note while having different note active
|
||||
// and then click on delete - obviously you want to delete only that one right-clicked
|
||||
node.setSelected(true);
|
||||
treeService.clearSelectedNodes();
|
||||
|
||||
// Disable tree keyboard handling
|
||||
ui.menu.prevKeyboard = node.tree.options.keyboard;
|
||||
node.tree.options.keyboard = false;
|
||||
},
|
||||
close: (event, ui) => {},
|
||||
select: (event, ui) => {
|
||||
const node = $.ui.fancytree.getNode(ui.target);
|
||||
|
||||
if (ui.cmd === "insertNoteHere") {
|
||||
const parentNoteId = node.data.parentNoteId;
|
||||
const isProtected = treeUtils.getParentProtectedStatus(node);
|
||||
|
||||
treeService.createNote(node, parentNoteId, 'after', isProtected);
|
||||
}
|
||||
else if (ui.cmd === "insertChildNote") {
|
||||
treeService.createNote(node, node.data.noteId, 'into');
|
||||
}
|
||||
else if (ui.cmd === "editBranchPrefix") {
|
||||
branchPrefixDialog.showDialog(node);
|
||||
}
|
||||
else if (ui.cmd === "protectSubtree") {
|
||||
protectedSessionService.protectSubtree(node.data.noteId, true);
|
||||
}
|
||||
else if (ui.cmd === "unprotectSubtree") {
|
||||
protectedSessionService.protectSubtree(node.data.noteId, false);
|
||||
}
|
||||
else if (ui.cmd === "copy") {
|
||||
copy(treeService.getSelectedNodes());
|
||||
}
|
||||
else if (ui.cmd === "cut") {
|
||||
cut(treeService.getSelectedNodes());
|
||||
}
|
||||
else if (ui.cmd === "pasteAfter") {
|
||||
pasteAfter(node);
|
||||
}
|
||||
else if (ui.cmd === "pasteInto") {
|
||||
pasteInto(node);
|
||||
}
|
||||
else if (ui.cmd === "delete") {
|
||||
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
|
||||
}
|
||||
else if (ui.cmd === "exportSubtreeToTar") {
|
||||
exportService.exportSubtree(node.data.branchId, 'tar');
|
||||
}
|
||||
else if (ui.cmd === "exportSubtreeToOpml") {
|
||||
exportService.exportSubtree(node.data.branchId, 'opml');
|
||||
}
|
||||
else if (ui.cmd === "exportSubtreeToMarkdown") {
|
||||
exportService.exportSubtree(node.data.branchId, 'markdown');
|
||||
}
|
||||
else if (ui.cmd === "importIntoNote") {
|
||||
exportService.importIntoNote(node.data.noteId);
|
||||
}
|
||||
else if (ui.cmd === "collapseSubtree") {
|
||||
treeService.collapseTree(node);
|
||||
}
|
||||
else if (ui.cmd === "forceNoteSync") {
|
||||
syncService.forceNoteSync(node.data.noteId);
|
||||
}
|
||||
else if (ui.cmd === "sortAlphabetically") {
|
||||
treeService.sortAlphabetically(node.data.noteId);
|
||||
}
|
||||
else {
|
||||
messagingService.logError("Unknown command: " + ui.cmd);
|
||||
}
|
||||
function enableItem(cmd, enabled) {
|
||||
const item = contextMenuItems.find(item => item.cmd === cmd);
|
||||
|
||||
if (!item) {
|
||||
throw new Error(`Command ${cmd} has not been found!`);
|
||||
}
|
||||
};
|
||||
|
||||
item.enabled = enabled;
|
||||
}
|
||||
|
||||
async function getContextMenuItems(event) {
|
||||
const node = $.ui.fancytree.getNode(event);
|
||||
const branch = await treeCache.getBranch(node.data.branchId);
|
||||
const note = await treeCache.getNote(node.data.noteId);
|
||||
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
||||
const isNotRoot = note.noteId !== 'root';
|
||||
|
||||
// Modify menu entries depending on node status
|
||||
enableItem("insertNoteHere", isNotRoot && parentNote.type !== 'search');
|
||||
enableItem("insertChildNote", note.type !== 'search');
|
||||
enableItem("delete", isNotRoot && parentNote.type !== 'search');
|
||||
enableItem("copy", isNotRoot);
|
||||
enableItem("cut", isNotRoot);
|
||||
enableItem("pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search');
|
||||
enableItem("pasteInto", clipboardIds.length > 0 && note.type !== 'search');
|
||||
enableItem("importIntoNote", note.type !== 'search');
|
||||
enableItem("exportSubtree", note.type !== 'search');
|
||||
enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search');
|
||||
|
||||
// Activate node on right-click
|
||||
node.setActive();
|
||||
|
||||
// right click resets selection to just this node
|
||||
// this is important when e.g. you right click on a note while having different note active
|
||||
// and then click on delete - obviously you want to delete only that one right-clicked
|
||||
node.setSelected(true);
|
||||
treeService.clearSelectedNodes();
|
||||
|
||||
return contextMenuItems;
|
||||
}
|
||||
|
||||
function selectContextMenuItem(event, cmd) {
|
||||
const node = $.ui.fancytree.getNode(event);
|
||||
|
||||
if (cmd === "insertNoteHere") {
|
||||
const parentNoteId = node.data.parentNoteId;
|
||||
const isProtected = treeUtils.getParentProtectedStatus(node);
|
||||
|
||||
treeService.createNote(node, parentNoteId, 'after', isProtected);
|
||||
}
|
||||
else if (cmd === "insertChildNote") {
|
||||
treeService.createNote(node, node.data.noteId, 'into');
|
||||
}
|
||||
else if (cmd === "editBranchPrefix") {
|
||||
branchPrefixDialog.showDialog(node);
|
||||
}
|
||||
else if (cmd === "protectSubtree") {
|
||||
protectedSessionService.protectSubtree(node.data.noteId, true);
|
||||
}
|
||||
else if (cmd === "unprotectSubtree") {
|
||||
protectedSessionService.protectSubtree(node.data.noteId, false);
|
||||
}
|
||||
else if (cmd === "copy") {
|
||||
copy(treeService.getSelectedNodes());
|
||||
}
|
||||
else if (cmd === "cut") {
|
||||
cut(treeService.getSelectedNodes());
|
||||
}
|
||||
else if (cmd === "pasteAfter") {
|
||||
pasteAfter(node);
|
||||
}
|
||||
else if (cmd === "pasteInto") {
|
||||
pasteInto(node);
|
||||
}
|
||||
else if (cmd === "delete") {
|
||||
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
|
||||
}
|
||||
else if (cmd === "exportSubtreeToTar") {
|
||||
exportService.exportSubtree(node.data.branchId, 'tar');
|
||||
}
|
||||
else if (cmd === "exportSubtreeToOpml") {
|
||||
exportService.exportSubtree(node.data.branchId, 'opml');
|
||||
}
|
||||
else if (cmd === "exportSubtreeToMarkdown") {
|
||||
exportService.exportSubtree(node.data.branchId, 'markdown');
|
||||
}
|
||||
else if (cmd === "importIntoNote") {
|
||||
exportService.importIntoNote(node.data.noteId);
|
||||
}
|
||||
else if (cmd === "collapseSubtree") {
|
||||
treeService.collapseTree(node);
|
||||
}
|
||||
else if (cmd === "forceNoteSync") {
|
||||
syncService.forceNoteSync(node.data.noteId);
|
||||
}
|
||||
else if (cmd === "sortAlphabetically") {
|
||||
treeService.sortAlphabetically(node.data.noteId);
|
||||
}
|
||||
else {
|
||||
messagingService.logError("Unknown command: " + cmd);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
pasteAfter,
|
||||
pasteInto,
|
||||
cut,
|
||||
copy,
|
||||
contextMenuOptions
|
||||
getContextMenuItems,
|
||||
selectContextMenuItem
|
||||
};
|
||||
Reference in New Issue
Block a user