run prettier

This commit is contained in:
2026-02-28 21:15:22 -08:00
parent e4038127cb
commit cbff3f9ad9
146 changed files with 891 additions and 584 deletions

View File

@@ -132,16 +132,15 @@ export function createAnilistTokenStore(
}
return decrypted;
}
if (
typeof parsed.plaintextToken === 'string' &&
parsed.plaintextToken.trim().length > 0
) {
if (typeof parsed.plaintextToken === 'string' && parsed.plaintextToken.trim().length > 0) {
if (storage.isEncryptionAvailable()) {
if (!isSafeStorageUsable()) {
return null;
}
const plaintext = parsed.plaintextToken.trim();
notifyUser('AniList token plaintext fallback payload found. Migrating to encrypted storage.');
notifyUser(
'AniList token plaintext fallback payload found. Migrating to encrypted storage.',
);
this.saveToken(plaintext);
return plaintext;
}

View File

@@ -283,8 +283,7 @@ export function handleCliCommand(
return;
}
const shouldStart =
args.start || args.toggle || args.toggleVisibleOverlay;
const shouldStart = args.start || args.toggle || args.toggleVisibleOverlay;
const needsOverlayRuntime = commandNeedsOverlayRuntime(args);
const shouldInitializeOverlayRuntime = needsOverlayRuntime || args.start;

View File

@@ -71,7 +71,8 @@ test('createFrequencyDictionaryLookup aggregates duplicate-term logs into a sing
assert.equal(lookup('猫'), 100);
assert.equal(
logs.filter((entry) => entry.includes('Frequency dictionary ignored 2 duplicate term entries')).length,
logs.filter((entry) => entry.includes('Frequency dictionary ignored 2 duplicate term entries'))
.length,
1,
);
assert.equal(

View File

@@ -32,7 +32,7 @@ function parsePositiveFrequencyString(value: string): number | null {
const chunks = numericPrefix.split(',');
const normalizedNumber =
chunks.length <= 1
? chunks[0] ?? ''
? (chunks[0] ?? '')
: chunks.slice(1).every((chunk) => /^\d{3}$/.test(chunk))
? chunks.join('')
: (chunks[0] ?? '');

View File

@@ -62,10 +62,7 @@ export {
updateOverlayWindowBounds,
} from './overlay-window';
export { initializeOverlayRuntime } from './overlay-runtime-init';
export {
setVisibleOverlayVisible,
updateVisibleOverlayVisibility,
} from './overlay-visibility';
export { setVisibleOverlayVisible, updateVisibleOverlayVisibility } from './overlay-visibility';
export {
MPV_REQUEST_ID_SECONDARY_SUB_VISIBILITY,
MpvIpcClient,

View File

@@ -150,7 +150,12 @@ test('registerIpcHandlers rejects malformed runtime-option payloads', async () =
});
const validResult = await setHandler!({}, 'anki.autoUpdateNewCards', true);
assert.deepEqual(validResult, { ok: true });
assert.deepEqual(calls, [{ id: 'anki.autoUpdateNewCards', value: true }]);
const validSubtitleAnnotationResult = await setHandler!({}, 'subtitle.annotation.jlpt', false);
assert.deepEqual(validSubtitleAnnotationResult, { ok: true });
assert.deepEqual(calls, [
{ id: 'anki.autoUpdateNewCards', value: true },
{ id: 'subtitle.annotation.jlpt', value: false },
]);
const cycleHandler = handlers.handle.get(IPC_CHANNELS.request.cycleRuntimeOption);
assert.ok(cycleHandler);
@@ -215,9 +220,7 @@ test('registerIpcHandlers ignores malformed fire-and-forget payloads', () => {
handlers.on.get(IPC_CHANNELS.command.saveSubtitlePosition)!({}, { yPercent: 'bad' });
handlers.on.get(IPC_CHANNELS.command.saveSubtitlePosition)!({}, { yPercent: 42 });
assert.deepEqual(saves, [
{ yPercent: 42 },
]);
assert.deepEqual(saves, [{ yPercent: 42 }]);
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'not-a-modal');
handlers.on.get(IPC_CHANNELS.command.overlayModalClosed)!({}, 'subsync');

View File

@@ -62,7 +62,8 @@ export function createJellyfinTokenStore(
}
const decrypted = safeStorage.decryptString(encrypted).trim();
const session = JSON.parse(decrypted) as Partial<JellyfinStoredSession>;
const accessToken = typeof session.accessToken === 'string' ? session.accessToken.trim() : '';
const accessToken =
typeof session.accessToken === 'string' ? session.accessToken.trim() : '';
const userId = typeof session.userId === 'string' ? session.userId.trim() : '';
if (!accessToken || !userId) return null;
return { accessToken, userId };
@@ -88,7 +89,9 @@ export function createJellyfinTokenStore(
(typeof parsed.encryptedToken === 'string' && parsed.encryptedToken.length > 0) ||
(typeof parsed.plaintextToken === 'string' && parsed.plaintextToken.trim().length > 0)
) {
logger.warn('Ignoring legacy Jellyfin token-only store payload because userId is missing.');
logger.warn(
'Ignoring legacy Jellyfin token-only store payload because userId is missing.',
);
}
} catch (error) {
logger.error('Failed to read Jellyfin session store.', error);

View File

@@ -34,10 +34,7 @@ export function sendToVisibleOverlayRuntime<T extends string>(options: {
const isLoading = typeof webContents.isLoading === 'function' ? webContents.isLoading() : false;
const currentURL = typeof webContents.getURL === 'function' ? webContents.getURL() : '';
const isReady =
!isLoading &&
currentURL !== '' &&
currentURL !== 'about:blank';
const isReady = !isLoading && currentURL !== '' && currentURL !== 'about:blank';
if (!isReady) {
if (typeof webContents.once !== 'function') {

View File

@@ -85,7 +85,9 @@ export function parseClipboardVideoPath(text: string): string | null {
return isSupportedVideoPath(unquoted) ? unquoted : null;
}
export function collectDroppedVideoPaths(dataTransfer: DropDataTransferLike | null | undefined): string[] {
export function collectDroppedVideoPaths(
dataTransfer: DropDataTransferLike | null | undefined,
): string[] {
if (!dataTransfer) return [];
const out: string[] = [];

View File

@@ -117,13 +117,9 @@ test('runtime-option broadcast still uses expected channel', () => {
},
);
let state = false;
const changed = setOverlayDebugVisualizationEnabledRuntime(
state,
true,
(enabled) => {
state = enabled;
},
);
const changed = setOverlayDebugVisualizationEnabledRuntime(state, true, (enabled) => {
state = enabled;
});
assert.equal(changed, true);
assert.equal(state, true);
assert.deepEqual(broadcasts, [['runtime-options:changed', []]]);

View File

@@ -41,13 +41,12 @@ test('initializeOverlayRuntime skips Anki integration when ankiConnect.enabled i
setIntegrationCalls += 1;
},
showDesktopNotification: () => {},
createFieldGroupingCallback: () =>
async () => ({
keepNoteId: 1,
deleteNoteId: 2,
deleteDuplicate: false,
cancelled: false,
}),
createFieldGroupingCallback: () => async () => ({
keepNoteId: 1,
deleteNoteId: 2,
deleteDuplicate: false,
cancelled: false,
}),
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
});
@@ -96,13 +95,12 @@ test('initializeOverlayRuntime starts Anki integration when ankiConnect.enabled
setIntegrationCalls += 1;
},
showDesktopNotification: () => {},
createFieldGroupingCallback: () =>
async () => ({
keepNoteId: 3,
deleteNoteId: 4,
deleteDuplicate: false,
cancelled: false,
}),
createFieldGroupingCallback: () => async () => ({
keepNoteId: 3,
deleteNoteId: 4,
deleteDuplicate: false,
cancelled: false,
}),
getKnownWordCacheStatePath: () => '/tmp/known-words-cache.json',
});

View File

@@ -23,7 +23,8 @@ type CreateAnkiIntegrationArgs = {
};
function createDefaultAnkiIntegration(args: CreateAnkiIntegrationArgs): AnkiIntegrationLike {
const { AnkiIntegration } = require('../../anki-integration') as typeof import('../../anki-integration');
const { AnkiIntegration } =
require('../../anki-integration') as typeof import('../../anki-integration');
return new AnkiIntegration(
args.config,
args.subtitleTimingTracker as never,
@@ -76,7 +77,10 @@ export function initializeOverlayRuntime(options: {
options.registerGlobalShortcuts();
const createWindowTrackerHandler = options.createWindowTracker ?? createWindowTracker;
const windowTracker = createWindowTrackerHandler(options.backendOverride, options.getMpvSocketPath());
const windowTracker = createWindowTrackerHandler(
options.backendOverride,
options.getMpvSocketPath(),
);
options.setWindowTracker(windowTracker);
if (windowTracker) {
windowTracker.onGeometryChange = (geometry: WindowGeometry) => {

View File

@@ -138,3 +138,35 @@ test('subtitle processing refresh can use explicit text override', async () => {
assert.deepEqual(emitted, [{ text: 'initial', tokens: [] }]);
});
test('subtitle processing cache invalidation only affects future subtitle events', async () => {
const emitted: SubtitleData[] = [];
const callsByText = new Map<string, number>();
const controller = createSubtitleProcessingController({
tokenizeSubtitle: async (text) => {
callsByText.set(text, (callsByText.get(text) ?? 0) + 1);
return { text, tokens: [] };
},
emitSubtitle: (payload) => emitted.push(payload),
});
controller.onSubtitleChange('same');
await flushMicrotasks();
controller.onSubtitleChange('other');
await flushMicrotasks();
controller.onSubtitleChange('same');
await flushMicrotasks();
assert.equal(callsByText.get('same'), 1);
assert.equal(emitted.length, 3);
controller.invalidateTokenizationCache();
assert.equal(emitted.length, 3);
controller.onSubtitleChange('different');
await flushMicrotasks();
controller.onSubtitleChange('same');
await flushMicrotasks();
assert.equal(callsByText.get('same'), 2);
});

View File

@@ -10,6 +10,7 @@ export interface SubtitleProcessingControllerDeps {
export interface SubtitleProcessingController {
onSubtitleChange: (text: string) => void;
refreshCurrentSubtitle: (textOverride?: string) => void;
invalidateTokenizationCache: () => void;
}
export function createSubtitleProcessingController(
@@ -126,5 +127,8 @@ export function createSubtitleProcessingController(
refreshRequested = true;
processLatest();
},
invalidateTokenizationCache: () => {
tokenizationCache.clear();
},
};
}

View File

@@ -52,7 +52,12 @@ test('annotateTokens known-word match mode uses headword vs surface', () => {
test('annotateTokens excludes frequency for particle/bound_auxiliary and pos1 exclusions', () => {
const tokens = [
makeToken({ surface: 'は', headword: 'は', partOfSpeech: PartOfSpeech.particle, frequencyRank: 3 }),
makeToken({
surface: 'は',
headword: 'は',
partOfSpeech: PartOfSpeech.particle,
frequencyRank: 3,
}),
makeToken({
surface: 'です',
headword: 'です',

View File

@@ -7,12 +7,7 @@ import {
DEFAULT_ANNOTATION_POS2_EXCLUSION_CONFIG,
resolveAnnotationPos2ExclusionSet,
} from '../../../token-pos2-exclusions';
import {
JlptLevel,
MergedToken,
NPlusOneMatchMode,
PartOfSpeech,
} from '../../../types';
import { JlptLevel, MergedToken, NPlusOneMatchMode, PartOfSpeech } from '../../../types';
import { shouldIgnoreJlptByTerm, shouldIgnoreJlptForMecabPos1 } from '../jlpt-token-filter';
const KATAKANA_TO_HIRAGANA_OFFSET = 0x60;
@@ -67,10 +62,7 @@ function normalizePos1Tag(pos1: string | undefined): string {
return typeof pos1 === 'string' ? pos1.trim() : '';
}
function isExcludedByTagSet(
normalizedTag: string,
exclusions: ReadonlySet<string>,
): boolean {
function isExcludedByTagSet(normalizedTag: string, exclusions: ReadonlySet<string>): boolean {
if (!normalizedTag) {
return false;
}
@@ -350,10 +342,7 @@ function isLikelyFrequencyNoiseToken(token: MergedToken): boolean {
continue;
}
if (
shouldIgnoreJlptByTerm(trimmedCandidate) ||
shouldIgnoreJlptByTerm(normalizedCandidate)
) {
if (shouldIgnoreJlptByTerm(trimmedCandidate) || shouldIgnoreJlptByTerm(normalizedCandidate)) {
return true;
}
@@ -442,13 +431,12 @@ export function annotateTokens(
}));
const frequencyEnabled = options.frequencyEnabled !== false;
const frequencyMarkedTokens =
frequencyEnabled
? applyFrequencyMarking(knownMarkedTokens, pos1Exclusions, pos2Exclusions)
: knownMarkedTokens.map((token) => ({
...token,
frequencyRank: undefined,
}));
const frequencyMarkedTokens = frequencyEnabled
? applyFrequencyMarking(knownMarkedTokens, pos1Exclusions, pos2Exclusions)
: knownMarkedTokens.map((token) => ({
...token,
frequencyRank: undefined,
}));
const jlptEnabled = options.jlptEnabled !== false;
const jlptMarkedTokens = jlptEnabled

View File

@@ -116,7 +116,9 @@ class ParserEnrichmentWorkerRuntime {
}
private handleWorkerFailure(error: Error): void {
logger.debug(`Parser enrichment worker unavailable, falling back to main thread: ${error.message}`);
logger.debug(
`Parser enrichment worker unavailable, falling back to main thread: ${error.message}`,
);
for (const pending of this.pending.values()) {
pending.reject(error);
}