refactorted shortcut handling into a service

This commit is contained in:
zadam 2022-12-01 00:17:15 +01:00
parent 0985314fb7
commit 720fb0f73e
12 changed files with 72 additions and 62 deletions

View File

@ -13,6 +13,7 @@ import appContext from "./app_context.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js"; import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import BasicWidget from "../widgets/basic_widget.js"; import BasicWidget from "../widgets/basic_widget.js";
import SpacedUpdate from "./spaced_update.js"; import SpacedUpdate from "./spaced_update.js";
import shortcutService from "./shortcuts.js";
/** /**
* This is the main frontend API interface for scripts. It's published in the local "api" object. * This is the main frontend API interface for scripts. It's published in the local "api" object.
@ -509,7 +510,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
* @param {string} keyboardShortcut - e.g. "ctrl+shift+a" * @param {string} keyboardShortcut - e.g. "ctrl+shift+a"
* @param {function} handler * @param {function} handler
*/ */
this.bindGlobalShortcut = utils.bindGlobalShortcut; this.bindGlobalShortcut = shortcutService.bindGlobalShortcut;
/** /**
* Trilium runs in backend and frontend process, when something is changed on the backend from script, * Trilium runs in backend and frontend process, when something is changed on the backend from script,

View File

@ -1,6 +1,6 @@
import server from "./server.js"; import server from "./server.js";
import utils from "./utils.js";
import appContext from "./app_context.js"; import appContext from "./app_context.js";
import shortcutService from "./shortcuts.js";
const keyboardActionRepo = {}; const keyboardActionRepo = {};
@ -31,7 +31,7 @@ async function setupActionsForElement(scope, $el, component) {
for (const action of actions) { for (const action of actions) {
for (const shortcut of action.effectiveShortcuts) { for (const shortcut of action.effectiveShortcuts) {
utils.bindElShortcut($el, shortcut, () => component.triggerCommand(action.actionName, {ntxId: appContext.tabManager.activeNtxId})); shortcutService.bindElShortcut($el, shortcut, () => component.triggerCommand(action.actionName, {ntxId: appContext.tabManager.activeNtxId}));
} }
} }
} }
@ -39,14 +39,14 @@ async function setupActionsForElement(scope, $el, component) {
getActionsForScope("window").then(actions => { getActionsForScope("window").then(actions => {
for (const action of actions) { for (const action of actions) {
for (const shortcut of action.effectiveShortcuts) { for (const shortcut of action.effectiveShortcuts) {
utils.bindGlobalShortcut(shortcut, () => appContext.triggerCommand(action.actionName, {ntxId: appContext.tabManager.activeNtxId})); shortcutService.bindGlobalShortcut(shortcut, () => appContext.triggerCommand(action.actionName, {ntxId: appContext.tabManager.activeNtxId}));
} }
} }
}); });
server.get('keyboard-shortcuts-for-notes').then(shortcutForNotes => { server.get('keyboard-shortcuts-for-notes').then(shortcutForNotes => {
for (const shortcut in shortcutForNotes) { for (const shortcut in shortcutForNotes) {
utils.bindGlobalShortcut(shortcut, async () => { shortcutService.bindGlobalShortcut(shortcut, async () => {
appContext.tabManager.getActiveContext().setNote(shortcutForNotes[shortcut]); appContext.tabManager.getActiveContext().setNote(shortcutForNotes[shortcut]);
}); });
} }
@ -64,7 +64,7 @@ function setElementActionHandler($el, actionName, handler) {
for (const shortcut of action.effectiveShortcuts) { for (const shortcut of action.effectiveShortcuts) {
if (shortcut) { if (shortcut) {
utils.bindElShortcut($el, shortcut, handler); shortcutService.bindElShortcut($el, shortcut, handler);
} }
} }
}); });

View File

@ -2,15 +2,16 @@
* Mac specific initialization * Mac specific initialization
*/ */
import utils from "./utils.js"; import utils from "./utils.js";
import shortcutService from "./shortcuts.js";
function init() { function init() {
if (utils.isElectron() && utils.isMac()) { if (utils.isElectron() && utils.isMac()) {
utils.bindGlobalShortcut('meta+c', () => exec("copy")); shortcutService.bindGlobalShortcut('meta+c', () => exec("copy"));
utils.bindGlobalShortcut('meta+v', () => exec('paste')); shortcutService.bindGlobalShortcut('meta+v', () => exec('paste'));
utils.bindGlobalShortcut('meta+x', () => exec('cut')); shortcutService.bindGlobalShortcut('meta+x', () => exec('cut'));
utils.bindGlobalShortcut('meta+a', () => exec('selectAll')); shortcutService.bindGlobalShortcut('meta+a', () => exec('selectAll'));
utils.bindGlobalShortcut('meta+z', () => exec('undo')); shortcutService.bindGlobalShortcut('meta+z', () => exec('undo'));
utils.bindGlobalShortcut('meta+y', () => exec('redo')); shortcutService.bindGlobalShortcut('meta+y', () => exec('redo'));
} }
} }

