feat: add configurable Anki word field with note ID merge tracking

- Extract word field config into reusable anki-field-config module
- Add ankiConnect.fields.word config option (default: "Expression")
- Replace hardcoded "Expression" field references across Anki integration
- Add note ID redirect tracking for merged/moved cards
- Support legacy ankiConnect.wordField migration path
This commit is contained in:
2026-03-18 02:24:26 -07:00
parent 61e1621137
commit a0015dc75c
17 changed files with 286 additions and 20 deletions

View File

@@ -1,4 +1,5 @@
import { AnkiConnectConfig } from '../types';
import { getConfiguredWordFieldName } from '../anki-field-config';
interface FieldGroupingMergeMedia {
audioField?: string;
@@ -77,6 +78,7 @@ export class FieldGroupingMergeCollaborator {
includeGeneratedMedia: boolean,
): Promise<Record<string, string>> {
const config = this.deps.getConfig();
const configuredWordField = getConfiguredWordFieldName(config);
const groupableFields = this.getGroupableFieldNames();
const keepFieldNames = Object.keys(keepNoteInfo.fields);
const sourceFields: Record<string, string> = {};
@@ -98,11 +100,17 @@ export class FieldGroupingMergeCollaborator {
if (!sourceFields['Sentence'] && sourceFields['SentenceFurigana']) {
sourceFields['Sentence'] = sourceFields['SentenceFurigana'];
}
if (!sourceFields['Expression'] && sourceFields['Word']) {
sourceFields['Expression'] = sourceFields['Word'];
if (!sourceFields[configuredWordField] && sourceFields['Expression']) {
sourceFields[configuredWordField] = sourceFields['Expression'];
}
if (!sourceFields['Word'] && sourceFields['Expression']) {
sourceFields['Word'] = sourceFields['Expression'];
if (!sourceFields[configuredWordField] && sourceFields['Word']) {
sourceFields[configuredWordField] = sourceFields['Word'];
}
if (!sourceFields['Expression'] && sourceFields[configuredWordField]) {
sourceFields['Expression'] = sourceFields[configuredWordField];
}
if (!sourceFields['Word'] && sourceFields[configuredWordField]) {
sourceFields['Word'] = sourceFields[configuredWordField];
}
if (!sourceFields['SentenceAudio'] && sourceFields['ExpressionAudio']) {
sourceFields['SentenceAudio'] = sourceFields['ExpressionAudio'];
@@ -148,6 +156,7 @@ export class FieldGroupingMergeCollaborator {
const keepFieldNormalized = keepFieldName.toLowerCase();
if (
keepFieldNormalized === 'expression' ||
keepFieldNormalized === configuredWordField.toLowerCase() ||
keepFieldNormalized === 'expressionfurigana' ||
keepFieldNormalized === 'expressionreading' ||
keepFieldNormalized === 'expressionaudio'