mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 11:26:15 +01:00
refactor(hotkeys): use own (rough) implementation
This commit is contained in:
@@ -13,7 +13,6 @@ import type ElectronRemote from "@electron/remote";
|
|||||||
import type Electron from "electron";
|
import type Electron from "electron";
|
||||||
import "./stylesheets/bootstrap.scss";
|
import "./stylesheets/bootstrap.scss";
|
||||||
import "boxicons/css/boxicons.min.css";
|
import "boxicons/css/boxicons.min.css";
|
||||||
import "jquery-hotkeys";
|
|
||||||
import "autocomplete.js/index_jquery.js";
|
import "autocomplete.js/index_jquery.js";
|
||||||
|
|
||||||
await appContext.earlyInit();
|
await appContext.earlyInit();
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
|
|
||||||
type ElementType = HTMLElement | Document;
|
type ElementType = HTMLElement | Document;
|
||||||
type Handler = (e: JQuery.TriggeredEvent<ElementType | Element, string, ElementType | Element, ElementType | Element>) => void;
|
type Handler = () => void;
|
||||||
|
|
||||||
|
interface ShortcutBinding {
|
||||||
|
element: HTMLElement | Document;
|
||||||
|
shortcut: string;
|
||||||
|
handler: Handler;
|
||||||
|
namespace: string | null;
|
||||||
|
listener: (evt: Event) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store all active shortcut bindings for management
|
||||||
|
const activeBindings: Map<string, ShortcutBinding[]> = new Map();
|
||||||
|
|
||||||
function removeGlobalShortcut(namespace: string) {
|
function removeGlobalShortcut(namespace: string) {
|
||||||
bindGlobalShortcut("", null, namespace);
|
bindGlobalShortcut("", null, namespace);
|
||||||
@@ -15,38 +26,141 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
|
|||||||
if (utils.isDesktop()) {
|
if (utils.isDesktop()) {
|
||||||
keyboardShortcut = normalizeShortcut(keyboardShortcut);
|
keyboardShortcut = normalizeShortcut(keyboardShortcut);
|
||||||
|
|
||||||
let eventName = "keydown";
|
// If namespace is provided, remove all previous bindings for this namespace
|
||||||
|
|
||||||
if (namespace) {
|
if (namespace) {
|
||||||
eventName += `.${namespace}`;
|
removeNamespaceBindings(namespace);
|
||||||
|
|
||||||
// if there's a namespace, then we replace the existing event handler with the new one
|
|
||||||
$el.off(eventName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// method can be called to remove the shortcut (e.g. when keyboardShortcut label is deleted)
|
// Method can be called to remove the shortcut (e.g. when keyboardShortcut label is deleted)
|
||||||
if (keyboardShortcut) {
|
if (keyboardShortcut && handler) {
|
||||||
$el.bind(eventName, keyboardShortcut, (e) => {
|
const element = $el.length > 0 ? $el[0] as (HTMLElement | Document) : document;
|
||||||
if (handler) {
|
|
||||||
handler(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
const listener = (evt: Event) => {
|
||||||
e.stopPropagation();
|
const e = evt as KeyboardEvent;
|
||||||
});
|
if (matchesShortcut(e, keyboardShortcut)) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the event listener
|
||||||
|
element.addEventListener('keydown', listener);
|
||||||
|
|
||||||
|
// Store the binding for later cleanup
|
||||||
|
const binding: ShortcutBinding = {
|
||||||
|
element,
|
||||||
|
shortcut: keyboardShortcut,
|
||||||
|
handler,
|
||||||
|
namespace,
|
||||||
|
listener
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = namespace || 'global';
|
||||||
|
if (!activeBindings.has(key)) {
|
||||||
|
activeBindings.set(key, []);
|
||||||
|
}
|
||||||
|
activeBindings.get(key)!.push(binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeNamespaceBindings(namespace: string) {
|
||||||
|
const bindings = activeBindings.get(namespace);
|
||||||
|
if (bindings) {
|
||||||
|
// Remove all event listeners for this namespace
|
||||||
|
bindings.forEach(binding => {
|
||||||
|
binding.element.removeEventListener('keydown', binding.listener);
|
||||||
|
});
|
||||||
|
activeBindings.delete(namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesShortcut(e: KeyboardEvent, shortcut: string): boolean {
|
||||||
|
if (!shortcut) return false;
|
||||||
|
|
||||||
|
const parts = shortcut.toLowerCase().split('+');
|
||||||
|
const key = parts[parts.length - 1]; // Last part is the actual key
|
||||||
|
const modifiers = parts.slice(0, -1); // Everything before is modifiers
|
||||||
|
|
||||||
|
// Check if the main key matches
|
||||||
|
if (!keyMatches(e, key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check modifiers
|
||||||
|
const expectedCtrl = modifiers.includes('ctrl') || modifiers.includes('control');
|
||||||
|
const expectedAlt = modifiers.includes('alt');
|
||||||
|
const expectedShift = modifiers.includes('shift');
|
||||||
|
const expectedMeta = modifiers.includes('meta') || modifiers.includes('cmd') || modifiers.includes('command');
|
||||||
|
|
||||||
|
return e.ctrlKey === expectedCtrl &&
|
||||||
|
e.altKey === expectedAlt &&
|
||||||
|
e.shiftKey === expectedShift &&
|
||||||
|
e.metaKey === expectedMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyMatches(e: KeyboardEvent, key: string): boolean {
|
||||||
|
// Handle special key mappings
|
||||||
|
const keyMap: { [key: string]: string[] } = {
|
||||||
|
'return': ['Enter'],
|
||||||
|
'del': ['Delete'],
|
||||||
|
'esc': ['Escape'],
|
||||||
|
'space': [' ', 'Space'],
|
||||||
|
'tab': ['Tab'],
|
||||||
|
'backspace': ['Backspace'],
|
||||||
|
'home': ['Home'],
|
||||||
|
'end': ['End'],
|
||||||
|
'pageup': ['PageUp'],
|
||||||
|
'pagedown': ['PageDown'],
|
||||||
|
'up': ['ArrowUp'],
|
||||||
|
'down': ['ArrowDown'],
|
||||||
|
'left': ['ArrowLeft'],
|
||||||
|
'right': ['ArrowRight']
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function keys
|
||||||
|
for (let i = 1; i <= 19; i++) {
|
||||||
|
keyMap[`f${i}`] = [`F${i}`];
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedKeys = keyMap[key.toLowerCase()];
|
||||||
|
if (mappedKeys) {
|
||||||
|
return mappedKeys.includes(e.key) || mappedKeys.includes(e.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For regular keys, check both key and code
|
||||||
|
return e.key.toLowerCase() === key.toLowerCase() ||
|
||||||
|
e.code.toLowerCase() === key.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize to the form expected by the jquery.hotkeys.js
|
* Normalize to a consistent format for our custom shortcut parser
|
||||||
*/
|
*/
|
||||||
function normalizeShortcut(shortcut: string): string {
|
function normalizeShortcut(shortcut: string): string {
|
||||||
if (!shortcut) {
|
if (!shortcut) {
|
||||||
return shortcut;
|
return 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;
|
return shortcut
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, '') // Remove any spaces
|
||||||
|
.replace("enter", "return")
|
||||||
|
.replace("delete", "del")
|
||||||
|
.replace("escape", "esc")
|
||||||
|
// Normalize modifier order: ctrl, alt, shift, meta, then key
|
||||||
|
.split('+')
|
||||||
|
.sort((a, b) => {
|
||||||
|
const order = ['ctrl', 'control', 'alt', 'shift', 'meta', 'cmd', 'command'];
|
||||||
|
const aIndex = order.indexOf(a);
|
||||||
|
const bIndex = order.indexOf(b);
|
||||||
|
if (aIndex === -1 && bIndex === -1) return 0;
|
||||||
|
if (aIndex === -1) return 1;
|
||||||
|
if (bIndex === -1) return -1;
|
||||||
|
return aIndex - bIndex;
|
||||||
|
})
|
||||||
|
.join('+');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import "jquery";
|
import "jquery";
|
||||||
import "jquery-hotkeys";
|
|
||||||
import utils from "./services/utils.js";
|
import utils from "./services/utils.js";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import "./stylesheets/bootstrap.scss";
|
import "./stylesheets/bootstrap.scss";
|
||||||
|
|||||||
Reference in New Issue
Block a user