This commit is contained in:
2026-02-17 22:50:57 -08:00
parent ffeef9c136
commit f20d019c11
315 changed files with 9876 additions and 12537 deletions

View File

@@ -1,42 +1,29 @@
import test from "node:test";
import assert from "node:assert/strict";
import test from 'node:test';
import assert from 'node:assert/strict';
import {
isAllowedAnilistExternalUrl,
isAllowedAnilistSetupNavigationUrl,
} from "./anilist-url-guard";
} from './anilist-url-guard';
test("allows only AniList https URLs for external opens", () => {
assert.equal(isAllowedAnilistExternalUrl("https://anilist.co"), true);
assert.equal(
isAllowedAnilistExternalUrl("https://www.anilist.co/settings/developer"),
true,
);
assert.equal(isAllowedAnilistExternalUrl("http://anilist.co"), false);
assert.equal(isAllowedAnilistExternalUrl("https://example.com"), false);
assert.equal(isAllowedAnilistExternalUrl("file:///tmp/test"), false);
assert.equal(isAllowedAnilistExternalUrl("not a url"), false);
test('allows only AniList https URLs for external opens', () => {
assert.equal(isAllowedAnilistExternalUrl('https://anilist.co'), true);
assert.equal(isAllowedAnilistExternalUrl('https://www.anilist.co/settings/developer'), true);
assert.equal(isAllowedAnilistExternalUrl('http://anilist.co'), false);
assert.equal(isAllowedAnilistExternalUrl('https://example.com'), false);
assert.equal(isAllowedAnilistExternalUrl('file:///tmp/test'), false);
assert.equal(isAllowedAnilistExternalUrl('not a url'), false);
});
test("allows only AniList https or data URLs for setup navigation", () => {
test('allows only AniList https or data URLs for setup navigation', () => {
assert.equal(
isAllowedAnilistSetupNavigationUrl(
"https://anilist.co/api/v2/oauth/authorize",
),
isAllowedAnilistSetupNavigationUrl('https://anilist.co/api/v2/oauth/authorize'),
true,
);
assert.equal(
isAllowedAnilistSetupNavigationUrl(
"data:text/html;charset=utf-8,%3Chtml%3E%3C%2Fhtml%3E",
),
isAllowedAnilistSetupNavigationUrl('data:text/html;charset=utf-8,%3Chtml%3E%3C%2Fhtml%3E'),
true,
);
assert.equal(
isAllowedAnilistSetupNavigationUrl("https://example.com/redirect"),
false,
);
assert.equal(
isAllowedAnilistSetupNavigationUrl("javascript:alert(1)"),
false,
);
assert.equal(isAllowedAnilistSetupNavigationUrl('https://example.com/redirect'), false);
assert.equal(isAllowedAnilistSetupNavigationUrl('javascript:alert(1)'), false);
});

View File

@@ -1,11 +1,10 @@
const ANILIST_ALLOWED_HOSTS = new Set(["anilist.co", "www.anilist.co"]);
const ANILIST_ALLOWED_HOSTS = new Set(['anilist.co', 'www.anilist.co']);
export function isAllowedAnilistExternalUrl(rawUrl: string): boolean {
try {
const parsedUrl = new URL(rawUrl);
return (
parsedUrl.protocol === "https:" &&
ANILIST_ALLOWED_HOSTS.has(parsedUrl.hostname.toLowerCase())
parsedUrl.protocol === 'https:' && ANILIST_ALLOWED_HOSTS.has(parsedUrl.hostname.toLowerCase())
);
} catch {
return false;
@@ -18,7 +17,7 @@ export function isAllowedAnilistSetupNavigationUrl(rawUrl: string): boolean {
}
try {
const parsedUrl = new URL(rawUrl);
return parsedUrl.protocol === "data:";
return parsedUrl.protocol === 'data:';
} catch {
return false;
}

View File

@@ -1,10 +1,10 @@
import type { CliArgs, CliCommandSource } from "../cli/args";
import { runAppReadyRuntime } from "../core/services/startup";
import type { AppReadyRuntimeDeps } from "../core/services/startup";
import type { AppLifecycleDepsRuntimeOptions } from "../core/services/app-lifecycle";
import type { CliArgs, CliCommandSource } from '../cli/args';
import { runAppReadyRuntime } from '../core/services/startup';
import type { AppReadyRuntimeDeps } from '../core/services/startup';
import type { AppLifecycleDepsRuntimeOptions } from '../core/services/app-lifecycle';
export interface AppLifecycleRuntimeDepsFactoryInput {
app: AppLifecycleDepsRuntimeOptions["app"];
app: AppLifecycleDepsRuntimeOptions['app'];
platform: NodeJS.Platform;
shouldStartApp: (args: CliArgs) => boolean;
parseArgs: (argv: string[]) => CliArgs;
@@ -18,30 +18,30 @@ export interface AppLifecycleRuntimeDepsFactoryInput {
}
export interface AppReadyRuntimeDepsFactoryInput {
loadSubtitlePosition: AppReadyRuntimeDeps["loadSubtitlePosition"];
resolveKeybindings: AppReadyRuntimeDeps["resolveKeybindings"];
createMpvClient: AppReadyRuntimeDeps["createMpvClient"];
reloadConfig: AppReadyRuntimeDeps["reloadConfig"];
getResolvedConfig: AppReadyRuntimeDeps["getResolvedConfig"];
getConfigWarnings: AppReadyRuntimeDeps["getConfigWarnings"];
logConfigWarning: AppReadyRuntimeDeps["logConfigWarning"];
initRuntimeOptionsManager: AppReadyRuntimeDeps["initRuntimeOptionsManager"];
setSecondarySubMode: AppReadyRuntimeDeps["setSecondarySubMode"];
defaultSecondarySubMode: AppReadyRuntimeDeps["defaultSecondarySubMode"];
defaultWebsocketPort: AppReadyRuntimeDeps["defaultWebsocketPort"];
hasMpvWebsocketPlugin: AppReadyRuntimeDeps["hasMpvWebsocketPlugin"];
startSubtitleWebsocket: AppReadyRuntimeDeps["startSubtitleWebsocket"];
log: AppReadyRuntimeDeps["log"];
setLogLevel: AppReadyRuntimeDeps["setLogLevel"];
createMecabTokenizerAndCheck: AppReadyRuntimeDeps["createMecabTokenizerAndCheck"];
createSubtitleTimingTracker: AppReadyRuntimeDeps["createSubtitleTimingTracker"];
createImmersionTracker?: AppReadyRuntimeDeps["createImmersionTracker"];
startJellyfinRemoteSession?: AppReadyRuntimeDeps["startJellyfinRemoteSession"];
loadYomitanExtension: AppReadyRuntimeDeps["loadYomitanExtension"];
texthookerOnlyMode: AppReadyRuntimeDeps["texthookerOnlyMode"];
shouldAutoInitializeOverlayRuntimeFromConfig: AppReadyRuntimeDeps["shouldAutoInitializeOverlayRuntimeFromConfig"];
initializeOverlayRuntime: AppReadyRuntimeDeps["initializeOverlayRuntime"];
handleInitialArgs: AppReadyRuntimeDeps["handleInitialArgs"];
loadSubtitlePosition: AppReadyRuntimeDeps['loadSubtitlePosition'];
resolveKeybindings: AppReadyRuntimeDeps['resolveKeybindings'];
createMpvClient: AppReadyRuntimeDeps['createMpvClient'];
reloadConfig: AppReadyRuntimeDeps['reloadConfig'];
getResolvedConfig: AppReadyRuntimeDeps['getResolvedConfig'];
getConfigWarnings: AppReadyRuntimeDeps['getConfigWarnings'];
logConfigWarning: AppReadyRuntimeDeps['logConfigWarning'];
initRuntimeOptionsManager: AppReadyRuntimeDeps['initRuntimeOptionsManager'];
setSecondarySubMode: AppReadyRuntimeDeps['setSecondarySubMode'];
defaultSecondarySubMode: AppReadyRuntimeDeps['defaultSecondarySubMode'];
defaultWebsocketPort: AppReadyRuntimeDeps['defaultWebsocketPort'];
hasMpvWebsocketPlugin: AppReadyRuntimeDeps['hasMpvWebsocketPlugin'];
startSubtitleWebsocket: AppReadyRuntimeDeps['startSubtitleWebsocket'];
log: AppReadyRuntimeDeps['log'];
setLogLevel: AppReadyRuntimeDeps['setLogLevel'];
createMecabTokenizerAndCheck: AppReadyRuntimeDeps['createMecabTokenizerAndCheck'];
createSubtitleTimingTracker: AppReadyRuntimeDeps['createSubtitleTimingTracker'];
createImmersionTracker?: AppReadyRuntimeDeps['createImmersionTracker'];
startJellyfinRemoteSession?: AppReadyRuntimeDeps['startJellyfinRemoteSession'];
loadYomitanExtension: AppReadyRuntimeDeps['loadYomitanExtension'];
texthookerOnlyMode: AppReadyRuntimeDeps['texthookerOnlyMode'];
shouldAutoInitializeOverlayRuntimeFromConfig: AppReadyRuntimeDeps['shouldAutoInitializeOverlayRuntimeFromConfig'];
initializeOverlayRuntime: AppReadyRuntimeDeps['initializeOverlayRuntime'];
handleInitialArgs: AppReadyRuntimeDeps['handleInitialArgs'];
}
export function createAppLifecycleRuntimeDeps(

View File

@@ -1,18 +1,15 @@
import {
handleCliCommand,
createCliCommandDepsRuntime,
} from "../core/services";
import type { CliArgs, CliCommandSource } from "../cli/args";
import { handleCliCommand, createCliCommandDepsRuntime } from '../core/services';
import type { CliArgs, CliCommandSource } from '../cli/args';
import {
createCliCommandRuntimeServiceDeps,
CliCommandRuntimeServiceDepsParams,
} from "./dependencies";
} from './dependencies';
export interface CliCommandRuntimeServiceContext {
getSocketPath: () => string;
setSocketPath: (socketPath: string) => void;
getClient: CliCommandRuntimeServiceDepsParams["mpv"]["getClient"];
showOsd: CliCommandRuntimeServiceDepsParams["mpv"]["showOsd"];
getClient: CliCommandRuntimeServiceDepsParams['mpv']['getClient'];
showOsd: CliCommandRuntimeServiceDepsParams['mpv']['showOsd'];
getTexthookerPort: () => number;
setTexthookerPort: (port: number) => void;
shouldOpenBrowser: () => boolean;
@@ -32,13 +29,13 @@ export interface CliCommandRuntimeServiceContext {
triggerFieldGrouping: () => Promise<void>;
triggerSubsyncFromConfig: () => Promise<void>;
markLastCardAsAudioCard: () => Promise<void>;
getAnilistStatus: CliCommandRuntimeServiceDepsParams["anilist"]["getStatus"];
clearAnilistToken: CliCommandRuntimeServiceDepsParams["anilist"]["clearToken"];
openAnilistSetup: CliCommandRuntimeServiceDepsParams["anilist"]["openSetup"];
getAnilistQueueStatus: CliCommandRuntimeServiceDepsParams["anilist"]["getQueueStatus"];
retryAnilistQueueNow: CliCommandRuntimeServiceDepsParams["anilist"]["retryQueueNow"];
openJellyfinSetup: CliCommandRuntimeServiceDepsParams["jellyfin"]["openSetup"];
runJellyfinCommand: CliCommandRuntimeServiceDepsParams["jellyfin"]["runCommand"];
getAnilistStatus: CliCommandRuntimeServiceDepsParams['anilist']['getStatus'];
clearAnilistToken: CliCommandRuntimeServiceDepsParams['anilist']['clearToken'];
openAnilistSetup: CliCommandRuntimeServiceDepsParams['anilist']['openSetup'];
getAnilistQueueStatus: CliCommandRuntimeServiceDepsParams['anilist']['getQueueStatus'];
retryAnilistQueueNow: CliCommandRuntimeServiceDepsParams['anilist']['retryQueueNow'];
openJellyfinSetup: CliCommandRuntimeServiceDepsParams['jellyfin']['openSetup'];
runJellyfinCommand: CliCommandRuntimeServiceDepsParams['jellyfin']['runCommand'];
openYomitanSettings: () => void;
cycleSecondarySubMode: () => void;
openRuntimeOptionsPalette: () => void;
@@ -53,12 +50,11 @@ export interface CliCommandRuntimeServiceContext {
}
export interface CliCommandRuntimeServiceContextHandlers {
texthookerService: CliCommandRuntimeServiceDepsParams["texthooker"]["service"];
texthookerService: CliCommandRuntimeServiceDepsParams['texthooker']['service'];
}
function createCliCommandDepsFromContext(
context: CliCommandRuntimeServiceContext &
CliCommandRuntimeServiceContextHandlers,
context: CliCommandRuntimeServiceContext & CliCommandRuntimeServiceContextHandlers,
): CliCommandRuntimeServiceDepsParams {
return {
mpv: {
@@ -86,8 +82,7 @@ function createCliCommandDepsFromContext(
copyCurrentSubtitle: context.copyCurrentSubtitle,
startPendingMultiCopy: context.startPendingMultiCopy,
mineSentenceCard: context.mineSentenceCard,
startPendingMineSentenceMultiple:
context.startPendingMineSentenceMultiple,
startPendingMineSentenceMultiple: context.startPendingMineSentenceMultiple,
updateLastCardFromClipboard: context.updateLastCardFromClipboard,
refreshKnownWords: context.refreshKnownWordCache,
triggerFieldGrouping: context.triggerFieldGrouping,
@@ -128,21 +123,14 @@ export function handleCliCommandRuntimeService(
source: CliCommandSource,
params: CliCommandRuntimeServiceDepsParams,
): void {
const deps = createCliCommandDepsRuntime(
createCliCommandRuntimeServiceDeps(params),
);
const deps = createCliCommandDepsRuntime(createCliCommandRuntimeServiceDeps(params));
handleCliCommand(args, source, deps);
}
export function handleCliCommandRuntimeServiceWithContext(
args: CliArgs,
source: CliCommandSource,
context: CliCommandRuntimeServiceContext &
CliCommandRuntimeServiceContextHandlers,
context: CliCommandRuntimeServiceContext & CliCommandRuntimeServiceContextHandlers,
): void {
handleCliCommandRuntimeService(
args,
source,
createCliCommandDepsFromContext(context),
);
handleCliCommandRuntimeService(args, source, createCliCommandDepsFromContext(context));
}

View File

@@ -1,19 +1,15 @@
import {
RuntimeOptionId,
RuntimeOptionValue,
SubsyncManualPayload,
} from "../types";
import { SubsyncResolvedConfig } from "../subsync/utils";
import type { SubsyncRuntimeDeps } from "../core/services/subsync-runner";
import type { IpcDepsRuntimeOptions } from "../core/services/ipc";
import type { AnkiJimakuIpcRuntimeOptions } from "../core/services/anki-jimaku";
import type { CliCommandDepsRuntimeOptions } from "../core/services/cli-command";
import type { HandleMpvCommandFromIpcOptions } from "../core/services/ipc-command";
import { RuntimeOptionId, RuntimeOptionValue, SubsyncManualPayload } from '../types';
import { SubsyncResolvedConfig } from '../subsync/utils';
import type { SubsyncRuntimeDeps } from '../core/services/subsync-runner';
import type { IpcDepsRuntimeOptions } from '../core/services/ipc';
import type { AnkiJimakuIpcRuntimeOptions } from '../core/services/anki-jimaku';
import type { CliCommandDepsRuntimeOptions } from '../core/services/cli-command';
import type { HandleMpvCommandFromIpcOptions } from '../core/services/ipc-command';
import {
cycleRuntimeOptionFromIpcRuntime,
setRuntimeOptionFromIpcRuntime,
} from "../core/services/runtime-options-ipc";
import { RuntimeOptionsManager } from "../runtime-options";
} from '../core/services/runtime-options-ipc';
import { RuntimeOptionsManager } from '../runtime-options';
export interface RuntimeOptionsIpcDepsParams {
getRuntimeOptionsManager: () => RuntimeOptionsManager | null;
@@ -21,7 +17,7 @@ export interface RuntimeOptionsIpcDepsParams {
}
export interface SubsyncRuntimeDepsParams {
getMpvClient: () => ReturnType<SubsyncRuntimeDeps["getMpvClient"]>;
getMpvClient: () => ReturnType<SubsyncRuntimeDeps['getMpvClient']>;
getResolvedSubsyncConfig: () => SubsyncResolvedConfig;
isSubsyncInProgress: () => boolean;
setSubsyncInProgress: (inProgress: boolean) => void;
@@ -29,9 +25,7 @@ export interface SubsyncRuntimeDepsParams {
openManualPicker: (payload: SubsyncManualPayload) => void;
}
export function createRuntimeOptionsIpcDeps(
params: RuntimeOptionsIpcDepsParams,
): {
export function createRuntimeOptionsIpcDeps(params: RuntimeOptionsIpcDepsParams): {
setRuntimeOption: (id: string, value: unknown) => unknown;
cycleRuntimeOption: (id: string, direction: 1 | -1) => unknown;
} {
@@ -53,9 +47,7 @@ export function createRuntimeOptionsIpcDeps(
};
}
export function createSubsyncRuntimeDeps(
params: SubsyncRuntimeDepsParams,
): SubsyncRuntimeDeps {
export function createSubsyncRuntimeDeps(params: SubsyncRuntimeDepsParams): SubsyncRuntimeDeps {
return {
getMpvClient: params.getMpvClient,
getResolvedSubsyncConfig: params.getResolvedSubsyncConfig,
@@ -67,136 +59,136 @@ export function createSubsyncRuntimeDeps(
}
export interface MainIpcRuntimeServiceDepsParams {
getInvisibleWindow: IpcDepsRuntimeOptions["getInvisibleWindow"];
getMainWindow: IpcDepsRuntimeOptions["getMainWindow"];
getVisibleOverlayVisibility: IpcDepsRuntimeOptions["getVisibleOverlayVisibility"];
getInvisibleOverlayVisibility: IpcDepsRuntimeOptions["getInvisibleOverlayVisibility"];
onOverlayModalClosed: IpcDepsRuntimeOptions["onOverlayModalClosed"];
openYomitanSettings: IpcDepsRuntimeOptions["openYomitanSettings"];
quitApp: IpcDepsRuntimeOptions["quitApp"];
toggleVisibleOverlay: IpcDepsRuntimeOptions["toggleVisibleOverlay"];
tokenizeCurrentSubtitle: IpcDepsRuntimeOptions["tokenizeCurrentSubtitle"];
getCurrentSubtitleAss: IpcDepsRuntimeOptions["getCurrentSubtitleAss"];
focusMainWindow?: IpcDepsRuntimeOptions["focusMainWindow"];
getMpvSubtitleRenderMetrics: IpcDepsRuntimeOptions["getMpvSubtitleRenderMetrics"];
getSubtitlePosition: IpcDepsRuntimeOptions["getSubtitlePosition"];
getSubtitleStyle: IpcDepsRuntimeOptions["getSubtitleStyle"];
saveSubtitlePosition: IpcDepsRuntimeOptions["saveSubtitlePosition"];
getMecabTokenizer: IpcDepsRuntimeOptions["getMecabTokenizer"];
handleMpvCommand: IpcDepsRuntimeOptions["handleMpvCommand"];
getKeybindings: IpcDepsRuntimeOptions["getKeybindings"];
getConfiguredShortcuts: IpcDepsRuntimeOptions["getConfiguredShortcuts"];
getSecondarySubMode: IpcDepsRuntimeOptions["getSecondarySubMode"];
getMpvClient: IpcDepsRuntimeOptions["getMpvClient"];
runSubsyncManual: IpcDepsRuntimeOptions["runSubsyncManual"];
getAnkiConnectStatus: IpcDepsRuntimeOptions["getAnkiConnectStatus"];
getRuntimeOptions: IpcDepsRuntimeOptions["getRuntimeOptions"];
setRuntimeOption: IpcDepsRuntimeOptions["setRuntimeOption"];
cycleRuntimeOption: IpcDepsRuntimeOptions["cycleRuntimeOption"];
reportOverlayContentBounds: IpcDepsRuntimeOptions["reportOverlayContentBounds"];
getAnilistStatus: IpcDepsRuntimeOptions["getAnilistStatus"];
clearAnilistToken: IpcDepsRuntimeOptions["clearAnilistToken"];
openAnilistSetup: IpcDepsRuntimeOptions["openAnilistSetup"];
getAnilistQueueStatus: IpcDepsRuntimeOptions["getAnilistQueueStatus"];
retryAnilistQueueNow: IpcDepsRuntimeOptions["retryAnilistQueueNow"];
getInvisibleWindow: IpcDepsRuntimeOptions['getInvisibleWindow'];
getMainWindow: IpcDepsRuntimeOptions['getMainWindow'];
getVisibleOverlayVisibility: IpcDepsRuntimeOptions['getVisibleOverlayVisibility'];
getInvisibleOverlayVisibility: IpcDepsRuntimeOptions['getInvisibleOverlayVisibility'];
onOverlayModalClosed: IpcDepsRuntimeOptions['onOverlayModalClosed'];
openYomitanSettings: IpcDepsRuntimeOptions['openYomitanSettings'];
quitApp: IpcDepsRuntimeOptions['quitApp'];
toggleVisibleOverlay: IpcDepsRuntimeOptions['toggleVisibleOverlay'];
tokenizeCurrentSubtitle: IpcDepsRuntimeOptions['tokenizeCurrentSubtitle'];
getCurrentSubtitleAss: IpcDepsRuntimeOptions['getCurrentSubtitleAss'];
focusMainWindow?: IpcDepsRuntimeOptions['focusMainWindow'];
getMpvSubtitleRenderMetrics: IpcDepsRuntimeOptions['getMpvSubtitleRenderMetrics'];
getSubtitlePosition: IpcDepsRuntimeOptions['getSubtitlePosition'];
getSubtitleStyle: IpcDepsRuntimeOptions['getSubtitleStyle'];
saveSubtitlePosition: IpcDepsRuntimeOptions['saveSubtitlePosition'];
getMecabTokenizer: IpcDepsRuntimeOptions['getMecabTokenizer'];
handleMpvCommand: IpcDepsRuntimeOptions['handleMpvCommand'];
getKeybindings: IpcDepsRuntimeOptions['getKeybindings'];
getConfiguredShortcuts: IpcDepsRuntimeOptions['getConfiguredShortcuts'];
getSecondarySubMode: IpcDepsRuntimeOptions['getSecondarySubMode'];
getMpvClient: IpcDepsRuntimeOptions['getMpvClient'];
runSubsyncManual: IpcDepsRuntimeOptions['runSubsyncManual'];
getAnkiConnectStatus: IpcDepsRuntimeOptions['getAnkiConnectStatus'];
getRuntimeOptions: IpcDepsRuntimeOptions['getRuntimeOptions'];
setRuntimeOption: IpcDepsRuntimeOptions['setRuntimeOption'];
cycleRuntimeOption: IpcDepsRuntimeOptions['cycleRuntimeOption'];
reportOverlayContentBounds: IpcDepsRuntimeOptions['reportOverlayContentBounds'];
getAnilistStatus: IpcDepsRuntimeOptions['getAnilistStatus'];
clearAnilistToken: IpcDepsRuntimeOptions['clearAnilistToken'];
openAnilistSetup: IpcDepsRuntimeOptions['openAnilistSetup'];
getAnilistQueueStatus: IpcDepsRuntimeOptions['getAnilistQueueStatus'];
retryAnilistQueueNow: IpcDepsRuntimeOptions['retryAnilistQueueNow'];
}
export interface AnkiJimakuIpcRuntimeServiceDepsParams {
patchAnkiConnectEnabled: AnkiJimakuIpcRuntimeOptions["patchAnkiConnectEnabled"];
getResolvedConfig: AnkiJimakuIpcRuntimeOptions["getResolvedConfig"];
getRuntimeOptionsManager: AnkiJimakuIpcRuntimeOptions["getRuntimeOptionsManager"];
getSubtitleTimingTracker: AnkiJimakuIpcRuntimeOptions["getSubtitleTimingTracker"];
getMpvClient: AnkiJimakuIpcRuntimeOptions["getMpvClient"];
getAnkiIntegration: AnkiJimakuIpcRuntimeOptions["getAnkiIntegration"];
setAnkiIntegration: AnkiJimakuIpcRuntimeOptions["setAnkiIntegration"];
getKnownWordCacheStatePath: AnkiJimakuIpcRuntimeOptions["getKnownWordCacheStatePath"];
showDesktopNotification: AnkiJimakuIpcRuntimeOptions["showDesktopNotification"];
createFieldGroupingCallback: AnkiJimakuIpcRuntimeOptions["createFieldGroupingCallback"];
broadcastRuntimeOptionsChanged: AnkiJimakuIpcRuntimeOptions["broadcastRuntimeOptionsChanged"];
getFieldGroupingResolver: AnkiJimakuIpcRuntimeOptions["getFieldGroupingResolver"];
setFieldGroupingResolver: AnkiJimakuIpcRuntimeOptions["setFieldGroupingResolver"];
parseMediaInfo: AnkiJimakuIpcRuntimeOptions["parseMediaInfo"];
getCurrentMediaPath: AnkiJimakuIpcRuntimeOptions["getCurrentMediaPath"];
jimakuFetchJson: AnkiJimakuIpcRuntimeOptions["jimakuFetchJson"];
getJimakuMaxEntryResults: AnkiJimakuIpcRuntimeOptions["getJimakuMaxEntryResults"];
getJimakuLanguagePreference: AnkiJimakuIpcRuntimeOptions["getJimakuLanguagePreference"];
resolveJimakuApiKey: AnkiJimakuIpcRuntimeOptions["resolveJimakuApiKey"];
isRemoteMediaPath: AnkiJimakuIpcRuntimeOptions["isRemoteMediaPath"];
downloadToFile: AnkiJimakuIpcRuntimeOptions["downloadToFile"];
patchAnkiConnectEnabled: AnkiJimakuIpcRuntimeOptions['patchAnkiConnectEnabled'];
getResolvedConfig: AnkiJimakuIpcRuntimeOptions['getResolvedConfig'];
getRuntimeOptionsManager: AnkiJimakuIpcRuntimeOptions['getRuntimeOptionsManager'];
getSubtitleTimingTracker: AnkiJimakuIpcRuntimeOptions['getSubtitleTimingTracker'];
getMpvClient: AnkiJimakuIpcRuntimeOptions['getMpvClient'];
getAnkiIntegration: AnkiJimakuIpcRuntimeOptions['getAnkiIntegration'];
setAnkiIntegration: AnkiJimakuIpcRuntimeOptions['setAnkiIntegration'];
getKnownWordCacheStatePath: AnkiJimakuIpcRuntimeOptions['getKnownWordCacheStatePath'];
showDesktopNotification: AnkiJimakuIpcRuntimeOptions['showDesktopNotification'];
createFieldGroupingCallback: AnkiJimakuIpcRuntimeOptions['createFieldGroupingCallback'];
broadcastRuntimeOptionsChanged: AnkiJimakuIpcRuntimeOptions['broadcastRuntimeOptionsChanged'];
getFieldGroupingResolver: AnkiJimakuIpcRuntimeOptions['getFieldGroupingResolver'];
setFieldGroupingResolver: AnkiJimakuIpcRuntimeOptions['setFieldGroupingResolver'];
parseMediaInfo: AnkiJimakuIpcRuntimeOptions['parseMediaInfo'];
getCurrentMediaPath: AnkiJimakuIpcRuntimeOptions['getCurrentMediaPath'];
jimakuFetchJson: AnkiJimakuIpcRuntimeOptions['jimakuFetchJson'];
getJimakuMaxEntryResults: AnkiJimakuIpcRuntimeOptions['getJimakuMaxEntryResults'];
getJimakuLanguagePreference: AnkiJimakuIpcRuntimeOptions['getJimakuLanguagePreference'];
resolveJimakuApiKey: AnkiJimakuIpcRuntimeOptions['resolveJimakuApiKey'];
isRemoteMediaPath: AnkiJimakuIpcRuntimeOptions['isRemoteMediaPath'];
downloadToFile: AnkiJimakuIpcRuntimeOptions['downloadToFile'];
}
export interface CliCommandRuntimeServiceDepsParams {
mpv: {
getSocketPath: CliCommandDepsRuntimeOptions["mpv"]["getSocketPath"];
setSocketPath: CliCommandDepsRuntimeOptions["mpv"]["setSocketPath"];
getClient: CliCommandDepsRuntimeOptions["mpv"]["getClient"];
showOsd: CliCommandDepsRuntimeOptions["mpv"]["showOsd"];
getSocketPath: CliCommandDepsRuntimeOptions['mpv']['getSocketPath'];
setSocketPath: CliCommandDepsRuntimeOptions['mpv']['setSocketPath'];
getClient: CliCommandDepsRuntimeOptions['mpv']['getClient'];
showOsd: CliCommandDepsRuntimeOptions['mpv']['showOsd'];
};
texthooker: {
service: CliCommandDepsRuntimeOptions["texthooker"]["service"];
getPort: CliCommandDepsRuntimeOptions["texthooker"]["getPort"];
setPort: CliCommandDepsRuntimeOptions["texthooker"]["setPort"];
shouldOpenBrowser: CliCommandDepsRuntimeOptions["texthooker"]["shouldOpenBrowser"];
openInBrowser: CliCommandDepsRuntimeOptions["texthooker"]["openInBrowser"];
service: CliCommandDepsRuntimeOptions['texthooker']['service'];
getPort: CliCommandDepsRuntimeOptions['texthooker']['getPort'];
setPort: CliCommandDepsRuntimeOptions['texthooker']['setPort'];
shouldOpenBrowser: CliCommandDepsRuntimeOptions['texthooker']['shouldOpenBrowser'];
openInBrowser: CliCommandDepsRuntimeOptions['texthooker']['openInBrowser'];
};
overlay: {
isInitialized: CliCommandDepsRuntimeOptions["overlay"]["isInitialized"];
initialize: CliCommandDepsRuntimeOptions["overlay"]["initialize"];
toggleVisible: CliCommandDepsRuntimeOptions["overlay"]["toggleVisible"];
toggleInvisible: CliCommandDepsRuntimeOptions["overlay"]["toggleInvisible"];
setVisible: CliCommandDepsRuntimeOptions["overlay"]["setVisible"];
setInvisible: CliCommandDepsRuntimeOptions["overlay"]["setInvisible"];
isInitialized: CliCommandDepsRuntimeOptions['overlay']['isInitialized'];
initialize: CliCommandDepsRuntimeOptions['overlay']['initialize'];
toggleVisible: CliCommandDepsRuntimeOptions['overlay']['toggleVisible'];
toggleInvisible: CliCommandDepsRuntimeOptions['overlay']['toggleInvisible'];
setVisible: CliCommandDepsRuntimeOptions['overlay']['setVisible'];
setInvisible: CliCommandDepsRuntimeOptions['overlay']['setInvisible'];
};
mining: {
copyCurrentSubtitle: CliCommandDepsRuntimeOptions["mining"]["copyCurrentSubtitle"];
startPendingMultiCopy: CliCommandDepsRuntimeOptions["mining"]["startPendingMultiCopy"];
mineSentenceCard: CliCommandDepsRuntimeOptions["mining"]["mineSentenceCard"];
startPendingMineSentenceMultiple: CliCommandDepsRuntimeOptions["mining"]["startPendingMineSentenceMultiple"];
updateLastCardFromClipboard: CliCommandDepsRuntimeOptions["mining"]["updateLastCardFromClipboard"];
refreshKnownWords: CliCommandDepsRuntimeOptions["mining"]["refreshKnownWords"];
triggerFieldGrouping: CliCommandDepsRuntimeOptions["mining"]["triggerFieldGrouping"];
triggerSubsyncFromConfig: CliCommandDepsRuntimeOptions["mining"]["triggerSubsyncFromConfig"];
markLastCardAsAudioCard: CliCommandDepsRuntimeOptions["mining"]["markLastCardAsAudioCard"];
copyCurrentSubtitle: CliCommandDepsRuntimeOptions['mining']['copyCurrentSubtitle'];
startPendingMultiCopy: CliCommandDepsRuntimeOptions['mining']['startPendingMultiCopy'];
mineSentenceCard: CliCommandDepsRuntimeOptions['mining']['mineSentenceCard'];
startPendingMineSentenceMultiple: CliCommandDepsRuntimeOptions['mining']['startPendingMineSentenceMultiple'];
updateLastCardFromClipboard: CliCommandDepsRuntimeOptions['mining']['updateLastCardFromClipboard'];
refreshKnownWords: CliCommandDepsRuntimeOptions['mining']['refreshKnownWords'];
triggerFieldGrouping: CliCommandDepsRuntimeOptions['mining']['triggerFieldGrouping'];
triggerSubsyncFromConfig: CliCommandDepsRuntimeOptions['mining']['triggerSubsyncFromConfig'];
markLastCardAsAudioCard: CliCommandDepsRuntimeOptions['mining']['markLastCardAsAudioCard'];
};
anilist: {
getStatus: CliCommandDepsRuntimeOptions["anilist"]["getStatus"];
clearToken: CliCommandDepsRuntimeOptions["anilist"]["clearToken"];
openSetup: CliCommandDepsRuntimeOptions["anilist"]["openSetup"];
getQueueStatus: CliCommandDepsRuntimeOptions["anilist"]["getQueueStatus"];
retryQueueNow: CliCommandDepsRuntimeOptions["anilist"]["retryQueueNow"];
getStatus: CliCommandDepsRuntimeOptions['anilist']['getStatus'];
clearToken: CliCommandDepsRuntimeOptions['anilist']['clearToken'];
openSetup: CliCommandDepsRuntimeOptions['anilist']['openSetup'];
getQueueStatus: CliCommandDepsRuntimeOptions['anilist']['getQueueStatus'];
retryQueueNow: CliCommandDepsRuntimeOptions['anilist']['retryQueueNow'];
};
jellyfin: {
openSetup: CliCommandDepsRuntimeOptions["jellyfin"]["openSetup"];
runCommand: CliCommandDepsRuntimeOptions["jellyfin"]["runCommand"];
openSetup: CliCommandDepsRuntimeOptions['jellyfin']['openSetup'];
runCommand: CliCommandDepsRuntimeOptions['jellyfin']['runCommand'];
};
ui: {
openYomitanSettings: CliCommandDepsRuntimeOptions["ui"]["openYomitanSettings"];
cycleSecondarySubMode: CliCommandDepsRuntimeOptions["ui"]["cycleSecondarySubMode"];
openRuntimeOptionsPalette: CliCommandDepsRuntimeOptions["ui"]["openRuntimeOptionsPalette"];
printHelp: CliCommandDepsRuntimeOptions["ui"]["printHelp"];
openYomitanSettings: CliCommandDepsRuntimeOptions['ui']['openYomitanSettings'];
cycleSecondarySubMode: CliCommandDepsRuntimeOptions['ui']['cycleSecondarySubMode'];
openRuntimeOptionsPalette: CliCommandDepsRuntimeOptions['ui']['openRuntimeOptionsPalette'];
printHelp: CliCommandDepsRuntimeOptions['ui']['printHelp'];
};
app: {
stop: CliCommandDepsRuntimeOptions["app"]["stop"];
hasMainWindow: CliCommandDepsRuntimeOptions["app"]["hasMainWindow"];
stop: CliCommandDepsRuntimeOptions['app']['stop'];
hasMainWindow: CliCommandDepsRuntimeOptions['app']['hasMainWindow'];
};
getMultiCopyTimeoutMs: CliCommandDepsRuntimeOptions["getMultiCopyTimeoutMs"];
schedule: CliCommandDepsRuntimeOptions["schedule"];
log: CliCommandDepsRuntimeOptions["log"];
warn: CliCommandDepsRuntimeOptions["warn"];
error: CliCommandDepsRuntimeOptions["error"];
getMultiCopyTimeoutMs: CliCommandDepsRuntimeOptions['getMultiCopyTimeoutMs'];
schedule: CliCommandDepsRuntimeOptions['schedule'];
log: CliCommandDepsRuntimeOptions['log'];
warn: CliCommandDepsRuntimeOptions['warn'];
error: CliCommandDepsRuntimeOptions['error'];
}
export interface MpvCommandRuntimeServiceDepsParams {
specialCommands: HandleMpvCommandFromIpcOptions["specialCommands"];
runtimeOptionsCycle: HandleMpvCommandFromIpcOptions["runtimeOptionsCycle"];
triggerSubsyncFromConfig: HandleMpvCommandFromIpcOptions["triggerSubsyncFromConfig"];
openRuntimeOptionsPalette: HandleMpvCommandFromIpcOptions["openRuntimeOptionsPalette"];
showMpvOsd: HandleMpvCommandFromIpcOptions["showMpvOsd"];
mpvReplaySubtitle: HandleMpvCommandFromIpcOptions["mpvReplaySubtitle"];
mpvPlayNextSubtitle: HandleMpvCommandFromIpcOptions["mpvPlayNextSubtitle"];
mpvSendCommand: HandleMpvCommandFromIpcOptions["mpvSendCommand"];
isMpvConnected: HandleMpvCommandFromIpcOptions["isMpvConnected"];
hasRuntimeOptionsManager: HandleMpvCommandFromIpcOptions["hasRuntimeOptionsManager"];
specialCommands: HandleMpvCommandFromIpcOptions['specialCommands'];
runtimeOptionsCycle: HandleMpvCommandFromIpcOptions['runtimeOptionsCycle'];
triggerSubsyncFromConfig: HandleMpvCommandFromIpcOptions['triggerSubsyncFromConfig'];
openRuntimeOptionsPalette: HandleMpvCommandFromIpcOptions['openRuntimeOptionsPalette'];
showMpvOsd: HandleMpvCommandFromIpcOptions['showMpvOsd'];
mpvReplaySubtitle: HandleMpvCommandFromIpcOptions['mpvReplaySubtitle'];
mpvPlayNextSubtitle: HandleMpvCommandFromIpcOptions['mpvPlayNextSubtitle'];
mpvSendCommand: HandleMpvCommandFromIpcOptions['mpvSendCommand'];
isMpvConnected: HandleMpvCommandFromIpcOptions['isMpvConnected'];
hasRuntimeOptionsManager: HandleMpvCommandFromIpcOptions['hasRuntimeOptionsManager'];
}
export function createMainIpcRuntimeServiceDeps(
@@ -295,8 +287,7 @@ export function createCliCommandRuntimeServiceDeps(
copyCurrentSubtitle: params.mining.copyCurrentSubtitle,
startPendingMultiCopy: params.mining.startPendingMultiCopy,
mineSentenceCard: params.mining.mineSentenceCard,
startPendingMineSentenceMultiple:
params.mining.startPendingMineSentenceMultiple,
startPendingMineSentenceMultiple: params.mining.startPendingMineSentenceMultiple,
updateLastCardFromClipboard: params.mining.updateLastCardFromClipboard,
refreshKnownWords: params.mining.refreshKnownWords,
triggerFieldGrouping: params.mining.triggerFieldGrouping,

View File

@@ -1,6 +1,6 @@
import * as path from "path";
import type { FrequencyDictionaryLookup } from "../types";
import { createFrequencyDictionaryLookup } from "../core/services";
import * as path from 'path';
import type { FrequencyDictionaryLookup } from '../types';
import { createFrequencyDictionaryLookup } from '../core/services';
export interface FrequencyDictionarySearchPathDeps {
getDictionaryRoots: () => string[];
@@ -31,18 +31,14 @@ export function getFrequencyDictionarySearchPaths(
// Root list should include `vendor/jiten_freq_global` in callers.
if (sourcePath && sourcePath.trim()) {
rawSearchPaths.push(sourcePath.trim());
rawSearchPaths.push(path.join(sourcePath.trim(), "frequency-dictionary"));
rawSearchPaths.push(
path.join(sourcePath.trim(), "vendor", "frequency-dictionary"),
);
rawSearchPaths.push(path.join(sourcePath.trim(), 'frequency-dictionary'));
rawSearchPaths.push(path.join(sourcePath.trim(), 'vendor', 'frequency-dictionary'));
}
for (const dictionaryRoot of dictionaryRoots) {
rawSearchPaths.push(dictionaryRoot);
rawSearchPaths.push(path.join(dictionaryRoot, "frequency-dictionary"));
rawSearchPaths.push(
path.join(dictionaryRoot, "vendor", "frequency-dictionary"),
);
rawSearchPaths.push(path.join(dictionaryRoot, 'frequency-dictionary'));
rawSearchPaths.push(path.join(dictionaryRoot, 'vendor', 'frequency-dictionary'));
}
return [...new Set(rawSearchPaths)];
@@ -68,27 +64,23 @@ export async function ensureFrequencyDictionaryLookup(
return;
}
if (!frequencyDictionaryLookupInitialization) {
frequencyDictionaryLookupInitialization =
initializeFrequencyDictionaryLookup(deps)
.then(() => {
frequencyDictionaryLookupInitialized = true;
})
.catch((error) => {
frequencyDictionaryLookupInitialized = true;
deps.log(
`Failed to initialize frequency dictionary: ${String(error)}`,
);
deps.setFrequencyRankLookup(() => null);
});
frequencyDictionaryLookupInitialization = initializeFrequencyDictionaryLookup(deps)
.then(() => {
frequencyDictionaryLookupInitialized = true;
})
.catch((error) => {
frequencyDictionaryLookupInitialized = true;
deps.log(`Failed to initialize frequency dictionary: ${String(error)}`);
deps.setFrequencyRankLookup(() => null);
});
}
await frequencyDictionaryLookupInitialization;
}
export function createFrequencyDictionaryRuntimeService(
deps: FrequencyDictionaryRuntimeDeps,
): { ensureFrequencyDictionaryLookup: () => Promise<void> } {
export function createFrequencyDictionaryRuntimeService(deps: FrequencyDictionaryRuntimeDeps): {
ensureFrequencyDictionaryLookup: () => Promise<void>;
} {
return {
ensureFrequencyDictionaryLookup: () =>
ensureFrequencyDictionaryLookup(deps),
ensureFrequencyDictionaryLookup: () => ensureFrequencyDictionaryLookup(deps),
};
}

View File

@@ -1,15 +1,12 @@
import type { RuntimeOptionApplyResult, RuntimeOptionId } from "../types";
import { handleMpvCommandFromIpc } from "../core/services";
import { createMpvCommandRuntimeServiceDeps } from "./dependencies";
import { SPECIAL_COMMANDS } from "../config";
import type { RuntimeOptionApplyResult, RuntimeOptionId } from '../types';
import { handleMpvCommandFromIpc } from '../core/services';
import { createMpvCommandRuntimeServiceDeps } from './dependencies';
import { SPECIAL_COMMANDS } from '../config';
export interface MpvCommandFromIpcRuntimeDeps {
triggerSubsyncFromConfig: () => void;
openRuntimeOptionsPalette: () => void;
cycleRuntimeOption: (
id: RuntimeOptionId,
direction: 1 | -1,
) => RuntimeOptionApplyResult;
cycleRuntimeOption: (id: RuntimeOptionId, direction: 1 | -1) => RuntimeOptionApplyResult;
showMpvOsd: (text: string) => void;
replayCurrentSubtitle: () => void;
playNextSubtitle: () => void;

View File

@@ -2,8 +2,8 @@ import {
createIpcDepsRuntime,
registerAnkiJimakuIpcRuntime,
registerIpcHandlers,
} from "../core/services";
import { registerAnkiJimakuIpcHandlers } from "../core/services/anki-jimaku-ipc";
} from '../core/services';
import { registerAnkiJimakuIpcHandlers } from '../core/services/anki-jimaku-ipc';
import {
createAnkiJimakuIpcRuntimeServiceDeps,
AnkiJimakuIpcRuntimeServiceDepsParams,
@@ -11,23 +11,16 @@ import {
MainIpcRuntimeServiceDepsParams,
createRuntimeOptionsIpcDeps,
RuntimeOptionsIpcDepsParams,
} from "./dependencies";
} from './dependencies';
export interface RegisterIpcRuntimeServicesParams {
runtimeOptions: RuntimeOptionsIpcDepsParams;
mainDeps: Omit<
MainIpcRuntimeServiceDepsParams,
"setRuntimeOption" | "cycleRuntimeOption"
>;
mainDeps: Omit<MainIpcRuntimeServiceDepsParams, 'setRuntimeOption' | 'cycleRuntimeOption'>;
ankiJimakuDeps: AnkiJimakuIpcRuntimeServiceDepsParams;
}
export function registerMainIpcRuntimeServices(
params: MainIpcRuntimeServiceDepsParams,
): void {
registerIpcHandlers(
createIpcDepsRuntime(createMainIpcRuntimeServiceDeps(params)),
);
export function registerMainIpcRuntimeServices(params: MainIpcRuntimeServiceDepsParams): void {
registerIpcHandlers(createIpcDepsRuntime(createMainIpcRuntimeServiceDeps(params)));
}
export function registerAnkiJimakuIpcRuntimeServices(
@@ -39,9 +32,7 @@ export function registerAnkiJimakuIpcRuntimeServices(
);
}
export function registerIpcRuntimeServices(
params: RegisterIpcRuntimeServicesParams,
): void {
export function registerIpcRuntimeServices(params: RegisterIpcRuntimeServicesParams): void {
const runtimeOptionsIpcDeps = createRuntimeOptionsIpcDeps({
getRuntimeOptionsManager: params.runtimeOptions.getRuntimeOptionsManager,
showMpvOsd: params.runtimeOptions.showMpvOsd,

View File

@@ -1,7 +1,7 @@
import * as path from "path";
import type { JlptLevel } from "../types";
import * as path from 'path';
import type { JlptLevel } from '../types';
import { createJlptVocabularyLookup } from "../core/services";
import { createJlptVocabularyLookup } from '../core/services';
export interface JlptDictionarySearchPathDeps {
getDictionaryRoots: () => string[];
@@ -19,16 +19,14 @@ export interface JlptDictionaryRuntimeDeps {
let jlptDictionaryLookupInitialized = false;
let jlptDictionaryLookupInitialization: Promise<void> | null = null;
export function getJlptDictionarySearchPaths(
deps: JlptDictionarySearchPathDeps,
): string[] {
export function getJlptDictionarySearchPaths(deps: JlptDictionarySearchPathDeps): string[] {
const dictionaryRoots = deps.getDictionaryRoots();
const searchPaths: string[] = [];
for (const dictionaryRoot of dictionaryRoots) {
searchPaths.push(dictionaryRoot);
searchPaths.push(path.join(dictionaryRoot, "vendor", "yomitan-jlpt-vocab"));
searchPaths.push(path.join(dictionaryRoot, "yomitan-jlpt-vocab"));
searchPaths.push(path.join(dictionaryRoot, 'vendor', 'yomitan-jlpt-vocab'));
searchPaths.push(path.join(dictionaryRoot, 'yomitan-jlpt-vocab'));
}
const uniquePaths = new Set<string>(searchPaths);
@@ -46,9 +44,7 @@ export async function initializeJlptDictionaryLookup(
);
}
export async function ensureJlptDictionaryLookup(
deps: JlptDictionaryRuntimeDeps,
): Promise<void> {
export async function ensureJlptDictionaryLookup(deps: JlptDictionaryRuntimeDeps): Promise<void> {
if (!deps.isJlptEnabled()) {
return;
}
@@ -68,9 +64,9 @@ export async function ensureJlptDictionaryLookup(
await jlptDictionaryLookupInitialization;
}
export function createJlptDictionaryRuntimeService(
deps: JlptDictionaryRuntimeDeps,
): { ensureJlptDictionaryLookup: () => Promise<void> } {
export function createJlptDictionaryRuntimeService(deps: JlptDictionaryRuntimeDeps): {
ensureJlptDictionaryLookup: () => Promise<void>;
} {
return {
ensureJlptDictionaryLookup: () => ensureJlptDictionaryLookup(deps),
};

View File

@@ -1,6 +1,6 @@
import { updateCurrentMediaPath } from "../core/services";
import { updateCurrentMediaPath } from '../core/services';
import type { SubtitlePosition } from "../types";
import type { SubtitlePosition } from '../types';
export interface MediaRuntimeDeps {
isRemoteMediaPath: (mediaPath: string) => boolean;
@@ -22,12 +22,10 @@ export interface MediaRuntimeService {
resolveMediaPathForJimaku: (mediaPath: string | null) => string | null;
}
export function createMediaRuntimeService(
deps: MediaRuntimeDeps,
): MediaRuntimeService {
export function createMediaRuntimeService(deps: MediaRuntimeDeps): MediaRuntimeService {
return {
updateCurrentMediaPath(mediaPath: unknown): void {
if (typeof mediaPath !== "string" || !deps.isRemoteMediaPath(mediaPath)) {
if (typeof mediaPath !== 'string' || !deps.isRemoteMediaPath(mediaPath)) {
deps.setCurrentMediaTitle(null);
}
@@ -53,7 +51,7 @@ export function createMediaRuntimeService(
},
updateCurrentMediaTitle(mediaTitle: unknown): void {
if (typeof mediaTitle === "string") {
if (typeof mediaTitle === 'string') {
const sanitized = mediaTitle.trim();
deps.setCurrentMediaTitle(sanitized.length > 0 ? sanitized : null);
return;
@@ -62,9 +60,7 @@ export function createMediaRuntimeService(
},
resolveMediaPathForJimaku(mediaPath: string | null): string | null {
return mediaPath &&
deps.isRemoteMediaPath(mediaPath) &&
deps.getCurrentMediaTitle()
return mediaPath && deps.isRemoteMediaPath(mediaPath) && deps.getCurrentMediaTitle()
? deps.getCurrentMediaTitle()
: mediaPath;
},

View File

@@ -1,7 +1,7 @@
import type { BrowserWindow } from "electron";
import type { BrowserWindow } from 'electron';
type OverlayHostedModal = "runtime-options" | "subsync" | "jimaku";
type OverlayHostLayer = "visible" | "invisible";
type OverlayHostedModal = 'runtime-options' | 'subsync' | 'jimaku';
type OverlayHostLayer = 'visible' | 'invisible';
export interface OverlayWindowResolver {
getMainWindow: () => BrowserWindow | null;
@@ -19,14 +19,9 @@ export interface OverlayModalRuntime {
getRestoreVisibleOverlayOnModalClose: () => Set<OverlayHostedModal>;
}
export function createOverlayModalRuntimeService(
deps: OverlayWindowResolver,
): OverlayModalRuntime {
export function createOverlayModalRuntimeService(deps: OverlayWindowResolver): OverlayModalRuntime {
const restoreVisibleOverlayOnModalClose = new Set<OverlayHostedModal>();
const overlayModalAutoShownLayer = new Map<
OverlayHostedModal,
OverlayHostLayer
>();
const overlayModalAutoShownLayer = new Map<OverlayHostedModal, OverlayHostLayer>();
const getTargetOverlayWindow = (): {
window: BrowserWindow;
@@ -36,21 +31,18 @@ export function createOverlayModalRuntimeService(
const invisibleWindow = deps.getInvisibleWindow();
if (visibleMainWindow && !visibleMainWindow.isDestroyed()) {
return { window: visibleMainWindow, layer: "visible" };
return { window: visibleMainWindow, layer: 'visible' };
}
if (invisibleWindow && !invisibleWindow.isDestroyed()) {
return { window: invisibleWindow, layer: "invisible" };
return { window: invisibleWindow, layer: 'invisible' };
}
return null;
};
const showOverlayWindowForModal = (
window: BrowserWindow,
layer: OverlayHostLayer,
): void => {
if (layer === "invisible" && typeof window.showInactive === "function") {
const showOverlayWindowForModal = (window: BrowserWindow, layer: OverlayHostLayer): void => {
if (layer === 'invisible' && typeof window.showInactive === 'function') {
window.showInactive();
} else {
window.show();
@@ -89,12 +81,8 @@ export function createOverlayModalRuntimeService(
}
if (targetWindow.webContents.isLoading()) {
targetWindow.webContents.once("did-finish-load", () => {
if (
targetWindow &&
!targetWindow.isDestroyed() &&
!targetWindow.webContents.isLoading()
) {
targetWindow.webContents.once('did-finish-load', () => {
if (targetWindow && !targetWindow.isDestroyed() && !targetWindow.webContents.isLoading()) {
sendNow();
}
});
@@ -106,8 +94,8 @@ export function createOverlayModalRuntimeService(
};
const openRuntimeOptionsPalette = (): void => {
sendToActiveOverlayWindow("runtime-options:open", undefined, {
restoreOnModalClose: "runtime-options",
sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
});
};
@@ -122,7 +110,7 @@ export function createOverlayModalRuntimeService(
);
if (shouldKeepLayerVisible) return;
if (layer === "visible") {
if (layer === 'visible') {
const mainWindow = deps.getMainWindow();
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.hide();
@@ -139,8 +127,7 @@ export function createOverlayModalRuntimeService(
sendToActiveOverlayWindow,
openRuntimeOptionsPalette,
handleOverlayModalClosed,
getRestoreVisibleOverlayOnModalClose: () =>
restoreVisibleOverlayOnModalClose,
getRestoreVisibleOverlayOnModalClose: () => restoreVisibleOverlayOnModalClose,
};
}

View File

@@ -1,15 +1,15 @@
import type { ConfiguredShortcuts } from "../core/utils/shortcut-config";
import type { ConfiguredShortcuts } from '../core/utils/shortcut-config';
import {
createOverlayShortcutRuntimeHandlers,
shortcutMatchesInputForLocalFallback,
} from "../core/services";
} from '../core/services';
import {
refreshOverlayShortcutsRuntime,
registerOverlayShortcuts,
syncOverlayShortcutsRuntime,
unregisterOverlayShortcutsRuntime,
} from "../core/services";
import { runOverlayShortcutLocalFallback } from "../core/services/overlay-shortcut-handler";
} from '../core/services';
import { runOverlayShortcutLocalFallback } from '../core/services/overlay-shortcut-handler';
export interface OverlayShortcutRuntimeServiceInput {
getConfiguredShortcuts: () => ConfiguredShortcuts;
@@ -85,13 +85,11 @@ export function createOverlayShortcutsRuntimeService(
getConfiguredShortcuts: () => input.getConfiguredShortcuts(),
getOverlayHandlers: () => handlers.overlayHandlers,
cancelPendingMultiCopy: () => input.cancelPendingMultiCopy(),
cancelPendingMineSentenceMultiple: () =>
input.cancelPendingMineSentenceMultiple(),
cancelPendingMineSentenceMultiple: () => input.cancelPendingMineSentenceMultiple(),
};
};
const shouldOverlayShortcutsBeActive = () =>
input.isOverlayRuntimeInitialized();
const shouldOverlayShortcutsBeActive = () => input.isOverlayRuntimeInitialized();
return {
tryHandleOverlayShortcutLocalFallback: (inputEvent) =>
@@ -103,10 +101,7 @@ export function createOverlayShortcutsRuntimeService(
),
registerOverlayShortcuts: () => {
input.setShortcutsRegistered(
registerOverlayShortcuts(
input.getConfiguredShortcuts(),
handlers.overlayHandlers,
),
registerOverlayShortcuts(input.getConfiguredShortcuts(), handlers.overlayHandlers),
);
},
unregisterOverlayShortcuts: () => {

View File

@@ -1,12 +1,12 @@
import type { BrowserWindow } from "electron";
import type { BrowserWindow } from 'electron';
import type { BaseWindowTracker } from "../window-trackers";
import type { WindowGeometry } from "../types";
import type { BaseWindowTracker } from '../window-trackers';
import type { WindowGeometry } from '../types';
import {
syncInvisibleOverlayMousePassthrough,
updateInvisibleOverlayVisibility,
updateVisibleOverlayVisibility,
} from "../core/services";
} from '../core/services';
export interface OverlayVisibilityRuntimeDeps {
getMainWindow: () => BrowserWindow | null;
@@ -39,7 +39,7 @@ export function createOverlayVisibilityRuntimeService(
const setIgnoreMouseEvents = (
ignore: boolean,
options?: Parameters<BrowserWindow["setIgnoreMouseEvents"]>[1],
options?: Parameters<BrowserWindow['setIgnoreMouseEvents']>[1],
): void => {
const invisibleWindow = deps.getInvisibleWindow();
if (!invisibleWindow || invisibleWindow.isDestroyed()) return;
@@ -58,8 +58,7 @@ export function createOverlayVisibilityRuntimeService(
},
updateVisibleOverlayBounds: (geometry: WindowGeometry) =>
deps.updateVisibleOverlayBounds(geometry),
ensureOverlayWindowLevel: (window: BrowserWindow) =>
deps.ensureOverlayWindowLevel(window),
ensureOverlayWindowLevel: (window: BrowserWindow) => deps.ensureOverlayWindowLevel(window),
enforceOverlayLayerOrder: () => deps.enforceOverlayLayerOrder(),
syncOverlayShortcuts: () => deps.syncOverlayShortcuts(),
});
@@ -73,8 +72,7 @@ export function createOverlayVisibilityRuntimeService(
windowTracker: deps.getWindowTracker(),
updateInvisibleOverlayBounds: (geometry: WindowGeometry) =>
deps.updateInvisibleOverlayBounds(geometry),
ensureOverlayWindowLevel: (window: BrowserWindow) =>
deps.ensureOverlayWindowLevel(window),
ensureOverlayWindowLevel: (window: BrowserWindow) => deps.ensureOverlayWindowLevel(window),
enforceOverlayLayerOrder: () => deps.enforceOverlayLayerOrder(),
syncOverlayShortcuts: () => deps.syncOverlayShortcuts(),
});

View File

@@ -1,11 +1,11 @@
import { CliArgs, CliCommandSource } from "../cli/args";
import { createAppLifecycleDepsRuntime } from "../core/services";
import { startAppLifecycle } from "../core/services/app-lifecycle";
import type { AppLifecycleDepsRuntimeOptions } from "../core/services/app-lifecycle";
import { createAppLifecycleRuntimeDeps } from "./app-lifecycle";
import { CliArgs, CliCommandSource } from '../cli/args';
import { createAppLifecycleDepsRuntime } from '../core/services';
import { startAppLifecycle } from '../core/services/app-lifecycle';
import type { AppLifecycleDepsRuntimeOptions } from '../core/services/app-lifecycle';
import { createAppLifecycleRuntimeDeps } from './app-lifecycle';
export interface AppLifecycleRuntimeRunnerParams {
app: AppLifecycleDepsRuntimeOptions["app"];
app: AppLifecycleDepsRuntimeOptions['app'];
platform: NodeJS.Platform;
shouldStartApp: (args: CliArgs) => boolean;
parseArgs: (argv: string[]) => CliArgs;

View File

@@ -1,7 +1,7 @@
import { CliArgs } from "../cli/args";
import type { ResolvedConfig } from "../types";
import type { StartupBootstrapRuntimeDeps } from "../core/services/startup";
import type { LogLevelSource } from "../logger";
import { CliArgs } from '../cli/args';
import type { ResolvedConfig } from '../types';
import type { StartupBootstrapRuntimeDeps } from '../core/services/startup';
import type { LogLevelSource } from '../logger';
export interface StartupBootstrapRuntimeFactoryDeps {
argv: string[];
@@ -36,8 +36,7 @@ export function createStartupBootstrapRuntimeDeps(
parseArgs: params.parseArgs,
setLogLevel: params.setLogLevel,
forceX11Backend: (args: CliArgs) => params.forceX11Backend(args),
enforceUnsupportedWaylandMode: (args: CliArgs) =>
params.enforceUnsupportedWaylandMode(args),
enforceUnsupportedWaylandMode: (args: CliArgs) => params.enforceUnsupportedWaylandMode(args),
getDefaultSocketPath: params.getDefaultSocketPath,
defaultTexthookerPort: params.defaultTexthookerPort,
runGenerateConfigFlow: (args: CliArgs) => {

View File

@@ -1,4 +1,4 @@
import type { BrowserWindow, Extension } from "electron";
import type { BrowserWindow, Extension } from 'electron';
import type {
Keybinding,
@@ -8,21 +8,21 @@ import type {
KikuFieldGroupingChoice,
JlptLevel,
FrequencyDictionaryLookup,
} from "../types";
import type { CliArgs } from "../cli/args";
import type { SubtitleTimingTracker } from "../subtitle-timing-tracker";
import type { AnkiIntegration } from "../anki-integration";
import type { ImmersionTrackerService } from "../core/services";
import type { MpvIpcClient } from "../core/services";
import type { JellyfinRemoteSessionService } from "../core/services";
import { DEFAULT_MPV_SUBTITLE_RENDER_METRICS } from "../core/services";
import type { RuntimeOptionsManager } from "../runtime-options";
import type { MecabTokenizer } from "../mecab-tokenizer";
import type { BaseWindowTracker } from "../window-trackers";
} from '../types';
import type { CliArgs } from '../cli/args';
import type { SubtitleTimingTracker } from '../subtitle-timing-tracker';
import type { AnkiIntegration } from '../anki-integration';
import type { ImmersionTrackerService } from '../core/services';
import type { MpvIpcClient } from '../core/services';
import type { JellyfinRemoteSessionService } from '../core/services';
import { DEFAULT_MPV_SUBTITLE_RENDER_METRICS } from '../core/services';
import type { RuntimeOptionsManager } from '../runtime-options';
import type { MecabTokenizer } from '../mecab-tokenizer';
import type { BaseWindowTracker } from '../window-trackers';
export interface AnilistSecretResolutionState {
status: "not_checked" | "resolved" | "error";
source: "none" | "literal" | "stored";
status: 'not_checked' | 'resolved' | 'error';
source: 'none' | 'literal' | 'stored';
message: string | null;
resolvedAt: number | null;
errorAt: number | null;
@@ -93,12 +93,12 @@ export interface AppStateInitialValues {
}
export interface StartupState {
initialArgs: Exclude<AppState["initialArgs"], null>;
mpvSocketPath: AppState["mpvSocketPath"];
texthookerPort: AppState["texthookerPort"];
backendOverride: AppState["backendOverride"];
autoStartOverlay: AppState["autoStartOverlay"];
texthookerOnlyMode: AppState["texthookerOnlyMode"];
initialArgs: Exclude<AppState['initialArgs'], null>;
mpvSocketPath: AppState['mpvSocketPath'];
texthookerPort: AppState['texthookerPort'];
backendOverride: AppState['backendOverride'];
autoStartOverlay: AppState['autoStartOverlay'];
texthookerOnlyMode: AppState['texthookerOnlyMode'];
}
export function createAppState(values: AppStateInitialValues): AppState {
@@ -113,16 +113,16 @@ export function createAppState(values: AppStateInitialValues): AppState {
mpvClient: null,
jellyfinRemoteSession: null,
reconnectTimer: null,
currentSubText: "",
currentSubAssText: "",
currentSubText: '',
currentSubAssText: '',
windowTracker: null,
subtitlePosition: null,
currentMediaPath: null,
currentMediaTitle: null,
pendingSubtitlePosition: null,
anilistClientSecretState: {
status: "not_checked",
source: "none",
status: 'not_checked',
source: 'none',
message: null,
resolvedAt: null,
errorAt: null,
@@ -132,7 +132,7 @@ export function createAppState(values: AppStateInitialValues): AppState {
subtitleTimingTracker: null,
immersionTracker: null,
ankiIntegration: null,
secondarySubMode: "hover",
secondarySubMode: 'hover',
lastSecondarySubToggleAtMs: 0,
previousSecondarySubVisibility: null,
mpvSubtitleRenderMetrics: {
@@ -165,10 +165,7 @@ export function createAppState(values: AppStateInitialValues): AppState {
};
}
export function applyStartupState(
appState: AppState,
startupState: StartupState,
): void {
export function applyStartupState(appState: AppState, startupState: StartupState): void {
appState.initialArgs = startupState.initialArgs;
appState.mpvSocketPath = startupState.mpvSocketPath;
appState.texthookerPort = startupState.texthookerPort;

View File

@@ -1,32 +1,28 @@
import { SubsyncResolvedConfig } from "../subsync/utils";
import type {
SubsyncManualPayload,
SubsyncManualRunRequest,
SubsyncResult,
} from "../types";
import type { SubsyncRuntimeDeps } from "../core/services/subsync-runner";
import { createSubsyncRuntimeDeps } from "./dependencies";
import { SubsyncResolvedConfig } from '../subsync/utils';
import type { SubsyncManualPayload, SubsyncManualRunRequest, SubsyncResult } from '../types';
import type { SubsyncRuntimeDeps } from '../core/services/subsync-runner';
import { createSubsyncRuntimeDeps } from './dependencies';
import {
runSubsyncManualFromIpcRuntime as runSubsyncManualFromIpcRuntimeCore,
triggerSubsyncFromConfigRuntime as triggerSubsyncFromConfigRuntimeCore,
} from "../core/services";
} from '../core/services';
export interface SubsyncRuntimeServiceInput {
getMpvClient: SubsyncRuntimeDeps["getMpvClient"];
getMpvClient: SubsyncRuntimeDeps['getMpvClient'];
getResolvedSubsyncConfig: () => SubsyncResolvedConfig;
isSubsyncInProgress: SubsyncRuntimeDeps["isSubsyncInProgress"];
setSubsyncInProgress: SubsyncRuntimeDeps["setSubsyncInProgress"];
showMpvOsd: SubsyncRuntimeDeps["showMpvOsd"];
isSubsyncInProgress: SubsyncRuntimeDeps['isSubsyncInProgress'];
setSubsyncInProgress: SubsyncRuntimeDeps['setSubsyncInProgress'];
showMpvOsd: SubsyncRuntimeDeps['showMpvOsd'];
openManualPicker: (payload: SubsyncManualPayload) => void;
}
export interface SubsyncRuntimeServiceStateInput {
getMpvClient: SubsyncRuntimeServiceInput["getMpvClient"];
getResolvedSubsyncConfig: SubsyncRuntimeServiceInput["getResolvedSubsyncConfig"];
getMpvClient: SubsyncRuntimeServiceInput['getMpvClient'];
getResolvedSubsyncConfig: SubsyncRuntimeServiceInput['getResolvedSubsyncConfig'];
getSubsyncInProgress: () => boolean;
setSubsyncInProgress: SubsyncRuntimeServiceInput["setSubsyncInProgress"];
showMpvOsd: SubsyncRuntimeServiceInput["showMpvOsd"];
openManualPicker: SubsyncRuntimeServiceInput["openManualPicker"];
setSubsyncInProgress: SubsyncRuntimeServiceInput['setSubsyncInProgress'];
showMpvOsd: SubsyncRuntimeServiceInput['showMpvOsd'];
openManualPicker: SubsyncRuntimeServiceInput['openManualPicker'];
}
export function createSubsyncRuntimeServiceInputFromState(
@@ -55,20 +51,13 @@ export function createSubsyncRuntimeServiceDeps(
});
}
export function triggerSubsyncFromConfigRuntime(
params: SubsyncRuntimeServiceInput,
): Promise<void> {
return triggerSubsyncFromConfigRuntimeCore(
createSubsyncRuntimeServiceDeps(params),
);
export function triggerSubsyncFromConfigRuntime(params: SubsyncRuntimeServiceInput): Promise<void> {
return triggerSubsyncFromConfigRuntimeCore(createSubsyncRuntimeServiceDeps(params));
}
export async function runSubsyncManualFromIpcRuntime(
request: SubsyncManualRunRequest,
params: SubsyncRuntimeServiceInput,
): Promise<SubsyncResult> {
return runSubsyncManualFromIpcRuntimeCore(
request,
createSubsyncRuntimeServiceDeps(params),
);
return runSubsyncManualFromIpcRuntimeCore(request, createSubsyncRuntimeServiceDeps(params));
}