mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
refactor: split known words config from n-plus-one
This commit is contained in:
@@ -217,10 +217,8 @@ export class KnownWordCacheManager {
|
|||||||
|
|
||||||
private getKnownWordDecks(): string[] {
|
private getKnownWordDecks(): string[] {
|
||||||
const configuredDecks = this.deps.getConfig().knownWords?.decks;
|
const configuredDecks = this.deps.getConfig().knownWords?.decks;
|
||||||
if (Array.isArray(configuredDecks)) {
|
if (configuredDecks && typeof configuredDecks === 'object' && !Array.isArray(configuredDecks)) {
|
||||||
return configuredDecks
|
return Object.keys(configuredDecks).map((d) => d.trim()).filter((d) => d.length > 0);
|
||||||
.map((deck) => (typeof deck === 'string' ? deck.trim() : ''))
|
|
||||||
.filter((deck) => deck.length > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deck = this.deps.getConfig().deck?.trim();
|
const deck = this.deps.getConfig().deck?.trim();
|
||||||
@@ -228,6 +226,18 @@ export class KnownWordCacheManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getConfiguredFields(): string[] {
|
private getConfiguredFields(): string[] {
|
||||||
|
const configuredDecks = this.deps.getConfig().knownWords?.decks;
|
||||||
|
if (configuredDecks && typeof configuredDecks === 'object' && !Array.isArray(configuredDecks)) {
|
||||||
|
const allFields = new Set<string>();
|
||||||
|
for (const fields of Object.values(configuredDecks)) {
|
||||||
|
if (Array.isArray(fields)) {
|
||||||
|
for (const f of fields) {
|
||||||
|
if (typeof f === 'string' && f.trim()) allFields.add(f.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allFields.size > 0) return [...allFields];
|
||||||
|
}
|
||||||
return ['Expression', 'Word', 'Reading', 'Word Reading'];
|
return ['Expression', 'Word', 'Reading', 'Word Reading'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1585,7 +1585,10 @@ test('supports legacy ankiConnect nPlusOne known-word settings as fallback', ()
|
|||||||
assert.equal(config.ankiConnect.knownWords.highlightEnabled, true);
|
assert.equal(config.ankiConnect.knownWords.highlightEnabled, true);
|
||||||
assert.equal(config.ankiConnect.knownWords.refreshMinutes, 90);
|
assert.equal(config.ankiConnect.knownWords.refreshMinutes, 90);
|
||||||
assert.equal(config.ankiConnect.knownWords.matchMode, 'surface');
|
assert.equal(config.ankiConnect.knownWords.matchMode, 'surface');
|
||||||
assert.deepEqual(config.ankiConnect.knownWords.decks, ['Mining', 'Kaishi 1.5k']);
|
assert.deepEqual(config.ankiConnect.knownWords.decks, {
|
||||||
|
'Mining': ['Expression', 'Word', 'Reading', 'Word Reading'],
|
||||||
|
'Kaishi 1.5k': ['Expression', 'Word', 'Reading', 'Word Reading'],
|
||||||
|
});
|
||||||
assert.equal(config.ankiConnect.knownWords.color, '#a6da95');
|
assert.equal(config.ankiConnect.knownWords.color, '#a6da95');
|
||||||
assert.ok(
|
assert.ok(
|
||||||
warnings.some(
|
warnings.some(
|
||||||
@@ -1842,14 +1845,14 @@ test('ignores deprecated isLapis sentence-card field overrides', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('accepts valid ankiConnect knownWords deck list', () => {
|
test('accepts valid ankiConnect knownWords deck object', () => {
|
||||||
const dir = makeTempDir();
|
const dir = makeTempDir();
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(dir, 'config.jsonc'),
|
path.join(dir, 'config.jsonc'),
|
||||||
`{
|
`{
|
||||||
"ankiConnect": {
|
"ankiConnect": {
|
||||||
"knownWords": {
|
"knownWords": {
|
||||||
"decks": ["Deck One", "Deck Two"]
|
"decks": { "Deck One": ["Word", "Reading"], "Deck Two": ["Expression"] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
@@ -1859,7 +1862,10 @@ test('accepts valid ankiConnect knownWords deck list', () => {
|
|||||||
const service = new ConfigService(dir);
|
const service = new ConfigService(dir);
|
||||||
const config = service.getConfig();
|
const config = service.getConfig();
|
||||||
|
|
||||||
assert.deepEqual(config.ankiConnect.knownWords.decks, ['Deck One', 'Deck Two']);
|
assert.deepEqual(config.ankiConnect.knownWords.decks, {
|
||||||
|
'Deck One': ['Word', 'Reading'],
|
||||||
|
'Deck Two': ['Expression'],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('accepts valid ankiConnect tags list', () => {
|
test('accepts valid ankiConnect tags list', () => {
|
||||||
@@ -1918,7 +1924,7 @@ test('falls back to default when ankiConnect knownWords deck list is invalid', (
|
|||||||
const config = service.getConfig();
|
const config = service.getConfig();
|
||||||
const warnings = service.getWarnings();
|
const warnings = service.getWarnings();
|
||||||
|
|
||||||
assert.deepEqual(config.ankiConnect.knownWords.decks, []);
|
assert.deepEqual(config.ankiConnect.knownWords.decks, {});
|
||||||
assert.ok(warnings.some((warning) => warning.path === 'ankiConnect.knownWords.decks'));
|
assert.ok(warnings.some((warning) => warning.path === 'ankiConnect.knownWords.decks'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const INTEGRATIONS_DEFAULT_CONFIG: Pick<
|
|||||||
highlightEnabled: false,
|
highlightEnabled: false,
|
||||||
refreshMinutes: 1440,
|
refreshMinutes: 1440,
|
||||||
matchMode: 'headword',
|
matchMode: 'headword',
|
||||||
decks: [],
|
decks: {},
|
||||||
color: '#a6da95',
|
color: '#a6da95',
|
||||||
},
|
},
|
||||||
behavior: {
|
behavior: {
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ export function buildIntegrationConfigOptionRegistry(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'ankiConnect.knownWords.decks',
|
path: 'ankiConnect.knownWords.decks',
|
||||||
kind: 'array',
|
kind: 'object',
|
||||||
defaultValue: defaultConfig.ankiConnect.knownWords.decks,
|
defaultValue: defaultConfig.ankiConnect.knownWords.decks,
|
||||||
description: 'Decks used for known-word cache scope. Supports one or more deck names.',
|
description: 'Decks and fields for known-word cache. Object mapping deck names to arrays of field names to extract, e.g. { "Kaishi 1.5k": ["Word", "Word Reading"] }.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'ankiConnect.nPlusOne.nPlusOne',
|
path: 'ankiConnect.nPlusOne.nPlusOne',
|
||||||
|
|||||||
@@ -50,17 +50,30 @@ test('normalizes ankiConnect tags by trimming and deduping', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('warns and falls back for invalid knownWords.decks entries', () => {
|
test('accepts knownWords.decks object format with field arrays', () => {
|
||||||
const { context, warnings } = makeContext({
|
const { context, warnings } = makeContext({
|
||||||
knownWords: { decks: ['Core Deck', 123] },
|
knownWords: { decks: { 'Core Deck': ['Word', 'Reading'], 'Mining': ['Expression'] } },
|
||||||
});
|
});
|
||||||
|
|
||||||
applyAnkiConnectResolution(context);
|
applyAnkiConnectResolution(context);
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(context.resolved.ankiConnect.knownWords.decks, {
|
||||||
context.resolved.ankiConnect.knownWords.decks,
|
'Core Deck': ['Word', 'Reading'],
|
||||||
DEFAULT_CONFIG.ankiConnect.knownWords.decks,
|
'Mining': ['Expression'],
|
||||||
);
|
});
|
||||||
|
assert.equal(warnings.some((warning) => warning.path === 'ankiConnect.knownWords.decks'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('converts legacy knownWords.decks array to object with default fields', () => {
|
||||||
|
const { context, warnings } = makeContext({
|
||||||
|
knownWords: { decks: ['Core Deck'] },
|
||||||
|
});
|
||||||
|
|
||||||
|
applyAnkiConnectResolution(context);
|
||||||
|
|
||||||
|
assert.deepEqual(context.resolved.ankiConnect.knownWords.decks, {
|
||||||
|
'Core Deck': ['Expression', 'Word', 'Reading', 'Word Reading'],
|
||||||
|
});
|
||||||
assert.ok(warnings.some((warning) => warning.path === 'ankiConnect.knownWords.decks'));
|
assert.ok(warnings.some((warning) => warning.path === 'ankiConnect.knownWords.decks'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -832,74 +832,70 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
|||||||
DEFAULT_CONFIG.ankiConnect.knownWords.matchMode;
|
DEFAULT_CONFIG.ankiConnect.knownWords.matchMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_FIELDS = ['Expression', 'Word', 'Reading', 'Word Reading'];
|
||||||
const knownWordsDecks = knownWordsConfig.decks;
|
const knownWordsDecks = knownWordsConfig.decks;
|
||||||
const legacyNPlusOneDecks = nPlusOneConfig.decks;
|
const legacyNPlusOneDecks = nPlusOneConfig.decks;
|
||||||
if (Array.isArray(knownWordsDecks)) {
|
if (isObject(knownWordsDecks)) {
|
||||||
const normalizedDecks = knownWordsDecks
|
const resolved: Record<string, string[]> = {};
|
||||||
|
for (const [deck, fields] of Object.entries(knownWordsDecks as Record<string, unknown>)) {
|
||||||
|
const deckName = deck.trim();
|
||||||
|
if (!deckName) continue;
|
||||||
|
if (Array.isArray(fields) && fields.every((f) => typeof f === 'string')) {
|
||||||
|
resolved[deckName] = (fields as string[]).map((f) => f.trim()).filter((f) => f.length > 0);
|
||||||
|
} else {
|
||||||
|
context.warn(
|
||||||
|
`ankiConnect.knownWords.decks["${deckName}"]`,
|
||||||
|
fields,
|
||||||
|
DEFAULT_FIELDS,
|
||||||
|
'Expected an array of field name strings.',
|
||||||
|
);
|
||||||
|
resolved[deckName] = DEFAULT_FIELDS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.resolved.ankiConnect.knownWords.decks = resolved;
|
||||||
|
} else if (Array.isArray(knownWordsDecks)) {
|
||||||
|
const normalized = knownWordsDecks
|
||||||
.filter((entry): entry is string => typeof entry === 'string')
|
.filter((entry): entry is string => typeof entry === 'string')
|
||||||
.map((entry) => entry.trim())
|
.map((entry) => entry.trim())
|
||||||
.filter((entry) => entry.length > 0);
|
.filter((entry) => entry.length > 0);
|
||||||
|
const resolved: Record<string, string[]> = {};
|
||||||
if (normalizedDecks.length === knownWordsDecks.length) {
|
for (const deck of new Set(normalized)) {
|
||||||
context.resolved.ankiConnect.knownWords.decks = [...new Set(normalizedDecks)];
|
resolved[deck] = DEFAULT_FIELDS;
|
||||||
} else if (knownWordsDecks.length > 0) {
|
}
|
||||||
|
context.resolved.ankiConnect.knownWords.decks = resolved;
|
||||||
|
if (normalized.length > 0) {
|
||||||
context.warn(
|
context.warn(
|
||||||
'ankiConnect.knownWords.decks',
|
'ankiConnect.knownWords.decks',
|
||||||
knownWordsDecks,
|
knownWordsDecks,
|
||||||
context.resolved.ankiConnect.knownWords.decks,
|
resolved,
|
||||||
'Expected an array of strings.',
|
'Legacy array format is deprecated; use object format: { "Deck Name": ["Field1", "Field2"] }',
|
||||||
);
|
);
|
||||||
context.resolved.ankiConnect.knownWords.decks = DEFAULT_CONFIG.ankiConnect.knownWords.decks;
|
|
||||||
} else {
|
|
||||||
context.resolved.ankiConnect.knownWords.decks = [];
|
|
||||||
}
|
}
|
||||||
} else if (knownWordsDecks !== undefined) {
|
} else if (knownWordsDecks !== undefined) {
|
||||||
context.warn(
|
context.warn(
|
||||||
'ankiConnect.knownWords.decks',
|
'ankiConnect.knownWords.decks',
|
||||||
knownWordsDecks,
|
knownWordsDecks,
|
||||||
context.resolved.ankiConnect.knownWords.decks,
|
context.resolved.ankiConnect.knownWords.decks,
|
||||||
'Expected an array of strings.',
|
'Expected an object mapping deck names to field arrays.',
|
||||||
);
|
);
|
||||||
context.resolved.ankiConnect.knownWords.decks = DEFAULT_CONFIG.ankiConnect.knownWords.decks;
|
|
||||||
} else if (Array.isArray(legacyNPlusOneDecks)) {
|
} else if (Array.isArray(legacyNPlusOneDecks)) {
|
||||||
const normalizedDecks = legacyNPlusOneDecks
|
const normalized = legacyNPlusOneDecks
|
||||||
.filter((entry): entry is string => typeof entry === 'string')
|
.filter((entry): entry is string => typeof entry === 'string')
|
||||||
.map((entry) => entry.trim())
|
.map((entry) => entry.trim())
|
||||||
.filter((entry) => entry.length > 0);
|
.filter((entry) => entry.length > 0);
|
||||||
|
const resolved: Record<string, string[]> = {};
|
||||||
if (normalizedDecks.length === legacyNPlusOneDecks.length) {
|
for (const deck of new Set(normalized)) {
|
||||||
context.resolved.ankiConnect.knownWords.decks = [...new Set(normalizedDecks)];
|
resolved[deck] = DEFAULT_FIELDS;
|
||||||
|
}
|
||||||
|
context.resolved.ankiConnect.knownWords.decks = resolved;
|
||||||
|
if (normalized.length > 0) {
|
||||||
context.warn(
|
context.warn(
|
||||||
'ankiConnect.nPlusOne.decks',
|
'ankiConnect.nPlusOne.decks',
|
||||||
legacyNPlusOneDecks,
|
legacyNPlusOneDecks,
|
||||||
DEFAULT_CONFIG.ankiConnect.knownWords.decks,
|
DEFAULT_CONFIG.ankiConnect.knownWords.decks,
|
||||||
'Legacy key is deprecated; use ankiConnect.knownWords.decks',
|
'Legacy key is deprecated; use ankiConnect.knownWords.decks with object format',
|
||||||
);
|
|
||||||
} else if (legacyNPlusOneDecks.length > 0) {
|
|
||||||
context.warn(
|
|
||||||
'ankiConnect.nPlusOne.decks',
|
|
||||||
legacyNPlusOneDecks,
|
|
||||||
context.resolved.ankiConnect.knownWords.decks,
|
|
||||||
'Expected an array of strings.',
|
|
||||||
);
|
|
||||||
context.resolved.ankiConnect.knownWords.decks = DEFAULT_CONFIG.ankiConnect.knownWords.decks;
|
|
||||||
} else {
|
|
||||||
context.resolved.ankiConnect.knownWords.decks = [];
|
|
||||||
context.warn(
|
|
||||||
'ankiConnect.nPlusOne.decks',
|
|
||||||
legacyNPlusOneDecks,
|
|
||||||
DEFAULT_CONFIG.ankiConnect.knownWords.decks,
|
|
||||||
'Legacy key is deprecated; use ankiConnect.knownWords.decks',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (legacyNPlusOneDecks !== undefined) {
|
|
||||||
context.warn(
|
|
||||||
'ankiConnect.nPlusOne.decks',
|
|
||||||
legacyNPlusOneDecks,
|
|
||||||
context.resolved.ankiConnect.knownWords.decks,
|
|
||||||
'Expected an array of strings.',
|
|
||||||
);
|
|
||||||
context.resolved.ankiConnect.knownWords.decks = DEFAULT_CONFIG.ankiConnect.knownWords.decks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nPlusOneHighlightColor = asColor(nPlusOneConfig.nPlusOne);
|
const nPlusOneHighlightColor = asColor(nPlusOneConfig.nPlusOne);
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export interface AnkiConnectConfig {
|
|||||||
highlightEnabled?: boolean;
|
highlightEnabled?: boolean;
|
||||||
refreshMinutes?: number;
|
refreshMinutes?: number;
|
||||||
matchMode?: NPlusOneMatchMode;
|
matchMode?: NPlusOneMatchMode;
|
||||||
decks?: string[];
|
decks?: Record<string, string[]>;
|
||||||
color?: string;
|
color?: string;
|
||||||
};
|
};
|
||||||
nPlusOne?: {
|
nPlusOne?: {
|
||||||
@@ -739,7 +739,7 @@ export interface ResolvedConfig {
|
|||||||
highlightEnabled: boolean;
|
highlightEnabled: boolean;
|
||||||
refreshMinutes: number;
|
refreshMinutes: number;
|
||||||
matchMode: NPlusOneMatchMode;
|
matchMode: NPlusOneMatchMode;
|
||||||
decks: string[];
|
decks: Record<string, string[]>;
|
||||||
color: string;
|
color: string;
|
||||||
};
|
};
|
||||||
nPlusOne: {
|
nPlusOne: {
|
||||||
|
|||||||
Reference in New Issue
Block a user