Replace grammar-ending permutations with shared matcher; preserve word a

- Extract `grammar-ending.ts` with `isStandaloneGrammarEndingText` / `isSubtitleGrammarEndingText` pattern matchers
- Replace `STANDALONE_GRAMMAR_ENDINGS` set in parser-selection-stage with shared matcher
- Replace generated phrase sets in subtitle-annotation-filter with shared matcher
- Remove stale duplicate subtitle-exclusion constants and helpers from annotation-stage
- Manual clipboard card updates now write only to the sentence audio field, leaving word/expression audio untouched
This commit is contained in:
2026-05-02 23:25:33 -07:00
parent 9c8784672c
commit 41b2c7eccf
15 changed files with 285 additions and 265 deletions
@@ -126,7 +126,7 @@ function createManualUpdateService(overrides: Partial<CardCreationDeps> = {}): {
};
}
test('manual clipboard subtitle update replaces expression and sentence audio even when overwriteAudio is disabled', async () => {
test('manual clipboard subtitle update replaces sentence audio without touching expression audio', async () => {
const { service, updatedFields, mergeCalls, storedMedia } = createManualUpdateService();
await service.updateLastAddedFromClipboard('字幕');
@@ -134,10 +134,10 @@ test('manual clipboard subtitle update replaces expression and sentence audio ev
assert.equal(updatedFields.length, 1);
assert.equal(storedMedia.length, 1);
const audioValue = `[sound:${storedMedia[0]}]`;
assert.equal(updatedFields[0]?.ExpressionAudio, audioValue);
assert.equal(updatedFields[0]?.SentenceAudio, audioValue);
assert.equal('ExpressionAudio' in updatedFields[0]!, false);
assert.deepEqual(
mergeCalls.map((call) => call.overwrite),
[true, true],
[true],
);
});
+7 -18
View File
@@ -219,10 +219,6 @@ export class CardCreationService {
this.deps.getConfig(),
);
const sentenceAudioField = this.getResolvedSentenceAudioFieldName(noteInfo);
const expressionAudioField = this.deps.resolveConfiguredFieldName(
noteInfo,
this.deps.getConfig().fields?.audio || 'ExpressionAudio',
);
const sentenceField = this.deps.getEffectiveSentenceCardConfig().sentenceField;
const sentence = blocks.join(' ');
@@ -252,22 +248,15 @@ export class CardCreationService {
if (audioBuffer) {
await this.deps.client.storeMediaFile(audioFilename, audioBuffer);
if (sentenceAudioField || expressionAudioField) {
if (sentenceAudioField) {
const audioValue = `[sound:${audioFilename}]`;
const audioFields = new Set(
[sentenceAudioField, expressionAudioField].filter(
(fieldName): fieldName is string => Boolean(fieldName),
),
const existingAudio = noteInfo.fields[sentenceAudioField]?.value || '';
// Manual clipboard updates intentionally replace old captured sentence audio.
updatedFields[sentenceAudioField] = this.deps.mergeFieldValue(
existingAudio,
audioValue,
true,
);
for (const audioField of audioFields) {
const existingAudio = noteInfo.fields[audioField]?.value || '';
// Manual clipboard updates intentionally replace old captured audio.
updatedFields[audioField] = this.deps.mergeFieldValue(
existingAudio,
audioValue,
true,
);
}
}
miscInfoFilename = audioFilename;
updatePerformed = true;