mirror of
https://github.com/zadam/trilium.git
synced 2025-01-15 19:51:57 +08:00
tree keyboard shortcuts
This commit is contained in:
parent
0e5028acd3
commit
465c3b87a7
4 changed files with 258 additions and 171 deletions
|
@ -52,13 +52,7 @@ function setActionHandler(actionName, handler) {
|
|||
}
|
||||
|
||||
async function triggerAction(actionName) {
|
||||
await keyboardActionsLoaded;
|
||||
|
||||
const action = keyboardActionRepo[actionName];
|
||||
|
||||
if (!action) {
|
||||
throw new Error(`Cannot find action ${actionName}`);
|
||||
}
|
||||
const action = getAction(actionName);
|
||||
|
||||
if (!action.handler) {
|
||||
throw new Error(`Action ${actionName} has no handler`);
|
||||
|
@ -67,7 +61,20 @@ async function triggerAction(actionName) {
|
|||
await action.handler();
|
||||
}
|
||||
|
||||
async function getAction(actionName) {
|
||||
await keyboardActionsLoaded;
|
||||
|
||||
const action = keyboardActionRepo[actionName];
|
||||
|
||||
if (!action) {
|
||||
throw new Error(`Cannot find action ${actionName}`);
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export default {
|
||||
setActionHandler,
|
||||
triggerAction
|
||||
triggerAction,
|
||||
getAction
|
||||
};
|
|
@ -9,9 +9,7 @@ import server from './server.js';
|
|||
import treeCache from './tree_cache.js';
|
||||
import toastService from "./toast.js";
|
||||
import treeBuilder from "./tree_builder.js";
|
||||
import treeKeyBindings from "./tree_keybindings.js";
|
||||
import Branch from '../entities/branch.js';
|
||||
import NoteShort from '../entities/note_short.js';
|
||||
import treeKeyBindingService from "./tree_keybindings.js";
|
||||
import hoistedNoteService from '../services/hoisted_note.js';
|
||||
import optionsService from "../services/options.js";
|
||||
import TreeContextMenu from "./tree_context_menu.js";
|
||||
|
@ -430,7 +428,7 @@ async function treeInitialized() {
|
|||
setFrontendAsLoaded();
|
||||
}
|
||||
|
||||
function initFancyTree(tree) {
|
||||
async function initFancyTree(tree) {
|
||||
utils.assertArguments(tree);
|
||||
|
||||
$tree.fancytree({
|
||||
|
@ -473,7 +471,7 @@ function initFancyTree(tree) {
|
|||
collapse: (event, data) => setExpandedToServer(data.node.data.branchId, false),
|
||||
init: (event, data) => treeInitialized(), // don't collapse to short form
|
||||
hotkeys: {
|
||||
keydown: treeKeyBindings
|
||||
keydown: await treeKeyBindingService.getKeyboardBindings()
|
||||
},
|
||||
dnd5: dragAndDropSetup,
|
||||
lazyLoad: function(event, data) {
|
||||
|
@ -754,7 +752,7 @@ async function sortAlphabetically(noteId) {
|
|||
async function showTree() {
|
||||
const tree = await loadTree();
|
||||
|
||||
initFancyTree(tree);
|
||||
await initFancyTree(tree);
|
||||
}
|
||||
|
||||
ws.subscribeToMessages(message => {
|
||||
|
|
|
@ -5,148 +5,9 @@ import hoistedNoteService from "./hoisted_note.js";
|
|||
import clipboard from "./clipboard.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import searchNoteService from "./search_notes.js";
|
||||
import keyboardActionService from "./keyboard_actions.js";
|
||||
|
||||
const keyBindings = {
|
||||
"del": node => {
|
||||
treeChangesService.deleteNodes(treeService.getSelectedOrActiveNodes(node));
|
||||
},
|
||||
"ctrl+up": node => {
|
||||
const beforeNode = node.getPrevSibling();
|
||||
|
||||
if (beforeNode !== null) {
|
||||
treeChangesService.moveBeforeNode([node], beforeNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+down": node => {
|
||||
const afterNode = node.getNextSibling();
|
||||
if (afterNode !== null) {
|
||||
treeChangesService.moveAfterNode([node], afterNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+left": node => {
|
||||
treeChangesService.moveNodeUpInHierarchy(node);
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+right": node => {
|
||||
const toNode = node.getPrevSibling();
|
||||
|
||||
if (toNode !== null) {
|
||||
treeChangesService.moveToNode([node], toNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"shift+up": () => {
|
||||
const node = treeService.getFocusedNode();
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.isActive()) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
|
||||
node.navigate($.ui.keyCode.UP, false).then(() => {
|
||||
const currentNode = treeService.getFocusedNode();
|
||||
|
||||
if (currentNode.isSelected()) {
|
||||
node.setSelected(false);
|
||||
}
|
||||
|
||||
currentNode.setSelected(true);
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
"shift+down": () => {
|
||||
const node = treeService.getFocusedNode();
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.isActive()) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
|
||||
node.navigate($.ui.keyCode.DOWN, false).then(() => {
|
||||
const currentNode = treeService.getFocusedNode();
|
||||
|
||||
if (currentNode.isSelected()) {
|
||||
node.setSelected(false);
|
||||
}
|
||||
|
||||
currentNode.setSelected(true);
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
"f2": async node => {
|
||||
const editBranchPrefixDialog = await import("../dialogs/branch_prefix.js");
|
||||
editBranchPrefixDialog.showDialog(node);
|
||||
},
|
||||
"alt+-": node => {
|
||||
treeService.collapseTree(node);
|
||||
},
|
||||
"alt+s": node => {
|
||||
treeService.sortAlphabetically(node.data.noteId);
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+a": node => {
|
||||
for (const child of node.getParent().getChildren()) {
|
||||
child.setSelected(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+c": node => {
|
||||
clipboard.copy(treeService.getSelectedOrActiveNodes(node));
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+x": node => {
|
||||
clipboard.cut(treeService.getSelectedOrActiveNodes(node));
|
||||
|
||||
return false;
|
||||
},
|
||||
"ctrl+v": node => {
|
||||
clipboard.pasteInto(node);
|
||||
|
||||
return false;
|
||||
},
|
||||
"return": node => {
|
||||
noteDetailService.focusOnTitle();
|
||||
|
||||
return false;
|
||||
},
|
||||
"backspace": async node => {
|
||||
if (!await hoistedNoteService.isRootNode(node)) {
|
||||
node.getParent().setActive().then(treeService.clearSelectedNodes);
|
||||
}
|
||||
},
|
||||
"ctrl+h": node => {
|
||||
hoistedNoteService.getHoistedNoteId().then(async hoistedNoteId => {
|
||||
if (node.data.noteId === hoistedNoteId) {
|
||||
hoistedNoteService.unhoist();
|
||||
}
|
||||
else {
|
||||
const note = await treeCache.getNote(node.data.noteId);
|
||||
|
||||
if (note.type !== 'search') {
|
||||
hoistedNoteService.setHoistedNoteId(node.data.noteId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
const fixedKeyBindings = {
|
||||
// code below shouldn't be necessary normally, however there's some problem with interaction with context menu plugin
|
||||
// after opening context menu, standard shortcuts don't work, but they are detected here
|
||||
// so we essentially takeover the standard handling with our implementation.
|
||||
|
@ -169,12 +30,173 @@ const keyBindings = {
|
|||
node.navigate($.ui.keyCode.DOWN, true).then(treeService.clearSelectedNodes);
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const templates = {
|
||||
"DeleteNote": node => {
|
||||
treeChangesService.deleteNodes(treeService.getSelectedOrActiveNodes(node));
|
||||
},
|
||||
"ctrl+shift+s": node => {
|
||||
"MoveNoteUp": node => {
|
||||
const beforeNode = node.getPrevSibling();
|
||||
|
||||
if (beforeNode !== null) {
|
||||
treeChangesService.moveBeforeNode([node], beforeNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"MoveNoteDown": node => {
|
||||
const afterNode = node.getNextSibling();
|
||||
if (afterNode !== null) {
|
||||
treeChangesService.moveAfterNode([node], afterNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"MoveNoteUpInHierarchy": node => {
|
||||
treeChangesService.moveNodeUpInHierarchy(node);
|
||||
|
||||
return false;
|
||||
},
|
||||
"MoveNoteDownInHierarchy": node => {
|
||||
const toNode = node.getPrevSibling();
|
||||
|
||||
if (toNode !== null) {
|
||||
treeChangesService.moveToNode([node], toNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"AddNoteAboveToSelection": () => {
|
||||
const node = treeService.getFocusedNode();
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.isActive()) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
|
||||
node.navigate($.ui.keyCode.UP, false).then(() => {
|
||||
const currentNode = treeService.getFocusedNode();
|
||||
|
||||
if (currentNode.isSelected()) {
|
||||
node.setSelected(false);
|
||||
}
|
||||
|
||||
currentNode.setSelected(true);
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
"AddNoteBelowToSelection": () => {
|
||||
const node = treeService.getFocusedNode();
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.isActive()) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
|
||||
node.navigate($.ui.keyCode.DOWN, false).then(() => {
|
||||
const currentNode = treeService.getFocusedNode();
|
||||
|
||||
if (currentNode.isSelected()) {
|
||||
node.setSelected(false);
|
||||
}
|
||||
|
||||
currentNode.setSelected(true);
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
// TODO: this shouldn't be tree specific shortcut
|
||||
"EditBranchPrefix": async node => {
|
||||
const editBranchPrefixDialog = await import("../dialogs/branch_prefix.js");
|
||||
editBranchPrefixDialog.showDialog(node);
|
||||
},
|
||||
"CollapseSubtree": node => {
|
||||
treeService.collapseTree(node);
|
||||
},
|
||||
"SortChildNotes": node => {
|
||||
treeService.sortAlphabetically(node.data.noteId);
|
||||
|
||||
return false;
|
||||
},
|
||||
"SelectAllNotesInParent": node => {
|
||||
for (const child of node.getParent().getChildren()) {
|
||||
child.setSelected(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
"CopyNotesToClipboard": node => {
|
||||
clipboard.copy(treeService.getSelectedOrActiveNodes(node));
|
||||
|
||||
return false;
|
||||
},
|
||||
"CutNotesToClipboard": node => {
|
||||
clipboard.cut(treeService.getSelectedOrActiveNodes(node));
|
||||
|
||||
return false;
|
||||
},
|
||||
"PasteNotesFromClipboard": node => {
|
||||
clipboard.pasteInto(node);
|
||||
|
||||
return false;
|
||||
},
|
||||
"EditNoteTitle": node => {
|
||||
noteDetailService.focusOnTitle();
|
||||
|
||||
return false;
|
||||
},
|
||||
"ActivateParentNote": async node => {
|
||||
if (!await hoistedNoteService.isRootNode(node)) {
|
||||
node.getParent().setActive().then(treeService.clearSelectedNodes);
|
||||
}
|
||||
},
|
||||
// TODO: this should probably be app-global shortcut
|
||||
"ToggleNoteHoisting": node => {
|
||||
hoistedNoteService.getHoistedNoteId().then(async hoistedNoteId => {
|
||||
if (node.data.noteId === hoistedNoteId) {
|
||||
hoistedNoteService.unhoist();
|
||||
}
|
||||
else {
|
||||
const note = await treeCache.getNote(node.data.noteId);
|
||||
|
||||
if (note.type !== 'search') {
|
||||
hoistedNoteService.setHoistedNoteId(node.data.noteId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
"SearchInSubtree": node => {
|
||||
searchNoteService.searchInSubtree(node.data.noteId);
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default keyBindings;
|
||||
async function getKeyboardBindings() {
|
||||
const bindings = Object.assign({}, fixedKeyBindings);
|
||||
|
||||
for (const actionName in templates) {
|
||||
const action = await keyboardActionService.getAction(actionName);
|
||||
|
||||
for (const shortcut of action.effectiveShortcuts || []) {
|
||||
bindings[shortcut] = templates[actionName];
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
export default {
|
||||
getKeyboardBindings
|
||||
};
|
|
@ -114,19 +114,89 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
|||
defaultShortcuts: ["CommandOrControl+return"]
|
||||
},
|
||||
{
|
||||
actionName: "ClipboardCopy",
|
||||
actionName: "DeleteNote",
|
||||
defaultShortcuts: ["Delete"],
|
||||
description: "Delete note"
|
||||
},
|
||||
{
|
||||
actionName: "MoveNoteUp",
|
||||
defaultShortcuts: ["Ctrl+Up"],
|
||||
description: "Move note up"
|
||||
},
|
||||
{
|
||||
actionName: "MoveNoteDown",
|
||||
defaultShortcuts: ["Ctrl+Down"],
|
||||
description: "Move note down"
|
||||
},
|
||||
{
|
||||
actionName: "MoveNoteUpInHierarchy",
|
||||
defaultShortcuts: ["Ctrl+Left"],
|
||||
description: "Move note up in hierarchy"
|
||||
},
|
||||
{
|
||||
actionName: "MoveNoteDownInHierarchy",
|
||||
defaultShortcuts: ["Ctrl+Right"],
|
||||
description: "Move note down in hierarchy"
|
||||
},
|
||||
{
|
||||
actionName: "AddNoteAboveToSelection",
|
||||
defaultShortcuts: ["Shift+Up"],
|
||||
description: "Add note above to the selection"
|
||||
},
|
||||
{
|
||||
actionName: "AddNoteBelowToSelection",
|
||||
defaultShortcuts: ["Shift+Down"],
|
||||
description: "Add note above to the selection"
|
||||
},
|
||||
{
|
||||
actionName: "CopyNotesToClipboard",
|
||||
defaultShortcuts: ["CommandOrControl+C"],
|
||||
description: "Copy selected notes to the clipboard"
|
||||
},
|
||||
{
|
||||
actionName: "ClipboardPaste",
|
||||
actionName: "PasteNotesFromClipboard",
|
||||
defaultShortcuts: ["CommandOrControl+V"],
|
||||
description: "Paste notes from the clipboard into active note"
|
||||
},
|
||||
{
|
||||
actionName: "ClipboardCut",
|
||||
actionName: "CutNotesToClipboard",
|
||||
defaultShortcuts: ["CommandOrControl+X"],
|
||||
description: "Copy selected notes to the clipboard"
|
||||
description: "Cut selected notes to the clipboard"
|
||||
},
|
||||
{
|
||||
actionName: "EditBranchPrefix",
|
||||
defaultShortcuts: ["F2"],
|
||||
description: "Show Edit branch prefix dialog"
|
||||
},
|
||||
{
|
||||
actionName: "CollapseSubtree",
|
||||
defaultShortcuts: ["Alt+-"],
|
||||
description: "Collapses subtree of current note"
|
||||
},
|
||||
{
|
||||
actionName: "SortChildNotes",
|
||||
defaultShortcuts: ["Alt+s"],
|
||||
description: "Sort child notes"
|
||||
},
|
||||
{
|
||||
actionName: "ActivateParentNote",
|
||||
defaultShortcuts: ["Backspace"],
|
||||
description: "Activates parent note of currently active note"
|
||||
},
|
||||
{
|
||||
actionName: "ToggleNoteHoisting",
|
||||
defaultShortcuts: ["Alt+h"],
|
||||
description: "Toggles note hoisting of active note"
|
||||
},
|
||||
{
|
||||
actionName: "SearchInSubtree",
|
||||
defaultShortcuts: ["CommandOrControl+Shift+S"],
|
||||
description: "Search for notes in the active note's subtree"
|
||||
},
|
||||
{
|
||||
actionName: "EditNoteTitle",
|
||||
defaultShortcuts: ["return"],
|
||||
description: "Edit active note title"
|
||||
},
|
||||
{
|
||||
actionName: "SelectAllNotesInParent",
|
||||
|
@ -136,16 +206,6 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
|||
{
|
||||
separator: "Text note operations"
|
||||
},
|
||||
{
|
||||
actionName: "Undo",
|
||||
defaultShortcuts: ["CommandOrControl+Z"],
|
||||
description: "Undo last text operation (applicable on MacOS only)"
|
||||
},
|
||||
{
|
||||
actionName: "Redo",
|
||||
defaultShortcuts: ["CommandOrControl+Y"],
|
||||
description: "Undo last text operation (applicable on MacOS only)"
|
||||
},
|
||||
{
|
||||
actionName: "AddLinkToText",
|
||||
defaultShortcuts: ["CommandOrControl+L"],
|
||||
|
|
Loading…
Reference in a new issue