fix(shortcuts): gate feature-dependent bindings

Disable Anki-dependent shortcuts when AnkiConnect is off and require jellyfin.enabled for remote startup warmups to avoid initializing disabled integrations.
This commit is contained in:
2026-02-21 17:50:09 -08:00
parent c749430c77
commit 4ad8109508
9 changed files with 251 additions and 40 deletions

View File

@@ -0,0 +1,76 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { Config } from '../../types';
import { resolveConfiguredShortcuts } from './shortcut-config';
test('forces Anki-dependent shortcuts to null when AnkiConnect is explicitly disabled', () => {
const config: Config = {
ankiConnect: { enabled: false },
shortcuts: {
copySubtitle: 'Ctrl+KeyC',
updateLastCardFromClipboard: 'Ctrl+KeyU',
triggerFieldGrouping: 'Alt+KeyG',
mineSentence: 'Ctrl+Digit1',
mineSentenceMultiple: 'Ctrl+Digit2',
markAudioCard: 'Alt+KeyM',
},
};
const defaults: Config = {
shortcuts: {
updateLastCardFromClipboard: 'Alt+KeyL',
triggerFieldGrouping: 'Alt+KeyF',
mineSentence: 'KeyQ',
mineSentenceMultiple: 'KeyW',
markAudioCard: 'KeyE',
},
};
const resolved = resolveConfiguredShortcuts(config, defaults);
assert.equal(resolved.updateLastCardFromClipboard, null);
assert.equal(resolved.triggerFieldGrouping, null);
assert.equal(resolved.mineSentence, null);
assert.equal(resolved.mineSentenceMultiple, null);
assert.equal(resolved.markAudioCard, null);
assert.equal(resolved.copySubtitle, 'Ctrl+C');
});
test('keeps Anki-dependent shortcuts enabled and normalized when AnkiConnect is enabled', () => {
const config: Config = {
ankiConnect: { enabled: true },
shortcuts: {
updateLastCardFromClipboard: 'Ctrl+KeyU',
mineSentence: 'Ctrl+Digit1',
mineSentenceMultiple: 'Ctrl+Digit2',
},
};
const defaults: Config = {
shortcuts: {
triggerFieldGrouping: 'Alt+KeyG',
markAudioCard: 'Alt+KeyM',
},
};
const resolved = resolveConfiguredShortcuts(config, defaults);
assert.equal(resolved.updateLastCardFromClipboard, 'Ctrl+U');
assert.equal(resolved.triggerFieldGrouping, 'Alt+G');
assert.equal(resolved.mineSentence, 'Ctrl+1');
assert.equal(resolved.mineSentenceMultiple, 'Ctrl+2');
assert.equal(resolved.markAudioCard, 'Alt+M');
});
test('normalizes fallback shortcuts when AnkiConnect flag is unset', () => {
const config: Config = {};
const defaults: Config = {
shortcuts: {
mineSentence: 'KeyQ',
openRuntimeOptions: 'Digit9',
},
};
const resolved = resolveConfiguredShortcuts(config, defaults);
assert.equal(resolved.mineSentence, 'Q');
assert.equal(resolved.openRuntimeOptions, '9');
});

View File

@@ -21,6 +21,8 @@ export function resolveConfiguredShortcuts(
config: Config,
defaultConfig: Config,
): ConfiguredShortcuts {
const isAnkiConnectDisabled = config.ankiConnect?.enabled === false;
const normalizeShortcut = (value: string | null | undefined): string | null | undefined => {
if (typeof value !== 'string') return value;
return value.replace(/\bKey([A-Z])\b/g, '$1').replace(/\bDigit([0-9])\b/g, '$1');
@@ -42,20 +44,28 @@ export function resolveConfiguredShortcuts(
config.shortcuts?.copySubtitleMultiple ?? defaultConfig.shortcuts?.copySubtitleMultiple,
),
updateLastCardFromClipboard: normalizeShortcut(
config.shortcuts?.updateLastCardFromClipboard ??
defaultConfig.shortcuts?.updateLastCardFromClipboard,
isAnkiConnectDisabled
? null
: (config.shortcuts?.updateLastCardFromClipboard ??
defaultConfig.shortcuts?.updateLastCardFromClipboard),
),
triggerFieldGrouping: normalizeShortcut(
config.shortcuts?.triggerFieldGrouping ?? defaultConfig.shortcuts?.triggerFieldGrouping,
isAnkiConnectDisabled
? null
: (config.shortcuts?.triggerFieldGrouping ?? defaultConfig.shortcuts?.triggerFieldGrouping),
),
triggerSubsync: normalizeShortcut(
config.shortcuts?.triggerSubsync ?? defaultConfig.shortcuts?.triggerSubsync,
),
mineSentence: normalizeShortcut(
config.shortcuts?.mineSentence ?? defaultConfig.shortcuts?.mineSentence,
isAnkiConnectDisabled
? null
: (config.shortcuts?.mineSentence ?? defaultConfig.shortcuts?.mineSentence),
),
mineSentenceMultiple: normalizeShortcut(
config.shortcuts?.mineSentenceMultiple ?? defaultConfig.shortcuts?.mineSentenceMultiple,
isAnkiConnectDisabled
? null
: (config.shortcuts?.mineSentenceMultiple ?? defaultConfig.shortcuts?.mineSentenceMultiple),
),
multiCopyTimeoutMs:
config.shortcuts?.multiCopyTimeoutMs ?? defaultConfig.shortcuts?.multiCopyTimeoutMs ?? 5000,
@@ -63,7 +73,9 @@ export function resolveConfiguredShortcuts(
config.shortcuts?.toggleSecondarySub ?? defaultConfig.shortcuts?.toggleSecondarySub,
),
markAudioCard: normalizeShortcut(
config.shortcuts?.markAudioCard ?? defaultConfig.shortcuts?.markAudioCard,
isAnkiConnectDisabled
? null
: (config.shortcuts?.markAudioCard ?? defaultConfig.shortcuts?.markAudioCard),
),
openRuntimeOptions: normalizeShortcut(
config.shortcuts?.openRuntimeOptions ?? defaultConfig.shortcuts?.openRuntimeOptions,