mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 18:22:42 -08:00
feat: integrate n+1 target highlighting
- Merge feature branch changes for n+1 target-only highlight flow - Extend merged token model and token-merger to mark exactly-one unknown targets - Thread n+1 candidate metadata through tokenizer and config systems - Update subtitle renderer/state to route configured colors and new token class - Resolve merge conflicts in core service tests, including subtitle and subsync behavior
This commit is contained in:
@@ -186,6 +186,63 @@ test("accepts valid ankiConnect n+1 match mode values", () => {
|
||||
assert.equal(config.ankiConnect.nPlusOne.matchMode, "surface");
|
||||
});
|
||||
|
||||
test("validates ankiConnect n+1 color values", () => {
|
||||
const dir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "config.jsonc"),
|
||||
`{
|
||||
"ankiConnect": {
|
||||
"nPlusOne": {
|
||||
"nPlusOne": "not-a-color",
|
||||
"knownWord": 123
|
||||
}
|
||||
}
|
||||
}`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const service = new ConfigService(dir);
|
||||
const config = service.getConfig();
|
||||
const warnings = service.getWarnings();
|
||||
|
||||
assert.equal(
|
||||
config.ankiConnect.nPlusOne.nPlusOne,
|
||||
DEFAULT_CONFIG.ankiConnect.nPlusOne.nPlusOne,
|
||||
);
|
||||
assert.equal(
|
||||
config.ankiConnect.nPlusOne.knownWord,
|
||||
DEFAULT_CONFIG.ankiConnect.nPlusOne.knownWord,
|
||||
);
|
||||
assert.ok(
|
||||
warnings.some((warning) => warning.path === "ankiConnect.nPlusOne.nPlusOne"),
|
||||
);
|
||||
assert.ok(
|
||||
warnings.some((warning) => warning.path === "ankiConnect.nPlusOne.knownWord"),
|
||||
);
|
||||
});
|
||||
|
||||
test("accepts valid ankiConnect n+1 color values", () => {
|
||||
const dir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "config.jsonc"),
|
||||
`{
|
||||
"ankiConnect": {
|
||||
"nPlusOne": {
|
||||
"nPlusOne": "#c6a0f6",
|
||||
"knownWord": "#a6da95"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const service = new ConfigService(dir);
|
||||
const config = service.getConfig();
|
||||
|
||||
assert.equal(config.ankiConnect.nPlusOne.nPlusOne, "#c6a0f6");
|
||||
assert.equal(config.ankiConnect.nPlusOne.knownWord, "#a6da95");
|
||||
});
|
||||
|
||||
test("supports legacy ankiConnect.behavior N+1 settings as fallback", () => {
|
||||
const dir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
@@ -268,5 +325,8 @@ test("template generator includes known keys", () => {
|
||||
assert.match(output, /"ankiConnect":/);
|
||||
assert.match(output, /"websocket":/);
|
||||
assert.match(output, /"youtubeSubgen":/);
|
||||
assert.match(output, /"nPlusOne"\s*:\s*\{/);
|
||||
assert.match(output, /"nPlusOne": "#c6a0f6"/);
|
||||
assert.match(output, /"knownWord": "#a6da95"/);
|
||||
assert.match(output, /auto-generated from src\/config\/definitions.ts/);
|
||||
});
|
||||
|
||||
@@ -128,6 +128,8 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
|
||||
refreshMinutes: 1440,
|
||||
matchMode: "headword",
|
||||
decks: [],
|
||||
nPlusOne: "#c6a0f6",
|
||||
knownWord: "#a6da95",
|
||||
},
|
||||
metadata: {
|
||||
pattern: "[SubMiner] %f (%t)",
|
||||
@@ -179,6 +181,8 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
|
||||
fontWeight: "normal",
|
||||
fontStyle: "normal",
|
||||
backgroundColor: "rgba(54, 58, 79, 0.5)",
|
||||
nPlusOneColor: "#c6a0f6",
|
||||
knownWordColor: "#a6da95",
|
||||
secondary: {
|
||||
fontSize: 24,
|
||||
fontColor: "#ffffff",
|
||||
@@ -321,6 +325,18 @@ export const CONFIG_OPTION_REGISTRY: ConfigOptionRegistryEntry[] = [
|
||||
description:
|
||||
"Decks used for N+1 known-word cache scope. Supports one or more deck names.",
|
||||
},
|
||||
{
|
||||
path: "ankiConnect.nPlusOne.nPlusOne",
|
||||
kind: "string",
|
||||
defaultValue: DEFAULT_CONFIG.ankiConnect.nPlusOne.nPlusOne,
|
||||
description: "Color used for the single N+1 target token highlight.",
|
||||
},
|
||||
{
|
||||
path: "ankiConnect.nPlusOne.knownWord",
|
||||
kind: "string",
|
||||
defaultValue: DEFAULT_CONFIG.ankiConnect.nPlusOne.knownWord,
|
||||
description: "Color used for legacy known-word highlights.",
|
||||
},
|
||||
{
|
||||
path: "ankiConnect.isKiku.fieldGrouping",
|
||||
kind: "enum",
|
||||
|
||||
@@ -36,6 +36,15 @@ function asBoolean(value: unknown): boolean | undefined {
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
}
|
||||
|
||||
const hexColorPattern =
|
||||
/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
||||
|
||||
function asColor(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") return undefined;
|
||||
const text = value.trim();
|
||||
return hexColorPattern.test(text) ? text : undefined;
|
||||
}
|
||||
|
||||
export class ConfigService {
|
||||
private readonly configDir: string;
|
||||
private readonly configFileJsonc: string;
|
||||
@@ -751,6 +760,34 @@ export class ConfigService {
|
||||
resolved.ankiConnect.nPlusOne.decks = [];
|
||||
}
|
||||
|
||||
const nPlusOneHighlightColor = asColor(nPlusOneConfig.nPlusOne);
|
||||
if (nPlusOneHighlightColor !== undefined) {
|
||||
resolved.ankiConnect.nPlusOne.nPlusOne = nPlusOneHighlightColor;
|
||||
} else if (nPlusOneConfig.nPlusOne !== undefined) {
|
||||
warn(
|
||||
"ankiConnect.nPlusOne.nPlusOne",
|
||||
nPlusOneConfig.nPlusOne,
|
||||
resolved.ankiConnect.nPlusOne.nPlusOne,
|
||||
"Expected a hex color value.",
|
||||
);
|
||||
resolved.ankiConnect.nPlusOne.nPlusOne =
|
||||
DEFAULT_CONFIG.ankiConnect.nPlusOne.nPlusOne;
|
||||
}
|
||||
|
||||
const nPlusOneKnownWordColor = asColor(nPlusOneConfig.knownWord);
|
||||
if (nPlusOneKnownWordColor !== undefined) {
|
||||
resolved.ankiConnect.nPlusOne.knownWord = nPlusOneKnownWordColor;
|
||||
} else if (nPlusOneConfig.knownWord !== undefined) {
|
||||
warn(
|
||||
"ankiConnect.nPlusOne.knownWord",
|
||||
nPlusOneConfig.knownWord,
|
||||
resolved.ankiConnect.nPlusOne.knownWord,
|
||||
"Expected a hex color value.",
|
||||
);
|
||||
resolved.ankiConnect.nPlusOne.knownWord =
|
||||
DEFAULT_CONFIG.ankiConnect.nPlusOne.knownWord;
|
||||
}
|
||||
|
||||
if (
|
||||
resolved.ankiConnect.isKiku.fieldGrouping !== "auto" &&
|
||||
resolved.ankiConnect.isKiku.fieldGrouping !== "manual" &&
|
||||
|
||||
Reference in New Issue
Block a user