mirror of
https://github.com/panatgithub/AnkiHeadingSync.git
synced 2026-07-22 06:51:43 +00:00
130 lines
No EOL
4.9 KiB
TypeScript
130 lines
No EOL
4.9 KiB
TypeScript
import type { Card } from "@/domain/card/entities/Card";
|
|
import { createNoteFieldMappingKey, type NoteModelFieldMapping } from "@/application/config/NoteModelFieldMapping";
|
|
import type { NoteModelDetails } from "@/application/dto/NoteModelDetails";
|
|
|
|
export class NoteFieldMappingService {
|
|
map(
|
|
card: Card,
|
|
noteModelDetails: NoteModelDetails,
|
|
noteFieldMappings: Record<string, NoteModelFieldMapping>,
|
|
): Record<string, string> {
|
|
const mapping = this.getRequiredMapping(card, noteFieldMappings);
|
|
this.validateMapping(mapping, noteModelDetails);
|
|
|
|
if (card.type === "basic") {
|
|
return this.mapBasic(card, mapping);
|
|
}
|
|
|
|
return this.mapCloze(card, noteModelDetails, mapping);
|
|
}
|
|
|
|
suggest(cardType: Card["type"], modelName: string, fieldNames: string[], loadedAt = Date.now()): NoteModelFieldMapping {
|
|
return cardType === "basic"
|
|
? {
|
|
cardType,
|
|
modelName,
|
|
loadedFieldNames: [...fieldNames],
|
|
titleField: findFieldName(fieldNames, ["Front", "Title"]) ?? fieldNames[0],
|
|
bodyField:
|
|
findFieldName(fieldNames, ["Back", "Body", "Answer"]) ??
|
|
fieldNames.find((fieldName) => fieldName !== (findFieldName(fieldNames, ["Front", "Title"]) ?? fieldNames[0])),
|
|
loadedAt,
|
|
}
|
|
: {
|
|
cardType,
|
|
modelName,
|
|
loadedFieldNames: [...fieldNames],
|
|
mainField: findFieldName(fieldNames, ["Text", "Body", "Content"]) ?? fieldNames[0],
|
|
loadedAt,
|
|
};
|
|
}
|
|
|
|
validateMapping(mapping: NoteModelFieldMapping, noteModelDetails: NoteModelDetails): void {
|
|
const availableFields = new Set(noteModelDetails.fieldNames);
|
|
|
|
if (mapping.cardType === "basic") {
|
|
if (!mapping.titleField || !mapping.bodyField) {
|
|
throw new Error(
|
|
`Saved field mapping for basic note type "${mapping.modelName}" is incomplete. Open plugin settings and save both title and body fields.`,
|
|
);
|
|
}
|
|
|
|
if (mapping.titleField === mapping.bodyField) {
|
|
throw new Error(`Basic note type "${mapping.modelName}" must use different title and body fields.`);
|
|
}
|
|
|
|
const missingFields = [mapping.titleField, mapping.bodyField].filter((fieldName) => !availableFields.has(fieldName));
|
|
if (missingFields.length > 0) {
|
|
throw new Error(this.createStaleMappingError(mapping.modelName, missingFields));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!mapping.mainField) {
|
|
throw new Error(
|
|
`Saved field mapping for cloze note type "${mapping.modelName}" is incomplete. Open plugin settings and save the main field.`,
|
|
);
|
|
}
|
|
|
|
if (!noteModelDetails.isCloze) {
|
|
throw new Error(`Cloze note type "${mapping.modelName}" is not cloze-compatible in Anki.`);
|
|
}
|
|
|
|
if (!availableFields.has(mapping.mainField)) {
|
|
throw new Error(this.createStaleMappingError(mapping.modelName, [mapping.mainField]));
|
|
}
|
|
}
|
|
|
|
private mapBasic(card: Card, mapping: NoteModelFieldMapping): Record<string, string> {
|
|
const titleFieldName = mapping.titleField;
|
|
const bodyFieldName = mapping.bodyField;
|
|
|
|
if (!titleFieldName || !bodyFieldName) {
|
|
throw new Error(`Saved field mapping for basic note type "${card.noteModel}" is incomplete.`);
|
|
}
|
|
|
|
return {
|
|
[titleFieldName]: card.renderedFields.title,
|
|
[bodyFieldName]: card.renderedFields.body,
|
|
};
|
|
}
|
|
|
|
private mapCloze(card: Card, noteModelDetails: NoteModelDetails, mapping: NoteModelFieldMapping): Record<string, string> {
|
|
if (!noteModelDetails.isCloze) {
|
|
throw new Error(`Cloze note type "${card.noteModel}" is not cloze-compatible in Anki.`);
|
|
}
|
|
|
|
if (!mapping.mainField) {
|
|
throw new Error(`Saved field mapping for cloze note type "${card.noteModel}" is incomplete.`);
|
|
}
|
|
|
|
return {
|
|
[mapping.mainField]: `${card.renderedFields.title}<br><br>${card.renderedFields.body}`,
|
|
};
|
|
}
|
|
|
|
private getRequiredMapping(
|
|
card: Card,
|
|
noteFieldMappings: Record<string, NoteModelFieldMapping>,
|
|
): NoteModelFieldMapping {
|
|
const mapping = noteFieldMappings[createNoteFieldMappingKey(card.type, card.noteModel)];
|
|
|
|
if (!mapping) {
|
|
throw new Error(
|
|
`No saved field mapping found for ${card.type === "basic" ? "basic" : "cloze"} note type "${card.noteModel}". Open plugin settings and read fields from Anki first.`,
|
|
);
|
|
}
|
|
|
|
return mapping;
|
|
}
|
|
|
|
private createStaleMappingError(modelName: string, missingFields: string[]): string {
|
|
const quotedMissingFields = missingFields.map((fieldName) => `"${fieldName}"`).join(", ");
|
|
return `Saved field mapping for note type "${modelName}" is stale because these fields no longer exist in Anki: ${quotedMissingFields}. Open plugin settings and read fields from Anki again.`;
|
|
}
|
|
}
|
|
|
|
function findFieldName(fieldNames: string[], preferredNames: string[]): string | undefined {
|
|
return fieldNames.find((fieldName) => preferredNames.some((preferredName) => preferredName.toLowerCase() === fieldName.toLowerCase()));
|
|
} |