mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-10 15:13:32 -07:00
feat: add Anki deck dropdown with Yomitan auto-fill in settings (#95)
This commit is contained in:
@@ -60,6 +60,7 @@ test('createConfigHotReloadAppliedHandler runs all hot-reload effects', () => {
|
||||
test('createConfigHotReloadAppliedHandler applies safe Anki, annotation, and logging changes', () => {
|
||||
const config = deepCloneConfig(DEFAULT_CONFIG);
|
||||
config.ankiConnect.behavior.autoUpdateNewCards = false;
|
||||
config.ankiConnect.deck = 'Mining';
|
||||
config.ankiConnect.knownWords.highlightEnabled = true;
|
||||
config.ankiConnect.knownWords.refreshMinutes = 90;
|
||||
config.ankiConnect.knownWords.decks = { Anime: ['Mining'] };
|
||||
@@ -100,6 +101,7 @@ test('createConfigHotReloadAppliedHandler applies safe Anki, annotation, and log
|
||||
{
|
||||
hotReloadFields: [
|
||||
'ankiConnect.behavior.autoUpdateNewCards',
|
||||
'ankiConnect.deck',
|
||||
'ankiConnect.knownWords.highlightEnabled',
|
||||
'ankiConnect.knownWords.refreshMinutes',
|
||||
'ankiConnect.knownWords.decks',
|
||||
@@ -123,6 +125,7 @@ test('createConfigHotReloadAppliedHandler applies safe Anki, annotation, and log
|
||||
|
||||
assert.deepEqual(ankiPatches, [
|
||||
{
|
||||
deck: 'Mining',
|
||||
behavior: { autoUpdateNewCards: false },
|
||||
knownWords: config.ankiConnect.knownWords,
|
||||
nPlusOne: config.ankiConnect.nPlusOne,
|
||||
|
||||
@@ -86,6 +86,9 @@ function buildAnkiRuntimeConfigPatch(
|
||||
if (diff.hotReloadFields.includes('ankiConnect.behavior.autoUpdateNewCards')) {
|
||||
patch.behavior = { autoUpdateNewCards: config.ankiConnect.behavior.autoUpdateNewCards };
|
||||
}
|
||||
if (diff.hotReloadFields.includes('ankiConnect.deck')) {
|
||||
patch.deck = config.ankiConnect.deck;
|
||||
}
|
||||
if (hasAnyHotReloadField(diff, ['ankiConnect.knownWords'])) {
|
||||
patch.knownWords = config.ankiConnect.knownWords;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { DEFAULT_CONFIG, deepCloneConfig } from '../../config';
|
||||
import { IPC_CHANNELS } from '../../shared/ipc/contracts';
|
||||
import { createConfigSettingsRuntime } from './config-settings-runtime';
|
||||
|
||||
test('config settings runtime exposes inferred Yomitan Anki deck lookup', async () => {
|
||||
const handlers = new Map<string, (event: unknown, ...args: unknown[]) => unknown>();
|
||||
const runtime = createConfigSettingsRuntime({
|
||||
fields: [],
|
||||
getConfigPath: () => '/tmp/config.jsonc',
|
||||
getRawConfig: () => ({}),
|
||||
getConfig: () => deepCloneConfig(DEFAULT_CONFIG),
|
||||
getWarnings: () => [],
|
||||
reloadConfigStrict: () =>
|
||||
({
|
||||
ok: true,
|
||||
config: deepCloneConfig(DEFAULT_CONFIG),
|
||||
warnings: [],
|
||||
path: '/tmp/config.jsonc',
|
||||
}) as never,
|
||||
getSettingsWindow: () => null,
|
||||
setSettingsWindow: () => undefined,
|
||||
createSettingsWindow: () => ({}) as never,
|
||||
settingsHtmlPath: '/tmp/settings.html',
|
||||
openPath: async () => '',
|
||||
defaultAnkiConnectUrl: DEFAULT_CONFIG.ankiConnect.url,
|
||||
createAnkiClient: () =>
|
||||
({
|
||||
deckNames: async () => [],
|
||||
fieldNamesForDeck: async () => [],
|
||||
modelNamesForDeck: async () => [],
|
||||
modelNames: async () => [],
|
||||
modelFieldNames: async () => [],
|
||||
}) as never,
|
||||
getYomitanAnkiDeckName: async () => 'Mining',
|
||||
ipcMain: {
|
||||
handle: (channel, listener) => {
|
||||
handlers.set(channel, listener);
|
||||
},
|
||||
},
|
||||
ipcChannels: IPC_CHANNELS.request,
|
||||
});
|
||||
|
||||
runtime.registerHandlers();
|
||||
|
||||
const handler = handlers.get(IPC_CHANNELS.request.getConfigSettingsYomitanAnkiDeckName);
|
||||
assert.ok(handler);
|
||||
assert.deepEqual(await handler({}, undefined), { ok: true, value: 'Mining' });
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import path from 'node:path';
|
||||
import { buildConfigSettingsSnapshot } from '../../config/settings/jsonc-edit';
|
||||
import type { ConfigValidationWarning, RawConfig, ResolvedConfig } from '../../types/config';
|
||||
import type {
|
||||
ConfigSettingsAnkiDeckResult,
|
||||
ConfigSettingsAnkiListResult,
|
||||
ConfigSettingsField,
|
||||
ConfigSettingsSaveResult,
|
||||
@@ -34,6 +35,7 @@ export interface ConfigSettingsIpcChannels {
|
||||
getConfigSettingsAnkiDeckModelNames: string;
|
||||
getConfigSettingsAnkiModelNames: string;
|
||||
getConfigSettingsAnkiModelFieldNames: string;
|
||||
getConfigSettingsYomitanAnkiDeckName: string;
|
||||
}
|
||||
|
||||
export interface ConfigSettingsAnkiClient {
|
||||
@@ -60,6 +62,7 @@ export interface ConfigSettingsRuntimeDeps<TWindow extends ConfigSettingsWindowL
|
||||
openPath(path: string): Promise<string>;
|
||||
defaultAnkiConnectUrl: string;
|
||||
createAnkiClient(url: string): ConfigSettingsAnkiClient;
|
||||
getYomitanAnkiDeckName?: () => Promise<string | null | undefined>;
|
||||
ipcMain: ConfigSettingsIpcMainLike;
|
||||
ipcChannels: ConfigSettingsIpcChannels;
|
||||
log?: (message: string) => void;
|
||||
@@ -190,6 +193,22 @@ export function createConfigSettingsRuntime<TWindow extends ConfigSettingsWindow
|
||||
};
|
||||
}
|
||||
|
||||
async function getYomitanAnkiDeckName(): Promise<ConfigSettingsAnkiDeckResult> {
|
||||
if (!deps.getYomitanAnkiDeckName) {
|
||||
return { ok: true, value: '' };
|
||||
}
|
||||
try {
|
||||
const value = await deps.getYomitanAnkiDeckName();
|
||||
return { ok: true, value: typeof value === 'string' ? value.trim() : '' };
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
value: '',
|
||||
error: error instanceof Error ? error.message : 'Failed to query Yomitan.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function registerHandlers(): void {
|
||||
deps.ipcMain.handle(deps.ipcChannels.getConfigSettingsSnapshot, () => getSnapshot());
|
||||
deps.ipcMain.handle(deps.ipcChannels.saveConfigSettingsPatch, (_event, patch: unknown) => {
|
||||
@@ -236,6 +255,9 @@ export function createConfigSettingsRuntime<TWindow extends ConfigSettingsWindow
|
||||
: invalidAnkiListResult('Note type is required.');
|
||||
},
|
||||
);
|
||||
deps.ipcMain.handle(deps.ipcChannels.getConfigSettingsYomitanAnkiDeckName, () =>
|
||||
getYomitanAnkiDeckName(),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user