mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-04 00:41:33 -07:00
fix: refresh current subtitle after known-word mining
This commit is contained in:
@@ -177,6 +177,44 @@ test('AnkiIntegration.refreshKnownWordCache skips work when highlight mode is di
|
||||
}
|
||||
});
|
||||
|
||||
test('AnkiIntegration notifies when mined note info updates known words', () => {
|
||||
const ctx = createIntegrationTestContext({
|
||||
stateDirPrefix: 'subminer-anki-integration-known-update-',
|
||||
});
|
||||
let notifications = 0;
|
||||
|
||||
try {
|
||||
const integrationState = ctx.integration as unknown as {
|
||||
config: AnkiConnectConfig;
|
||||
appendKnownWordsFromNoteInfo: (noteInfo: {
|
||||
noteId: number;
|
||||
fields: Record<string, { value: string }>;
|
||||
}) => void;
|
||||
};
|
||||
integrationState.config.deck = 'Mining';
|
||||
integrationState.config.knownWords = {
|
||||
...integrationState.config.knownWords,
|
||||
decks: {
|
||||
Mining: ['Word'],
|
||||
},
|
||||
};
|
||||
ctx.integration.setKnownWordCacheUpdatedCallback(() => {
|
||||
notifications += 1;
|
||||
});
|
||||
integrationState.appendKnownWordsFromNoteInfo({
|
||||
noteId: 42,
|
||||
fields: {
|
||||
Word: { value: '食べる' },
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(ctx.integration.isKnownWord('食べる'), true);
|
||||
assert.equal(notifications, 1);
|
||||
} finally {
|
||||
cleanupIntegrationTestContext(ctx);
|
||||
}
|
||||
});
|
||||
|
||||
test('AnkiIntegration.refreshKnownWordCache deduplicates concurrent refreshes', async () => {
|
||||
let releaseFindNotes: (() => void) | undefined;
|
||||
const findNotesPromise = new Promise<void>((resolve) => {
|
||||
|
||||
+21
-1
@@ -148,6 +148,7 @@ export class AnkiIntegration {
|
||||
private runtime: AnkiIntegrationRuntime;
|
||||
private aiConfig: AiConfig;
|
||||
private recordCardsMinedCallback: ((count: number, noteIds?: number[]) => void) | null = null;
|
||||
private knownWordCacheUpdatedCallback: (() => void) | null = null;
|
||||
private noteIdRedirects = new Map<number, number>();
|
||||
private trackedDuplicateNoteIds = new Map<number, number[]>();
|
||||
|
||||
@@ -552,10 +553,25 @@ export class AnkiIntegration {
|
||||
return;
|
||||
}
|
||||
|
||||
this.knownWordCache.appendFromNoteInfo({
|
||||
const changed = this.knownWordCache.appendFromNoteInfo({
|
||||
noteId: noteInfo.noteId,
|
||||
fields: noteInfo.fields,
|
||||
});
|
||||
if (changed) {
|
||||
this.notifyKnownWordCacheUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
private notifyKnownWordCacheUpdated(): void {
|
||||
if (!this.knownWordCacheUpdatedCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.knownWordCacheUpdatedCallback();
|
||||
} catch (error) {
|
||||
log.warn('Known-word cache update callback failed:', (error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
private getLapisConfig(): {
|
||||
@@ -1267,6 +1283,10 @@ export class AnkiIntegration {
|
||||
this.recordCardsMinedCallback = callback;
|
||||
}
|
||||
|
||||
setKnownWordCacheUpdatedCallback(callback: (() => void) | null): void {
|
||||
this.knownWordCacheUpdatedCallback = callback;
|
||||
}
|
||||
|
||||
resolveCurrentNoteId(noteId: number): number {
|
||||
let resolved = noteId;
|
||||
const seen = new Set<number>();
|
||||
|
||||
@@ -165,9 +165,9 @@ export class KnownWordCacheManager {
|
||||
}
|
||||
}
|
||||
|
||||
appendFromNoteInfo(noteInfo: KnownWordCacheNoteInfo): void {
|
||||
appendFromNoteInfo(noteInfo: KnownWordCacheNoteInfo): boolean {
|
||||
if (!this.isKnownWordCacheEnabled() || !this.shouldAddMinedWordsImmediately()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentStateKey = this.getKnownWordCacheStateKey();
|
||||
@@ -180,13 +180,13 @@ export class KnownWordCacheManager {
|
||||
|
||||
const preferredFields = this.getImmediateAppendFields();
|
||||
if (!preferredFields) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const nextWords = this.extractNormalizedKnownWordsFromNoteInfo(noteInfo, preferredFields);
|
||||
const changed = this.replaceNoteSnapshot(noteInfo.noteId, nextWords);
|
||||
if (!changed) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.knownWordsLastRefreshedAtMs <= 0) {
|
||||
@@ -199,6 +199,7 @@ export class KnownWordCacheManager {
|
||||
`wordCount=${nextWords.length}`,
|
||||
`scope=${getKnownWordCacheScopeForConfig(this.deps.getConfig())}`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
clearKnownWordCacheState(): void {
|
||||
|
||||
+11
-3
@@ -1407,9 +1407,8 @@ const subtitleProcessingController = createSubtitleProcessingController(
|
||||
let subtitlePrefetchService: SubtitlePrefetchService | null = null;
|
||||
let subtitlePrefetchRefreshTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let lastObservedTimePos = 0;
|
||||
let cancelLinuxMpvFullscreenOverlayRefreshBurst:
|
||||
| CancelLinuxMpvFullscreenOverlayRefreshBurst
|
||||
| null = null;
|
||||
let cancelLinuxMpvFullscreenOverlayRefreshBurst: CancelLinuxMpvFullscreenOverlayRefreshBurst | null =
|
||||
null;
|
||||
const SEEK_THRESHOLD_SECONDS = 3;
|
||||
|
||||
function clearScheduledSubtitlePrefetchRefresh(): void {
|
||||
@@ -3439,6 +3438,9 @@ const recordTrackedCardsMined = (count: number, noteIds?: number[]): void => {
|
||||
ensureImmersionTrackerStarted();
|
||||
appState.immersionTracker?.recordCardsMined(count, noteIds);
|
||||
};
|
||||
const refreshCurrentSubtitleAfterKnownWordUpdate = (): void => {
|
||||
subtitleProcessingController.refreshCurrentSubtitle(appState.currentSubText);
|
||||
};
|
||||
let hasAttemptedImmersionTrackerStartup = false;
|
||||
const ensureImmersionTrackerStarted = (): void => {
|
||||
if (hasAttemptedImmersionTrackerStartup || appState.immersionTracker) {
|
||||
@@ -4264,6 +4266,9 @@ function destroyTray(): void {
|
||||
function initializeOverlayRuntime(): void {
|
||||
initializeOverlayRuntimeHandler();
|
||||
appState.ankiIntegration?.setRecordCardsMinedCallback(recordTrackedCardsMined);
|
||||
appState.ankiIntegration?.setKnownWordCacheUpdatedCallback(
|
||||
refreshCurrentSubtitleAfterKnownWordUpdate,
|
||||
);
|
||||
syncOverlayMpvSubtitleSuppression();
|
||||
}
|
||||
|
||||
@@ -4970,6 +4975,9 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
|
||||
setAnkiIntegration: (integration: AnkiIntegration | null) => {
|
||||
appState.ankiIntegration = integration;
|
||||
appState.ankiIntegration?.setRecordCardsMinedCallback(recordTrackedCardsMined);
|
||||
appState.ankiIntegration?.setKnownWordCacheUpdatedCallback(
|
||||
refreshCurrentSubtitleAfterKnownWordUpdate,
|
||||
);
|
||||
},
|
||||
getKnownWordCacheStatePath: () => path.join(USER_DATA_PATH, 'known-words-cache.json'),
|
||||
showDesktopNotification,
|
||||
|
||||
Reference in New Issue
Block a user