mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-04 00:41:33 -07:00
feat: open texthooker from cli and tray
This commit is contained in:
@@ -28,6 +28,7 @@ function createContext(): LauncherCommandContext {
|
||||
useTexthooker: false,
|
||||
autoStartOverlay: false,
|
||||
texthookerOnly: false,
|
||||
texthookerOpenBrowser: false,
|
||||
useRofi: false,
|
||||
logLevel: 'info',
|
||||
passwordStore: '',
|
||||
|
||||
@@ -144,6 +144,7 @@ test('applyInvocationsToArgs maps config and jellyfin invocation state', () => {
|
||||
doctorRefreshKnownWords: false,
|
||||
texthookerTriggered: false,
|
||||
texthookerLogLevel: null,
|
||||
texthookerOpenBrowser: false,
|
||||
});
|
||||
|
||||
assert.equal(parsed.jellyfin, false);
|
||||
@@ -157,3 +158,36 @@ test('applyInvocationsToArgs maps config and jellyfin invocation state', () => {
|
||||
assert.equal(parsed.configShow, true);
|
||||
assert.equal(parsed.logLevel, 'warn');
|
||||
});
|
||||
|
||||
test('applyInvocationsToArgs maps texthooker browser-open request', () => {
|
||||
const parsed = createDefaultArgs({});
|
||||
|
||||
applyInvocationsToArgs(parsed, {
|
||||
jellyfinInvocation: null,
|
||||
configInvocation: null,
|
||||
mpvInvocation: null,
|
||||
appInvocation: null,
|
||||
dictionaryTriggered: false,
|
||||
dictionaryTarget: null,
|
||||
dictionaryLogLevel: null,
|
||||
dictionaryCandidates: false,
|
||||
dictionarySelect: false,
|
||||
dictionaryAnilistId: null,
|
||||
statsTriggered: false,
|
||||
statsBackground: false,
|
||||
statsStop: false,
|
||||
statsCleanup: false,
|
||||
statsCleanupVocab: false,
|
||||
statsCleanupLifetime: false,
|
||||
statsLogLevel: null,
|
||||
doctorTriggered: false,
|
||||
doctorLogLevel: null,
|
||||
doctorRefreshKnownWords: false,
|
||||
texthookerTriggered: true,
|
||||
texthookerLogLevel: null,
|
||||
texthookerOpenBrowser: true,
|
||||
});
|
||||
|
||||
assert.equal(parsed.texthookerOnly, true);
|
||||
assert.equal(parsed.texthookerOpenBrowser, true);
|
||||
});
|
||||
|
||||
@@ -184,6 +184,7 @@ export function createDefaultArgs(
|
||||
useTexthooker: true,
|
||||
autoStartOverlay: false,
|
||||
texthookerOnly: false,
|
||||
texthookerOpenBrowser: false,
|
||||
useRofi: false,
|
||||
logLevel: 'info',
|
||||
passwordStore: '',
|
||||
@@ -247,6 +248,7 @@ export function applyInvocationsToArgs(parsed: Args, invocations: CliInvocations
|
||||
if (invocations.doctorTriggered) parsed.doctor = true;
|
||||
if (invocations.doctorRefreshKnownWords) parsed.doctorRefreshKnownWords = true;
|
||||
if (invocations.texthookerTriggered) parsed.texthookerOnly = true;
|
||||
if (invocations.texthookerOpenBrowser) parsed.texthookerOpenBrowser = true;
|
||||
|
||||
if (invocations.jellyfinInvocation) {
|
||||
if (invocations.jellyfinInvocation.logLevel) {
|
||||
|
||||
@@ -35,3 +35,10 @@ test('parseCliPrograms routes app alias arguments through passthrough mode', ()
|
||||
appArgs: ['--anilist', '--log-level', 'debug'],
|
||||
});
|
||||
});
|
||||
|
||||
test('parseCliPrograms captures texthooker browser-open flag', () => {
|
||||
const result = parseCliPrograms(['texthooker', '-o'], 'subminer');
|
||||
|
||||
assert.equal(result.invocations.texthookerTriggered, true);
|
||||
assert.equal(result.invocations.texthookerOpenBrowser, true);
|
||||
});
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface CliInvocations {
|
||||
doctorRefreshKnownWords: boolean;
|
||||
texthookerTriggered: boolean;
|
||||
texthookerLogLevel: string | null;
|
||||
texthookerOpenBrowser: boolean;
|
||||
}
|
||||
|
||||
function applyRootOptions(program: Command): void {
|
||||
@@ -152,6 +153,7 @@ export function parseCliPrograms(
|
||||
let doctorLogLevel: string | null = null;
|
||||
let doctorRefreshKnownWords = false;
|
||||
let texthookerLogLevel: string | null = null;
|
||||
let texthookerOpenBrowser = false;
|
||||
let doctorTriggered = false;
|
||||
let texthookerTriggered = false;
|
||||
|
||||
@@ -313,10 +315,12 @@ export function parseCliPrograms(
|
||||
commandProgram
|
||||
.command('texthooker')
|
||||
.description('Launch texthooker-only mode')
|
||||
.option('-o, --open-browser', 'Open texthooker in the default browser')
|
||||
.option('--log-level <level>', 'Log level')
|
||||
.action((options: Record<string, unknown>) => {
|
||||
texthookerTriggered = true;
|
||||
texthookerLogLevel = typeof options.logLevel === 'string' ? options.logLevel : null;
|
||||
texthookerOpenBrowser = options.openBrowser === true;
|
||||
});
|
||||
|
||||
commandProgram
|
||||
@@ -369,6 +373,7 @@ export function parseCliPrograms(
|
||||
doctorRefreshKnownWords,
|
||||
texthookerTriggered,
|
||||
texthookerLogLevel,
|
||||
texthookerOpenBrowser,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -270,6 +270,29 @@ test('launchTexthookerOnly exits non-zero when app binary cannot be spawned', ()
|
||||
assert.equal(error.code, 1);
|
||||
});
|
||||
|
||||
test('launchTexthookerOnly forwards browser-open request to app command', () => {
|
||||
const { dir } = createTempSocketPath();
|
||||
const appPath = path.join(dir, 'fake-subminer.sh');
|
||||
const argsPath = path.join(dir, 'args.txt');
|
||||
const openedUrls: string[] = [];
|
||||
fs.writeFileSync(appPath, `#!/bin/sh\nprintf '%s\\n' "$@" > "${argsPath}"\nexit 0\n`);
|
||||
fs.chmodSync(appPath, 0o755);
|
||||
|
||||
const error = withProcessExitIntercept(() => {
|
||||
launchTexthookerOnly(appPath, makeArgs({ logLevel: 'info', texthookerOpenBrowser: true }), {
|
||||
openBrowser: (url) => openedUrls.push(url),
|
||||
});
|
||||
});
|
||||
|
||||
assert.equal(error.code, 0);
|
||||
assert.deepEqual(fs.readFileSync(argsPath, 'utf8').trim().split('\n'), [
|
||||
'--texthooker',
|
||||
'--open-browser',
|
||||
]);
|
||||
assert.deepEqual(openedUrls, ['http://127.0.0.1:5174']);
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('launchAppCommandDetached handles child process spawn errors', async () => {
|
||||
let uncaughtError: Error | null = null;
|
||||
const onUncaughtException = (error: Error) => {
|
||||
@@ -399,6 +422,7 @@ function makeArgs(overrides: Partial<Args> = {}): Args {
|
||||
useTexthooker: false,
|
||||
autoStartOverlay: false,
|
||||
texthookerOnly: false,
|
||||
texthookerOpenBrowser: false,
|
||||
useRofi: false,
|
||||
logLevel: 'error',
|
||||
passwordStore: '',
|
||||
|
||||
+30
-1
@@ -831,8 +831,30 @@ export async function startOverlay(
|
||||
}
|
||||
}
|
||||
|
||||
export function launchTexthookerOnly(appPath: string, args: Args): never {
|
||||
export function openUrlInDefaultBrowser(url: string, logLevel: LogLevel): void {
|
||||
const target =
|
||||
process.platform === 'darwin'
|
||||
? { command: 'open', args: [url] }
|
||||
: process.platform === 'win32'
|
||||
? { command: 'cmd', args: ['/c', 'start', '', url] }
|
||||
: { command: 'xdg-open', args: [url] };
|
||||
const result = spawnSync(target.command, target.args, {
|
||||
stdio: 'ignore',
|
||||
env: process.env,
|
||||
windowsHide: true,
|
||||
});
|
||||
if (result.error) {
|
||||
log('warn', logLevel, `Failed to open browser for ${url}: ${result.error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function launchTexthookerOnly(
|
||||
appPath: string,
|
||||
args: Args,
|
||||
deps: { openBrowser?: (url: string) => void } = {},
|
||||
): never {
|
||||
const overlayArgs = ['--texthooker'];
|
||||
if (args.texthookerOpenBrowser) overlayArgs.push('--open-browser');
|
||||
if (args.logLevel !== 'info') overlayArgs.push('--log-level', args.logLevel);
|
||||
|
||||
log('info', args.logLevel, 'Launching texthooker mode...');
|
||||
@@ -840,6 +862,13 @@ export function launchTexthookerOnly(appPath: string, args: Args): never {
|
||||
if (result.error) {
|
||||
fail(`Failed to launch texthooker mode: ${result.error.message}`);
|
||||
}
|
||||
if (args.texthookerOpenBrowser && (result.status ?? 0) === 0) {
|
||||
const url = 'http://127.0.0.1:5174';
|
||||
const openBrowser =
|
||||
deps.openBrowser ??
|
||||
((browserUrl: string) => openUrlInDefaultBrowser(browserUrl, args.logLevel));
|
||||
openBrowser(url);
|
||||
}
|
||||
process.exit(result.status ?? 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ export interface Args {
|
||||
useTexthooker: boolean;
|
||||
autoStartOverlay: boolean;
|
||||
texthookerOnly: boolean;
|
||||
texthookerOpenBrowser: boolean;
|
||||
useRofi: boolean;
|
||||
logLevel: LogLevel;
|
||||
passwordStore: string;
|
||||
|
||||
Reference in New Issue
Block a user