mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-09 15:13:32 -07:00
feat: add Anki deck dropdown with Yomitan auto-fill in settings (#95)
This commit is contained in:
@@ -30,6 +30,7 @@ test('classifyConfigHotReloadDiff treats safe nested config paths as hot-reloada
|
||||
next.youtube.primarySubLanguages = ['ja', 'en'];
|
||||
next.jimaku.maxEntryResults = prev.jimaku.maxEntryResults + 1;
|
||||
next.subsync.replace = !prev.subsync.replace;
|
||||
next.ankiConnect.deck = 'Mining';
|
||||
next.ankiConnect.behavior.autoUpdateNewCards = !prev.ankiConnect.behavior.autoUpdateNewCards;
|
||||
next.ankiConnect.knownWords.highlightEnabled = !prev.ankiConnect.knownWords.highlightEnabled;
|
||||
next.ankiConnect.knownWords.refreshMinutes = prev.ankiConnect.knownWords.refreshMinutes + 5;
|
||||
@@ -63,6 +64,7 @@ test('classifyConfigHotReloadDiff treats safe nested config paths as hot-reloada
|
||||
'youtube.primarySubLanguages',
|
||||
'jimaku.maxEntryResults',
|
||||
'subsync.replace',
|
||||
'ankiConnect.deck',
|
||||
'ankiConnect.behavior.autoUpdateNewCards',
|
||||
'ankiConnect.knownWords.highlightEnabled',
|
||||
'ankiConnect.knownWords.refreshMinutes',
|
||||
|
||||
@@ -66,6 +66,7 @@ const HOT_RELOAD_EXACT_OR_PREFIX_PATHS = [
|
||||
'youtube.primarySubLanguages',
|
||||
'jimaku',
|
||||
'subsync',
|
||||
'ankiConnect.deck',
|
||||
'ankiConnect.behavior.autoUpdateNewCards',
|
||||
'ankiConnect.knownWords.highlightEnabled',
|
||||
'ankiConnect.knownWords.refreshMinutes',
|
||||
|
||||
@@ -36,6 +36,7 @@ export {
|
||||
} from './tokenizer/yomitan-parser-runtime';
|
||||
export {
|
||||
deleteYomitanDictionaryByTitle,
|
||||
getYomitanCurrentAnkiDeckName,
|
||||
getYomitanDictionaryInfo,
|
||||
getYomitanSettingsFull,
|
||||
importYomitanDictionaryFromZip,
|
||||
|
||||
@@ -6,6 +6,7 @@ import test from 'node:test';
|
||||
import * as vm from 'node:vm';
|
||||
import {
|
||||
addYomitanNoteViaSearch,
|
||||
extractYomitanCurrentAnkiDeckName,
|
||||
getYomitanDictionaryInfo,
|
||||
importYomitanDictionaryFromZip,
|
||||
deleteYomitanDictionaryByTitle,
|
||||
@@ -181,6 +182,72 @@ test('syncYomitanDefaultAnkiServer no-ops for empty target url', async () => {
|
||||
assert.equal(executeCount, 0);
|
||||
});
|
||||
|
||||
test('extractYomitanCurrentAnkiDeckName prefers the active profile first term card format deck', () => {
|
||||
assert.equal(
|
||||
extractYomitanCurrentAnkiDeckName({
|
||||
profileCurrent: 1,
|
||||
profiles: [
|
||||
{
|
||||
options: {
|
||||
anki: {
|
||||
cardFormats: [{ type: 'term', deck: 'Inactive' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
options: {
|
||||
anki: {
|
||||
cardFormats: [
|
||||
{ type: 'kanji', deck: 'Kanji' },
|
||||
{ type: 'term', deck: 'Mining' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
'Mining',
|
||||
);
|
||||
});
|
||||
|
||||
test('extractYomitanCurrentAnkiDeckName ignores disabled card format decks', () => {
|
||||
assert.equal(
|
||||
extractYomitanCurrentAnkiDeckName({
|
||||
profiles: [
|
||||
{
|
||||
options: {
|
||||
anki: {
|
||||
cardFormats: [
|
||||
{ type: 'term', deck: 'Disabled Term', enabled: false },
|
||||
{ type: 'kanji', deck: 'Disabled Kanji', enabled: false },
|
||||
{ type: 'term', deck: 'Mining', enabled: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
'Mining',
|
||||
);
|
||||
});
|
||||
|
||||
test('extractYomitanCurrentAnkiDeckName falls back to legacy term deck', () => {
|
||||
assert.equal(
|
||||
extractYomitanCurrentAnkiDeckName({
|
||||
profiles: [
|
||||
{
|
||||
options: {
|
||||
anki: {
|
||||
terms: { deck: 'Legacy Mining' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
'Legacy Mining',
|
||||
);
|
||||
});
|
||||
|
||||
test('requestYomitanTermFrequencies returns normalized frequency entries', async () => {
|
||||
let scriptValue = '';
|
||||
const deps = createDeps(async (script) => {
|
||||
|
||||
@@ -1897,6 +1897,73 @@ export async function syncYomitanDefaultAnkiServer(
|
||||
}
|
||||
}
|
||||
|
||||
function readDeckName(value: unknown): string {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
}
|
||||
|
||||
function getYomitanDeckFromProfileOptions(profileOptions: Record<string, unknown>): string {
|
||||
const anki = profileOptions.anki;
|
||||
if (!isObject(anki)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const cardFormats = Array.isArray(anki.cardFormats) ? anki.cardFormats : [];
|
||||
const enabledCardFormats = cardFormats
|
||||
.filter((cardFormat): cardFormat is Record<string, unknown> => isObject(cardFormat))
|
||||
.filter((cardFormat) => cardFormat.enabled !== false);
|
||||
|
||||
const termDeck = enabledCardFormats.find(
|
||||
(cardFormat) => cardFormat.type === 'term' && readDeckName(cardFormat.deck).length > 0,
|
||||
);
|
||||
if (termDeck) {
|
||||
return readDeckName(termDeck.deck);
|
||||
}
|
||||
|
||||
const firstDeck = enabledCardFormats
|
||||
.map((cardFormat) => readDeckName(cardFormat.deck))
|
||||
.find((deckName) => deckName.length > 0);
|
||||
if (firstDeck) {
|
||||
return firstDeck;
|
||||
}
|
||||
|
||||
const terms = anki.terms;
|
||||
if (isObject(terms)) {
|
||||
const legacyTermDeck = readDeckName(terms.deck);
|
||||
if (legacyTermDeck) {
|
||||
return legacyTermDeck;
|
||||
}
|
||||
}
|
||||
|
||||
const kanji = anki.kanji;
|
||||
return isObject(kanji) ? readDeckName(kanji.deck) : '';
|
||||
}
|
||||
|
||||
export function extractYomitanCurrentAnkiDeckName(optionsFull: Record<string, unknown>): string {
|
||||
const profiles = Array.isArray(optionsFull.profiles) ? optionsFull.profiles : [];
|
||||
if (profiles.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const profileCurrent =
|
||||
typeof optionsFull.profileCurrent === 'number' && Number.isFinite(optionsFull.profileCurrent)
|
||||
? Math.max(0, Math.floor(optionsFull.profileCurrent))
|
||||
: 0;
|
||||
const targetProfile = profiles[profileCurrent];
|
||||
if (!isObject(targetProfile) || !isObject(targetProfile.options)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return getYomitanDeckFromProfileOptions(targetProfile.options as Record<string, unknown>);
|
||||
}
|
||||
|
||||
export async function getYomitanCurrentAnkiDeckName(
|
||||
deps: YomitanParserRuntimeDeps,
|
||||
logger: LoggerLike,
|
||||
): Promise<string> {
|
||||
const optionsFull = await getYomitanSettingsFull(deps, logger);
|
||||
return optionsFull ? extractYomitanCurrentAnkiDeckName(optionsFull) : '';
|
||||
}
|
||||
|
||||
function buildYomitanInvokeScript(actionLiteral: string, paramsLiteral: string): string {
|
||||
return `
|
||||
(async () => {
|
||||
|
||||
Reference in New Issue
Block a user