mirror of
https://github.com/panatgithub/AnkiHeadingSync.git
synced 2026-07-22 06:51:43 +00:00
fix(sync): rebuild cached all-cloze cards
中文: 修复全库同步复用旧缓存时漏掉 #anki-cloze-all 模式,确保旧 sequential 状态会强制重读并触发重建。 English: Force unchanged vault-sync files to be re-read when cached cloze state missed the all-cloze marker, so sequential-to-all shrink rebuilds can run and clear stale empty cards.
This commit is contained in:
parent
5c1bf809b2
commit
6f7636da55
3 changed files with 102 additions and 17 deletions
30
main.js
30
main.js
File diff suppressed because one or more lines are too long
|
|
@ -76,6 +76,69 @@ describe("FileIndexerService", () => {
|
|||
expect(result.cards[0]).toMatchObject({ noteId: 10, noteIdSource: "marker", idMarkerState: "present-valid" });
|
||||
});
|
||||
|
||||
it("re-reads unchanged files when cached cloze state missed an all-cloze marker", async () => {
|
||||
const content = [
|
||||
"#### Cloze #anki-cloze-all",
|
||||
"{A} {B} {C}",
|
||||
"<!--ID: 10-->",
|
||||
].join("\n");
|
||||
const settings = createIndexSettingsForPath("notes/one.md");
|
||||
const vaultGateway = new FakeManualSyncVaultGateway({
|
||||
"notes/one.md": content,
|
||||
});
|
||||
const service = new FileIndexerService(vaultGateway);
|
||||
const state = {
|
||||
files: {
|
||||
"notes/one.md": {
|
||||
filePath: "notes/one.md",
|
||||
fileHash: "hash-a",
|
||||
fileStamp: `1:${content.length}`,
|
||||
deckRulesFingerprint: createDeckRulesFingerprint(settings),
|
||||
lastIndexedAt: 1,
|
||||
noteIds: [10],
|
||||
},
|
||||
},
|
||||
cards: {
|
||||
"10": {
|
||||
noteId: 10,
|
||||
filePath: "notes/one.md",
|
||||
heading: "Cloze #anki-cloze-all",
|
||||
backlinkHeadingText: "Cloze #anki-cloze-all",
|
||||
headingLevel: 4,
|
||||
bodyMarkdown: "{A} {B} {C}",
|
||||
cardType: "cloze" as const,
|
||||
clozeMode: "sequential" as const,
|
||||
blockStartOffset: 0,
|
||||
blockEndOffset: content.length,
|
||||
blockStartLine: 1,
|
||||
bodyStartLine: 2,
|
||||
blockEndLine: 3,
|
||||
contentEndLine: 2,
|
||||
markerLine: 3,
|
||||
rawBlockText: content,
|
||||
rawBlockHash: "hash-card",
|
||||
renderConfigHash: "render-hash",
|
||||
deck: "Obsidian",
|
||||
deckWarnings: [],
|
||||
tagsHint: [],
|
||||
lastSyncedAt: 1,
|
||||
orphan: false,
|
||||
},
|
||||
},
|
||||
pendingWriteBack: [],
|
||||
};
|
||||
|
||||
const result = await service.indexVault(settings, state);
|
||||
|
||||
expect(result.skippedUnchangedFiles).toBe(0);
|
||||
expect(vaultGateway.readCalls).toEqual(["notes/one.md"]);
|
||||
expect(result.cards[0]).toMatchObject({
|
||||
noteId: 10,
|
||||
cardType: "cloze",
|
||||
clozeMode: "all",
|
||||
});
|
||||
});
|
||||
|
||||
it("forces re-read when deck rules fingerprint changed even if file stamp is unchanged", async () => {
|
||||
const content = ["#### One", "Body"].join("\n");
|
||||
const vaultGateway = new FakeManualSyncVaultGateway({
|
||||
|
|
@ -252,4 +315,4 @@ function createIndexSettingsForPath(filePath: string, overrides: Parameters<type
|
|||
includeFolders: [filePath.slice(0, firstSlashIndex)],
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,12 +96,14 @@ export class FileIndexerService {
|
|||
const hasPendingWriteBack = pendingWriteBack.length > 0;
|
||||
const knownCards = stateIndex.cardsByFilePath.get(ref.path) ?? [];
|
||||
const hasMissingKnownCard = (existingFileState?.noteIds ?? []).some((noteId) => !state.cards[toNoteIdKey(noteId)]);
|
||||
const hasStaleClozeAllMode = hasCachedClozeAllMarkerWithoutAllMode(knownCards, settings);
|
||||
const shouldRead =
|
||||
forceReadAll ||
|
||||
hasPendingWriteBack ||
|
||||
!existingFileState ||
|
||||
existingFileState.fileStamp !== fileStamp ||
|
||||
existingFileState.deckRulesFingerprint !== deckRulesFingerprint ||
|
||||
hasStaleClozeAllMode ||
|
||||
hasMissingKnownCard;
|
||||
|
||||
if (!shouldRead) {
|
||||
|
|
@ -233,6 +235,26 @@ function restoreIndexedCard(card: CardState): IndexedCard {
|
|||
};
|
||||
}
|
||||
|
||||
function hasCachedClozeAllMarkerWithoutAllMode(cards: CardState[], settings: PluginSettings): boolean {
|
||||
const clozeAllConfig = settings.cardTypeConfigs["cloze-all"];
|
||||
const marker = clozeAllConfig.enabled ? clozeAllConfig.extraMarker.trim() : "";
|
||||
if (marker.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return cards.some((card) => {
|
||||
if (card.cardType !== "cloze" || normalizeStoredClozeMode(card.cardType, card.clozeMode) === "all") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return card.heading.trimEnd().endsWith(marker) || getRawHeading(card.rawBlockText).trimEnd().endsWith(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function getRawHeading(rawBlockText: string): string {
|
||||
return rawBlockText.split(/\r?\n/, 1)[0] ?? "";
|
||||
}
|
||||
|
||||
function restoreIndexedGroupBlock(groupBlock: GroupBlockState): IndexedGroupCardBlock {
|
||||
return {
|
||||
noteId: groupBlock.noteId,
|
||||
|
|
@ -283,4 +305,4 @@ export function createDeckRulesFingerprint(settings: PluginSettings): string {
|
|||
syncObsidianTagsToAnki: settings.syncObsidianTagsToAnki,
|
||||
keepPureTagLinesInCardBody: settings.keepPureTagLinesInCardBody,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue