mirror of
https://github.com/zadam/trilium.git
synced 2025-12-21 23:59:59 +01:00
chore(react/type_widget): hot-pluggable keyboard shortcuts
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import appContext, { type CommandNames } from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import shortcutService from "./shortcuts.js";
|
import shortcutService, { ShortcutBinding } from "./shortcuts.js";
|
||||||
import type Component from "../components/component.js";
|
import type Component from "../components/component.js";
|
||||||
import type { ActionKeyboardShortcut } from "@triliumnext/commons";
|
import type { ActionKeyboardShortcut } from "@triliumnext/commons";
|
||||||
|
|
||||||
@@ -30,12 +30,18 @@ async function getActionsForScope(scope: string) {
|
|||||||
|
|
||||||
async function setupActionsForElement(scope: string, $el: JQuery<HTMLElement>, component: Component) {
|
async function setupActionsForElement(scope: string, $el: JQuery<HTMLElement>, component: Component) {
|
||||||
const actions = await getActionsForScope(scope);
|
const actions = await getActionsForScope(scope);
|
||||||
|
const bindings: ShortcutBinding[] = [];
|
||||||
|
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
for (const shortcut of action.effectiveShortcuts ?? []) {
|
for (const shortcut of action.effectiveShortcuts ?? []) {
|
||||||
shortcutService.bindElShortcut($el, shortcut, () => component.triggerCommand(action.actionName, { ntxId: appContext.tabManager.activeNtxId }));
|
const binding = shortcutService.bindElShortcut($el, shortcut, () => component.triggerCommand(action.actionName, { ntxId: appContext.tabManager.activeNtxId }));
|
||||||
|
if (binding) {
|
||||||
|
bindings.push(binding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return bindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
getActionsForScope("window").then((actions) => {
|
getActionsForScope("window").then((actions) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import utils from "./utils.js";
|
|||||||
type ElementType = HTMLElement | Document;
|
type ElementType = HTMLElement | Document;
|
||||||
type Handler = (e: KeyboardEvent) => void;
|
type Handler = (e: KeyboardEvent) => void;
|
||||||
|
|
||||||
interface ShortcutBinding {
|
export interface ShortcutBinding {
|
||||||
element: HTMLElement | Document;
|
element: HTMLElement | Document;
|
||||||
shortcut: string;
|
shortcut: string;
|
||||||
handler: Handler;
|
handler: Handler;
|
||||||
@@ -51,7 +51,7 @@ export function isIMEComposing(e: KeyboardEvent): boolean {
|
|||||||
if (!e) {
|
if (!e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard check for composition state
|
// Standard check for composition state
|
||||||
// e.isComposing is true when IME is actively composing
|
// e.isComposing is true when IME is actively composing
|
||||||
// e.keyCode === 229 is a fallback for older browsers where 229 indicates IME processing
|
// e.keyCode === 229 is a fallback for older browsers where 229 indicates IME processing
|
||||||
@@ -86,13 +86,13 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
|
|||||||
}
|
}
|
||||||
|
|
||||||
const e = evt as KeyboardEvent;
|
const e = evt as KeyboardEvent;
|
||||||
|
|
||||||
// Skip processing if IME is composing to prevent shortcuts from
|
// Skip processing if IME is composing to prevent shortcuts from
|
||||||
// interfering with text input in CJK languages
|
// interfering with text input in CJK languages
|
||||||
if (isIMEComposing(e)) {
|
if (isIMEComposing(e)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchesShortcut(e, keyboardShortcut)) {
|
if (matchesShortcut(e, keyboardShortcut)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -117,10 +117,20 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
|
|||||||
activeBindings.set(key, []);
|
activeBindings.set(key, []);
|
||||||
}
|
}
|
||||||
activeBindings.get(key)!.push(binding);
|
activeBindings.get(key)!.push(binding);
|
||||||
|
return binding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeIndividualBinding(binding: ShortcutBinding) {
|
||||||
|
const key = binding.namespace ?? "global";
|
||||||
|
const activeBindingsInNamespace = activeBindings.get(key);
|
||||||
|
if (activeBindingsInNamespace) {
|
||||||
|
activeBindings.set(key, activeBindingsInNamespace.filter(aBinding => aBinding.handler === binding.handler));
|
||||||
|
}
|
||||||
|
binding.element.removeEventListener("keydown", binding.listener);
|
||||||
|
}
|
||||||
|
|
||||||
function removeNamespaceBindings(namespace: string) {
|
function removeNamespaceBindings(namespace: string) {
|
||||||
const bindings = activeBindings.get(namespace);
|
const bindings = activeBindings.get(namespace);
|
||||||
if (bindings) {
|
if (bindings) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inputs, MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
import { MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||||
import { CommandListenerData, EventData, EventNames } from "../../components/app_context";
|
import { EventData, EventNames } from "../../components/app_context";
|
||||||
import { ParentComponent } from "./react_utils";
|
import { ParentComponent, refToJQuerySelector } from "./react_utils";
|
||||||
import SpacedUpdate from "../../services/spaced_update";
|
import SpacedUpdate from "../../services/spaced_update";
|
||||||
import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons";
|
import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons";
|
||||||
import options, { type OptionValue } from "../../services/options";
|
import options, { type OptionValue } from "../../services/options";
|
||||||
@@ -21,6 +21,7 @@ import Component from "../../components/component";
|
|||||||
import toast, { ToastOptions } from "../../services/toast";
|
import toast, { ToastOptions } from "../../services/toast";
|
||||||
import protected_session_holder from "../../services/protected_session_holder";
|
import protected_session_holder from "../../services/protected_session_holder";
|
||||||
import server from "../../services/server";
|
import server from "../../services/server";
|
||||||
|
import { removeIndividualBinding } from "../../services/shortcuts";
|
||||||
|
|
||||||
export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) {
|
export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) {
|
||||||
const parentComponent = useContext(ParentComponent);
|
const parentComponent = useContext(ParentComponent);
|
||||||
@@ -721,3 +722,17 @@ export function useResizeObserver(ref: RefObject<HTMLElement>, callback: () => v
|
|||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, [ callback, ref ]);
|
}, [ callback, ref ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useKeyboardShortcuts(scope: "code-detail" | "text-detail", containerRef: RefObject<HTMLElement>, parentComponent: Component | undefined) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!parentComponent) return;
|
||||||
|
const $container = refToJQuerySelector(containerRef);
|
||||||
|
const bindingPromise = keyboard_actions.setupActionsForElement(scope, $container, parentComponent);
|
||||||
|
return async () => {
|
||||||
|
const bindings = await bindingPromise;
|
||||||
|
for (const binding of bindings) {
|
||||||
|
removeIndividualBinding(binding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import { TypeWidgetProps } from "../type_widget";
|
|||||||
import "./code.css";
|
import "./code.css";
|
||||||
import CodeMirror, { CodeMirrorProps } from "./CodeMirror";
|
import CodeMirror, { CodeMirrorProps } from "./CodeMirror";
|
||||||
import utils from "../../../services/utils";
|
import utils from "../../../services/utils";
|
||||||
import { useEditorSpacedUpdate, useNoteBlob, useSyncedRef, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
|
import { useEditorSpacedUpdate, useKeyboardShortcuts, useNoteBlob, useSyncedRef, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
|
||||||
import { t } from "../../../services/i18n";
|
import { t } from "../../../services/i18n";
|
||||||
import appContext from "../../../components/app_context";
|
import appContext from "../../../components/app_context";
|
||||||
import TouchBar, { TouchBarButton } from "../../react/TouchBar";
|
import TouchBar, { TouchBarButton } from "../../react/TouchBar";
|
||||||
import keyboard_actions from "../../../services/keyboard_actions";
|
|
||||||
import { refToJQuerySelector } from "../../react/react_utils";
|
import { refToJQuerySelector } from "../../react/react_utils";
|
||||||
import { CODE_THEME_DEFAULT_PREFIX as DEFAULT_PREFIX } from "../constants";
|
import { CODE_THEME_DEFAULT_PREFIX as DEFAULT_PREFIX } from "../constants";
|
||||||
|
|
||||||
@@ -69,11 +68,7 @@ export function EditableCode({ note, ntxId, debounceUpdate, parentComponent, upd
|
|||||||
updateInterval
|
updateInterval
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up keyboard shortcuts.
|
useKeyboardShortcuts("code-detail", containerRef, parentComponent);
|
||||||
useEffect(() => {
|
|
||||||
if (!parentComponent) return;
|
|
||||||
keyboard_actions.setupActionsForElement("code-detail", refToJQuerySelector(containerRef), parentComponent);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="note-detail-code note-detail-printable">
|
<div className="note-detail-code note-detail-printable">
|
||||||
|
|||||||
Reference in New Issue
Block a user