diff --git a/docs-site/configuration.md b/docs-site/configuration.md index 8be469d..d24a604 100644 --- a/docs-site/configuration.md +++ b/docs-site/configuration.md @@ -112,6 +112,7 @@ The configuration file includes several main sections: - [**Jimaku**](#jimaku) - Jimaku API configuration and defaults - [**Auto Subtitle Sync**](#auto-subtitle-sync) - Sync current subtitle with `alass`/`ffsubsync` - [**AniList**](#anilist) - Optional post-watch progress updates +- [**Yomitan**](#yomitan) - Reuse an external read-only Yomitan profile via `yomitan.externalProfilePath` - [**Jellyfin**](#jellyfin) - Optional Jellyfin auth, library listing, and playback launch - [**Discord Rich Presence**](#discord-rich-presence) - Optional Discord activity card updates - [**Immersion Tracking**](#immersion-tracking) - Track subtitle sessions and mining activity in SQLite diff --git a/src/config/resolve/integrations.ts b/src/config/resolve/integrations.ts index a3872a5..5b4d6d9 100644 --- a/src/config/resolve/integrations.ts +++ b/src/config/resolve/integrations.ts @@ -1,6 +1,19 @@ +import * as os from 'node:os'; +import * as path from 'node:path'; import { ResolveContext } from './context'; import { asBoolean, asNumber, asString, isObject } from './shared'; +function normalizeExternalProfilePath(value: string): string { + const trimmed = value.trim(); + if (trimmed === '~') { + return os.homedir(); + } + if (trimmed.startsWith('~/') || trimmed.startsWith('~\\')) { + return path.join(os.homedir(), trimmed.slice(2)); + } + return trimmed; +} + export function applyIntegrationConfig(context: ResolveContext): void { const { src, resolved, warn } = context; @@ -202,7 +215,7 @@ export function applyIntegrationConfig(context: ResolveContext): void { if (isObject(src.yomitan)) { const externalProfilePath = asString(src.yomitan.externalProfilePath); if (externalProfilePath !== undefined) { - resolved.yomitan.externalProfilePath = externalProfilePath.trim(); + resolved.yomitan.externalProfilePath = normalizeExternalProfilePath(externalProfilePath); } else if (src.yomitan.externalProfilePath !== undefined) { warn( 'yomitan.externalProfilePath', diff --git a/src/config/resolve/jellyfin.test.ts b/src/config/resolve/jellyfin.test.ts index 0cb8106..eed36a1 100644 --- a/src/config/resolve/jellyfin.test.ts +++ b/src/config/resolve/jellyfin.test.ts @@ -1,5 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; +import * as os from 'node:os'; import { createResolveContext } from './context'; import { applyIntegrationConfig } from './integrations'; @@ -127,3 +128,16 @@ test('yomitan externalProfilePath is trimmed and invalid values warn', () => { assert.equal(invalid.context.resolved.yomitan.externalProfilePath, ''); assert.ok(invalid.warnings.some((warning) => warning.path === 'yomitan.externalProfilePath')); }); + +test('yomitan externalProfilePath expands leading tilde to the current home directory', () => { + const homeDir = os.homedir(); + const { context } = createResolveContext({ + yomitan: { + externalProfilePath: '~/.config/gsm_overlay', + }, + }); + + applyIntegrationConfig(context); + + assert.equal(context.resolved.yomitan.externalProfilePath, `${homeDir}/.config/gsm_overlay`); +}); diff --git a/src/main/runtime/yomitan-settings-opener.test.ts b/src/main/runtime/yomitan-settings-opener.test.ts index 76620a4..6cdb52d 100644 --- a/src/main/runtime/yomitan-settings-opener.test.ts +++ b/src/main/runtime/yomitan-settings-opener.test.ts @@ -22,12 +22,12 @@ test('yomitan opener warns when extension cannot be loaded', async () => { }); test('yomitan opener opens settings window when extension is available', async () => { - let opened = false; + let forwardedSession: { id: string } | null | undefined; const yomitanSession = { id: 'session' }; const openSettings = createOpenYomitanSettingsHandler({ ensureYomitanExtensionLoaded: async () => ({ id: 'ext' }), - openYomitanSettingsWindow: ({ yomitanSession: forwardedSession }) => { - opened = (forwardedSession as { id: string } | null)?.id === 'session'; + openYomitanSettingsWindow: ({ yomitanSession: nextSession }) => { + forwardedSession = nextSession as { id: string } | null; }, getExistingWindow: () => null, setWindow: () => {}, @@ -39,5 +39,5 @@ test('yomitan opener opens settings window when extension is available', async ( openSettings(); await Promise.resolve(); await Promise.resolve(); - assert.equal(opened, true); + assert.equal(forwardedSession, yomitanSession); });