mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-30 12:15:26 -07:00
feat(anki): add runtime toggle for media timing review
- Add session-only runtime option with live config hot-reload support - Document the runtime palette toggle
This commit is contained in:
@@ -2181,6 +2181,7 @@ test('runtime options registry is centralized', () => {
|
||||
const ids = RUNTIME_OPTION_REGISTRY.map((entry) => entry.id);
|
||||
assert.deepEqual(ids, [
|
||||
'anki.autoUpdateNewCards',
|
||||
'anki.mediaReviewTiming',
|
||||
'subtitle.annotation.knownWords.highlightEnabled',
|
||||
'subtitle.annotation.knownWords.maturityEnabled',
|
||||
'subtitle.annotation.nPlusOne',
|
||||
|
||||
@@ -202,6 +202,7 @@ export function buildIntegrationConfigOptionRegistry(
|
||||
defaultValue: defaultConfig.ankiConnect.media.reviewTiming,
|
||||
description:
|
||||
'Review and preview subtitle media timing before SubMiner creates or enriches a mined card.',
|
||||
runtime: runtimeOptionById.get('anki.mediaReviewTiming'),
|
||||
},
|
||||
{
|
||||
path: 'ankiConnect.media.generateImage',
|
||||
|
||||
@@ -19,6 +19,20 @@ export function buildRuntimeOptionRegistry(
|
||||
behavior: { autoUpdateNewCards: value === true },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'anki.mediaReviewTiming',
|
||||
path: 'ankiConnect.media.reviewTiming',
|
||||
label: 'Review Media Timing',
|
||||
scope: 'ankiConnect',
|
||||
valueType: 'boolean',
|
||||
allowedValues: [true, false],
|
||||
defaultValue: defaultConfig.ankiConnect.media.reviewTiming,
|
||||
requiresRestart: false,
|
||||
formatValueForOsd: (value) => (value === true ? 'On' : 'Off'),
|
||||
toAnkiPatch: (value) => ({
|
||||
media: { reviewTiming: value === true },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'subtitle.annotation.knownWords.highlightEnabled',
|
||||
path: 'ankiConnect.knownWords.highlightEnabled',
|
||||
|
||||
@@ -352,6 +352,7 @@ test('settings registry marks safe live config paths as hot-reloadable', () => {
|
||||
'ankiConnect.deck',
|
||||
'ankiConnect.media.normalizeAudio',
|
||||
'ankiConnect.media.mirrorMpvVolume',
|
||||
'ankiConnect.media.reviewTiming',
|
||||
'ankiConnect.knownWords.highlightEnabled',
|
||||
'ankiConnect.knownWords.refreshMinutes',
|
||||
'ankiConnect.knownWords.addMinedWordsImmediately',
|
||||
|
||||
@@ -33,6 +33,7 @@ test('classifyConfigHotReloadDiff treats safe nested config paths as hot-reloada
|
||||
next.ankiConnect.deck = 'Mining';
|
||||
next.ankiConnect.media.normalizeAudio = !prev.ankiConnect.media.normalizeAudio;
|
||||
next.ankiConnect.media.mirrorMpvVolume = !prev.ankiConnect.media.mirrorMpvVolume;
|
||||
next.ankiConnect.media.reviewTiming = !prev.ankiConnect.media.reviewTiming;
|
||||
next.ankiConnect.behavior.autoUpdateNewCards = !prev.ankiConnect.behavior.autoUpdateNewCards;
|
||||
next.ankiConnect.knownWords.highlightEnabled = !prev.ankiConnect.knownWords.highlightEnabled;
|
||||
next.ankiConnect.knownWords.refreshMinutes = prev.ankiConnect.knownWords.refreshMinutes + 5;
|
||||
@@ -69,6 +70,7 @@ test('classifyConfigHotReloadDiff treats safe nested config paths as hot-reloada
|
||||
'ankiConnect.deck',
|
||||
'ankiConnect.media.normalizeAudio',
|
||||
'ankiConnect.media.mirrorMpvVolume',
|
||||
'ankiConnect.media.reviewTiming',
|
||||
'ankiConnect.behavior.autoUpdateNewCards',
|
||||
'ankiConnect.knownWords.highlightEnabled',
|
||||
'ankiConnect.knownWords.refreshMinutes',
|
||||
|
||||
@@ -70,6 +70,7 @@ const HOT_RELOAD_EXACT_OR_PREFIX_PATHS = [
|
||||
'ankiConnect.deck',
|
||||
'ankiConnect.media.normalizeAudio',
|
||||
'ankiConnect.media.mirrorMpvVolume',
|
||||
'ankiConnect.media.reviewTiming',
|
||||
'ankiConnect.behavior.autoUpdateNewCards',
|
||||
'ankiConnect.knownWords.highlightEnabled',
|
||||
'ankiConnect.knownWords.refreshMinutes',
|
||||
|
||||
@@ -156,6 +156,7 @@ test('createConfigHotReloadAppliedHandler applies only changed Anki media option
|
||||
const config = deepCloneConfig(DEFAULT_CONFIG);
|
||||
config.ankiConnect.media.normalizeAudio = false;
|
||||
config.ankiConnect.media.mirrorMpvVolume = false;
|
||||
config.ankiConnect.media.reviewTiming = true;
|
||||
const ankiPatches: unknown[] = [];
|
||||
|
||||
const applyHotReload = createConfigHotReloadAppliedHandler({
|
||||
@@ -181,10 +182,18 @@ test('createConfigHotReloadAppliedHandler applies only changed Anki media option
|
||||
},
|
||||
config,
|
||||
);
|
||||
applyHotReload(
|
||||
{
|
||||
hotReloadFields: ['ankiConnect.media.reviewTiming'],
|
||||
restartRequiredFields: [],
|
||||
},
|
||||
config,
|
||||
);
|
||||
|
||||
assert.deepEqual(ankiPatches, [
|
||||
{ media: { normalizeAudio: false } },
|
||||
{ media: { mirrorMpvVolume: false } },
|
||||
{ media: { reviewTiming: true } },
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -100,6 +100,9 @@ function buildAnkiRuntimeConfigPatch(
|
||||
if (diff.hotReloadFields.includes('ankiConnect.media.mirrorMpvVolume')) {
|
||||
mediaPatch.mirrorMpvVolume = config.ankiConnect.media.mirrorMpvVolume;
|
||||
}
|
||||
if (diff.hotReloadFields.includes('ankiConnect.media.reviewTiming')) {
|
||||
mediaPatch.reviewTiming = config.ankiConnect.media.reviewTiming;
|
||||
}
|
||||
if (Object.keys(mediaPatch).length > 0) {
|
||||
patch.media = mediaPatch;
|
||||
}
|
||||
|
||||
@@ -82,3 +82,19 @@ test('RuntimeOptionsManager keeps known-word and n+1 annotation toggles separate
|
||||
assert.equal(effective.nPlusOne?.enabled, true);
|
||||
assert.deepEqual(patches, []);
|
||||
});
|
||||
|
||||
test('RuntimeOptionsManager applies media timing review to the live Anki config', () => {
|
||||
const baseConfig = structuredClone(DEFAULT_CONFIG.ankiConnect);
|
||||
const patches: unknown[] = [];
|
||||
const manager = new RuntimeOptionsManager(() => structuredClone(baseConfig), {
|
||||
applyAnkiPatch: (patch) => {
|
||||
patches.push(patch);
|
||||
},
|
||||
onOptionsChanged: () => undefined,
|
||||
});
|
||||
|
||||
assert.equal(manager.getOptionValue('anki.mediaReviewTiming'), false);
|
||||
assert.equal(manager.setOptionValue('anki.mediaReviewTiming', true).ok, true);
|
||||
assert.equal(manager.getEffectiveAnkiConnectConfig().media?.reviewTiming, true);
|
||||
assert.deepEqual(patches, [{ media: { reviewTiming: true } }]);
|
||||
});
|
||||
|
||||
@@ -53,6 +53,7 @@ const SESSION_ACTION_IDS: SessionActionId[] = [
|
||||
|
||||
const RUNTIME_OPTION_IDS: RuntimeOptionId[] = [
|
||||
'anki.autoUpdateNewCards',
|
||||
'anki.mediaReviewTiming',
|
||||
'subtitle.annotation.knownWords.highlightEnabled',
|
||||
'subtitle.annotation.knownWords.maturityEnabled',
|
||||
'subtitle.annotation.nPlusOne',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export type RuntimeOptionId =
|
||||
| 'anki.autoUpdateNewCards'
|
||||
| 'anki.mediaReviewTiming'
|
||||
| 'subtitle.annotation.knownWords.highlightEnabled'
|
||||
| 'subtitle.annotation.knownWords.maturityEnabled'
|
||||
| 'subtitle.annotation.nPlusOne'
|
||||
|
||||
Reference in New Issue
Block a user