View File

@ -0,0 +1,36 @@
import utils from "./utils.js";
function bindGlobalShortcut(keyboardShortcut, handler) {
bindElShortcut($(document), keyboardShortcut, handler);
}
function bindElShortcut($el, keyboardShortcut, handler) {
if (utils.isDesktop()) {
keyboardShortcut = normalizeShortcut(keyboardShortcut);
$el.bind('keydown', keyboardShortcut, e => {
handler(e);
e.preventDefault();
e.stopPropagation();
});
}
}
/**
* Normalize to the form expected by the jquery.hotkeys.js
*/
function normalizeShortcut(shortcut) {
return shortcut
.toLowerCase()
.replace("enter", "return")
.replace("delete", "del")
.replace("ctrl+alt", "alt+ctrl")
.replace("meta+alt", "alt+meta"); // alt needs to be first;
}
export default {
bindGlobalShortcut,
bindElShortcut,
normalizeShortcut
}

View File

@ -132,35 +132,6 @@ function randomString(len) {
return text; return text;
} }
function bindGlobalShortcut(keyboardShortcut, handler) {
bindElShortcut($(document), keyboardShortcut, handler);
}
function bindElShortcut($el, keyboardShortcut, handler) {
if (isDesktop()) {
keyboardShortcut = normalizeShortcut(keyboardShortcut);
$el.bind('keydown', keyboardShortcut, e => {
handler(e);
e.preventDefault();
e.stopPropagation();
});
}
}
/**
* Normalize to the form expected by the jquery.hotkeys.js
*/
function normalizeShortcut(shortcut) {
return shortcut
.toLowerCase()
.replace("enter", "return")
.replace("delete", "del")
.replace("ctrl+alt", "alt+ctrl")
.replace("meta+alt", "alt+meta"); // alt needs to be first;
}
function isMobile() { function isMobile() {
return window.device === "mobile" return window.device === "mobile"
// window.device is not available in setup // window.device is not available in setup
@ -387,8 +358,6 @@ export default {
formatLabel, formatLabel,
toObject, toObject,
randomString, randomString,
bindGlobalShortcut,
bindElShortcut,
isMobile, isMobile,
isDesktop, isDesktop,
setCookie, setCookie,
@ -402,7 +371,6 @@ export default {
focusSavedElement, focusSavedElement,
isHtmlEmpty, isHtmlEmpty,
clearBrowserCache, clearBrowserCache,
normalizeShortcut,
copySelectionToClipboard, copySelectionToClipboard,
dynamicRequire, dynamicRequire,
timeLimit, timeLimit,

View File

@ -8,6 +8,7 @@ import promotedAttributeDefinitionParser from '../../services/promoted_attribute
import NoteContextAwareWidget from "../note_context_aware_widget.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js";
import SpacedUpdate from "../../services/spaced_update.js"; import SpacedUpdate from "../../services/spaced_update.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import shortcutService from "../../services/shortcuts.js";
const TPL = ` const TPL = `
<div class="attr-detail"> <div class="attr-detail">
@ -271,8 +272,8 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$widget = $(TPL); this.$widget = $(TPL);
utils.bindElShortcut(this.$widget, 'ctrl+return', () => this.saveAndClose()); shortcutService.bindElShortcut(this.$widget, 'ctrl+return', () => this.saveAndClose());
utils.bindElShortcut(this.$widget, 'esc', () => this.cancelAndClose()); shortcutService.bindElShortcut(this.$widget, 'esc', () => this.cancelAndClose());
this.$title = this.$widget.find('.attr-detail-title'); this.$title = this.$widget.find('.attr-detail-title');

View File

@ -2,6 +2,7 @@ import noteAutocompleteService from '../../services/note_autocomplete.js';
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import appContext from "../../services/app_context.js"; import appContext from "../../services/app_context.js";
import BasicWidget from "../basic_widget.js"; import BasicWidget from "../basic_widget.js";
import shortcutService from "../../services/shortcuts.js";
const TPL = `<div class="jump-to-note-dialog modal mx-auto" tabindex="-1" role="dialog"> const TPL = `<div class="jump-to-note-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document"> <div class="modal-dialog modal-lg" role="document">
@ -42,7 +43,7 @@ export default class JumpToNoteDialog extends BasicWidget {
this.$showInFullTextButton = this.$widget.find(".show-in-full-text-button"); this.$showInFullTextButton = this.$widget.find(".show-in-full-text-button");
this.$showInFullTextButton.on('click', e => this.showInFullText(e)); this.$showInFullTextButton.on('click', e => this.showInFullText(e));
utils.bindElShortcut(this.$widget, 'ctrl+return', e => this.showInFullText(e)); shortcutService.bindElShortcut(this.$widget, 'ctrl+return', e => this.showInFullText(e));
} }
async jumpToNoteEvent() { async jumpToNoteEvent() {

View File

@ -3,6 +3,7 @@ import toastService from "../../services/toast.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import appContext from "../../services/app_context.js"; import appContext from "../../services/app_context.js";
import BasicWidget from "../basic_widget.js"; import BasicWidget from "../basic_widget.js";
import shortcutService from "../../services/shortcuts.js";
const TPL = ` const TPL = `
<div class="markdown-import-dialog modal fade mx-auto" tabindex="-1" role="dialog"> <div class="markdown-import-dialog modal fade mx-auto" tabindex="-1" role="dialog">
@ -42,7 +43,7 @@ export default class MarkdownImportDialog extends BasicWidget {
this.$widget.on('shown.bs.modal', () => this.$importTextarea.trigger('focus')); this.$widget.on('shown.bs.modal', () => this.$importTextarea.trigger('focus'));
utils.bindElShortcut(this.$widget, 'ctrl+return', () => this.sendForm()); shortcutService.bindElShortcut(this.$widget, 'ctrl+return', () => this.sendForm());
} }
async convertMarkdownToHtml(text) { async convertMarkdownToHtml(text) {

View File

@ -1,10 +1,10 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js";
import utils from "../services/utils.js";
import protectedSessionHolder from "../services/protected_session_holder.js"; import protectedSessionHolder from "../services/protected_session_holder.js";
import server from "../services/server.js"; import server from "../services/server.js";
import SpacedUpdate from "../services/spaced_update.js"; import SpacedUpdate from "../services/spaced_update.js";
import appContext from "../services/app_context.js"; import appContext from "../services/app_context.js";
import branchService from "../services/branches.js"; import branchService from "../services/branches.js";
import shortcutService from "../services/shortcuts.js";
const TPL = ` const TPL = `
<div class="note-title-widget"> <div class="note-title-widget">
@ -58,13 +58,13 @@ export default class NoteTitleWidget extends NoteContextAwareWidget {
this.deleteNoteOnEscape = false; this.deleteNoteOnEscape = false;
}); });
utils.bindElShortcut(this.$noteTitle, 'esc', () => { shortcutService.bindElShortcut(this.$noteTitle, 'esc', () => {
if (this.deleteNoteOnEscape && this.noteContext.isActive()) { if (this.deleteNoteOnEscape && this.noteContext.isActive()) {
branchService.deleteNotes(Object.values(this.noteContext.note.parentToBranch)); branchService.deleteNotes(Object.values(this.noteContext.note.parentToBranch));
} }
}); });
utils.bindElShortcut(this.$noteTitle, 'return', () => { shortcutService.bindElShortcut(this.$noteTitle, 'return', () => {
this.triggerCommand('focusOnDetail', {ntxId: this.noteContext.ntxId}); this.triggerCommand('focusOnDetail', {ntxId: this.noteContext.ntxId});
}); });
} }

View File

@ -18,6 +18,7 @@ import syncService from "../services/sync.js";
import options from "../services/options.js"; import options from "../services/options.js";
import protectedSessionHolder from "../services/protected_session_holder.js"; import protectedSessionHolder from "../services/protected_session_holder.js";
import dialogService from "../services/dialog.js"; import dialogService from "../services/dialog.js";
import shortcutService from "../services/shortcuts.js";
const TPL = ` const TPL = `
<div class="tree-wrapper"> <div class="tree-wrapper">
@ -1331,7 +1332,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
for (const action of actions) { for (const action of actions) {
for (const shortcut of action.effectiveShortcuts) { for (const shortcut of action.effectiveShortcuts) {
hotKeyMap[utils.normalizeShortcut(shortcut)] = node => { hotKeyMap[shortcutService.normalizeShortcut(shortcut)] = node => {
const notePath = treeService.getNotePath(node); const notePath = treeService.getNotePath(node);
this.triggerCommand(action.actionName, {node, notePath}); this.triggerCommand(action.actionName, {node, notePath});

View File

@ -1,10 +1,10 @@
import BasicWidget from "./basic_widget.js"; import BasicWidget from "./basic_widget.js";
import server from "../services/server.js"; import server from "../services/server.js";
import linkService from "../services/link.js"; import linkService from "../services/link.js";
import dateNotesService from "../services/date_notes.js";
import froca from "../services/froca.js"; import froca from "../services/froca.js";
import utils from "../services/utils.js"; import utils from "../services/utils.js";
import appContext from "../services/app_context.js"; import appContext from "../services/app_context.js";
import shortcutService from "../services/shortcuts.js";
const TPL = ` const TPL = `
<div class="quick-search input-group input-group-sm"> <div class="quick-search input-group input-group-sm">
@ -66,7 +66,7 @@ export default class QuickSearchWidget extends BasicWidget {
}) })
} }
utils.bindElShortcut(this.$searchString, 'return', () => { shortcutService.bindElShortcut(this.$searchString, 'return', () => {
if (this.$dropdownMenu.is(":visible")) { if (this.$dropdownMenu.is(":visible")) {
this.search(); // just update already visible dropdown this.search(); // just update already visible dropdown
} else { } else {
@ -76,11 +76,11 @@ export default class QuickSearchWidget extends BasicWidget {
this.$searchString.focus(); this.$searchString.focus();
}); });
utils.bindElShortcut(this.$searchString, 'down', () => { shortcutService.bindElShortcut(this.$searchString, 'down', () => {
this.$dropdownMenu.find('.dropdown-item:first').focus(); this.$dropdownMenu.find('.dropdown-item:first').focus();
}); });
utils.bindElShortcut(this.$searchString, 'esc', () => { shortcutService.bindElShortcut(this.$searchString, 'esc', () => {
this.$dropdownToggle.dropdown('hide'); this.$dropdownToggle.dropdown('hide');
}); });
@ -120,7 +120,7 @@ export default class QuickSearchWidget extends BasicWidget {
appContext.tabManager.getActiveContext().setNote(note.noteId); appContext.tabManager.getActiveContext().setNote(note.noteId);
} }
}); });
utils.bindElShortcut($link, 'return', () => { shortcutService.bindElShortcut($link, 'return', () => {
this.$dropdownToggle.dropdown("hide"); this.$dropdownToggle.dropdown("hide");
appContext.tabManager.getActiveContext().setNote(note.noteId); appContext.tabManager.getActiveContext().setNote(note.noteId);
@ -140,9 +140,9 @@ export default class QuickSearchWidget extends BasicWidget {
$showInFullButton.on('click', () => this.showInFullSearch()); $showInFullButton.on('click', () => this.showInFullSearch());
utils.bindElShortcut($showInFullButton, 'return', () => this.showInFullSearch()); shortcutService.bindElShortcut($showInFullButton, 'return', () => this.showInFullSearch());
utils.bindElShortcut(this.$dropdownMenu.find('.dropdown-item:first'), 'up', () => this.$searchString.focus()); shortcutService.bindElShortcut(this.$dropdownMenu.find('.dropdown-item:first'), 'up', () => this.$searchString.focus());
this.$dropdownToggle.dropdown('update'); this.$dropdownToggle.dropdown('update');
} }

View File

@ -1,7 +1,7 @@
import AbstractSearchOption from "./abstract_search_option.js"; import AbstractSearchOption from "./abstract_search_option.js";
import utils from "../../services/utils.js";
import SpacedUpdate from "../../services/spaced_update.js"; import SpacedUpdate from "../../services/spaced_update.js";
import server from "../../services/server.js"; import server from "../../services/server.js";
import shortcutService from "../../services/shortcuts.js";
const TPL = ` const TPL = `
<tr> <tr>
@ -45,7 +45,7 @@ export default class SearchString extends AbstractSearchOption {
this.$searchString = $option.find('.search-string'); this.$searchString = $option.find('.search-string');
this.$searchString.on('input', () => this.spacedUpdate.scheduleUpdate()); this.$searchString.on('input', () => this.spacedUpdate.scheduleUpdate());
utils.bindElShortcut(this.$searchString, 'return', async () => { shortcutService.bindElShortcut(this.$searchString, 'return', async () => {
// this also in effect disallows new lines in query string. // this also in effect disallows new lines in query string.
// on one hand this makes sense since search string is a label // on one hand this makes sense since search string is a label
// on the other hand it could be nice for structuring long search string. It's probably a niche case though. // on the other hand it could be nice for structuring long search string. It's probably a niche case though.