mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-05 07:21:34 -07:00
Anki maturity-based known-word highlighting (#172)
This commit is contained in:
@@ -2,6 +2,12 @@ import type { SessionHelpSection } from './session-help-sections';
|
||||
|
||||
export type SessionHelpSubtitleStyle = {
|
||||
knownWordColor?: unknown;
|
||||
knownWordMaturityColors?: {
|
||||
new?: unknown;
|
||||
learning?: unknown;
|
||||
young?: unknown;
|
||||
mature?: unknown;
|
||||
};
|
||||
nPlusOneColor?: unknown;
|
||||
nameMatchColor?: unknown;
|
||||
jlptColors?: {
|
||||
@@ -13,10 +19,19 @@ export type SessionHelpSubtitleStyle = {
|
||||
};
|
||||
};
|
||||
|
||||
export type SessionHelpColorOptions = {
|
||||
/** When true, known words are colored per Anki card maturity instead of one flat color. */
|
||||
knownWordMaturityEnabled?: boolean;
|
||||
};
|
||||
|
||||
const HEX_COLOR_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
||||
|
||||
const FALLBACK_COLORS = {
|
||||
knownWordColor: '#a6da95',
|
||||
knownWordMaturityNewColor: '#ee99a0',
|
||||
knownWordMaturityLearningColor: '#b7bdf8',
|
||||
knownWordMaturityYoungColor: '#91d7e3',
|
||||
knownWordMaturityMatureColor: '#a6da95',
|
||||
nPlusOneColor: '#c6a0f6',
|
||||
nameMatchColor: '#f5bde6',
|
||||
jlptN1Color: '#ed8796',
|
||||
@@ -32,15 +47,53 @@ function normalizeColor(value: unknown, fallback: string): string {
|
||||
return HEX_COLOR_RE.test(next) ? next : fallback;
|
||||
}
|
||||
|
||||
export function buildColorSection(style: SessionHelpSubtitleStyle): SessionHelpSection {
|
||||
function buildKnownWordRows(
|
||||
style: SessionHelpSubtitleStyle,
|
||||
options: SessionHelpColorOptions,
|
||||
): SessionHelpSection['rows'] {
|
||||
if (!options.knownWordMaturityEnabled) {
|
||||
const knownWordColor = normalizeColor(style.knownWordColor, FALLBACK_COLORS.knownWordColor);
|
||||
return [{ shortcut: 'Known words', action: knownWordColor, color: knownWordColor }];
|
||||
}
|
||||
|
||||
const maturityColors = style.knownWordMaturityColors;
|
||||
const tiers: Array<{ label: string; value: unknown; fallback: string }> = [
|
||||
{
|
||||
label: 'Known words (new)',
|
||||
value: maturityColors?.new,
|
||||
fallback: FALLBACK_COLORS.knownWordMaturityNewColor,
|
||||
},
|
||||
{
|
||||
label: 'Known words (learning)',
|
||||
value: maturityColors?.learning,
|
||||
fallback: FALLBACK_COLORS.knownWordMaturityLearningColor,
|
||||
},
|
||||
{
|
||||
label: 'Known words (young)',
|
||||
value: maturityColors?.young,
|
||||
fallback: FALLBACK_COLORS.knownWordMaturityYoungColor,
|
||||
},
|
||||
{
|
||||
label: 'Known words (mature)',
|
||||
value: maturityColors?.mature,
|
||||
fallback: FALLBACK_COLORS.knownWordMaturityMatureColor,
|
||||
},
|
||||
];
|
||||
|
||||
return tiers.map((tier) => {
|
||||
const color = normalizeColor(tier.value, tier.fallback);
|
||||
return { shortcut: tier.label, action: color, color };
|
||||
});
|
||||
}
|
||||
|
||||
export function buildColorSection(
|
||||
style: SessionHelpSubtitleStyle,
|
||||
options: SessionHelpColorOptions = {},
|
||||
): SessionHelpSection {
|
||||
return {
|
||||
title: 'Color legend',
|
||||
rows: [
|
||||
{
|
||||
shortcut: 'Known words',
|
||||
action: normalizeColor(style.knownWordColor, FALLBACK_COLORS.knownWordColor),
|
||||
color: normalizeColor(style.knownWordColor, FALLBACK_COLORS.knownWordColor),
|
||||
},
|
||||
...buildKnownWordRows(style, options),
|
||||
{
|
||||
shortcut: 'N+1 words',
|
||||
action: normalizeColor(style.nPlusOneColor, FALLBACK_COLORS.nPlusOneColor),
|
||||
|
||||
@@ -403,6 +403,7 @@ export function buildSessionHelpSections(input: {
|
||||
markWatchedKey?: string | null;
|
||||
subtitleSidebarToggleKey?: string | null;
|
||||
subtitleStyle: SessionHelpSubtitleStyle | null | undefined;
|
||||
knownWordMaturityEnabled?: boolean;
|
||||
}): SessionHelpSection[] {
|
||||
const sessionBindings = input.sessionBindings.filter((binding) => {
|
||||
if (binding.actionType !== 'session-action') return true;
|
||||
@@ -420,7 +421,9 @@ export function buildSessionHelpSections(input: {
|
||||
subtitleSidebarToggleKey: input.subtitleSidebarToggleKey,
|
||||
}),
|
||||
...buildFixedOverlaySections(),
|
||||
buildColorSection(input.subtitleStyle ?? {}),
|
||||
buildColorSection(input.subtitleStyle ?? {}, {
|
||||
knownWordMaturityEnabled: input.knownWordMaturityEnabled,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ import {
|
||||
createSessionHelpModal,
|
||||
describeSessionHelpCommand,
|
||||
formatSessionHelpKeybinding,
|
||||
isKnownWordMaturityLegendEnabled,
|
||||
} from './session-help.js';
|
||||
import type { RuntimeOptionId, RuntimeOptionState } from '../../types/runtime-options.js';
|
||||
|
||||
test('session help describes sub-seek commands as subtitle-line navigation', () => {
|
||||
assert.equal(describeSessionHelpCommand(['sub-seek', 1]), 'Jump to next subtitle');
|
||||
@@ -104,6 +106,101 @@ test('session help builds rows from canonical session bindings and fixed overlay
|
||||
assert.ok(rows.some((row) => row.shortcut === 'Y then D' && row.action === 'Toggle DevTools'));
|
||||
});
|
||||
|
||||
function booleanRuntimeOption(id: RuntimeOptionId, value: boolean): RuntimeOptionState {
|
||||
return {
|
||||
id,
|
||||
label: id,
|
||||
scope: 'subtitle',
|
||||
valueType: 'boolean',
|
||||
value,
|
||||
allowedValues: [true, false],
|
||||
requiresRestart: false,
|
||||
};
|
||||
}
|
||||
|
||||
test('maturity legend requires both known-word highlighting and maturity coloring', () => {
|
||||
const highlightOn = booleanRuntimeOption('subtitle.annotation.knownWords.highlightEnabled', true);
|
||||
const highlightOff = booleanRuntimeOption(
|
||||
'subtitle.annotation.knownWords.highlightEnabled',
|
||||
false,
|
||||
);
|
||||
const maturityOn = booleanRuntimeOption('subtitle.annotation.knownWords.maturityEnabled', true);
|
||||
const maturityOff = booleanRuntimeOption('subtitle.annotation.knownWords.maturityEnabled', false);
|
||||
|
||||
assert.equal(isKnownWordMaturityLegendEnabled([highlightOn, maturityOn]), true);
|
||||
assert.equal(isKnownWordMaturityLegendEnabled([highlightOff, maturityOn]), false);
|
||||
assert.equal(isKnownWordMaturityLegendEnabled([highlightOn, maturityOff]), false);
|
||||
assert.equal(isKnownWordMaturityLegendEnabled([maturityOn]), false);
|
||||
assert.equal(isKnownWordMaturityLegendEnabled([]), false);
|
||||
});
|
||||
|
||||
function colorLegendRows(input: Parameters<typeof buildSessionHelpSections>[0]) {
|
||||
const sections = buildSessionHelpSections(input);
|
||||
return sections.find((section) => section.title === 'Color legend')?.rows ?? [];
|
||||
}
|
||||
|
||||
test('color legend shows the flat known-word color when maturity coloring is off', () => {
|
||||
const rows = colorLegendRows({
|
||||
sessionBindings: [],
|
||||
subtitleStyle: {
|
||||
knownWordColor: '#a6da95',
|
||||
knownWordMaturityColors: {
|
||||
new: '#ee99a0',
|
||||
learning: '#b7bdf8',
|
||||
young: '#91d7e3',
|
||||
mature: '#a6da95',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
rows.filter((row) => row.shortcut.startsWith('Known words')),
|
||||
[{ shortcut: 'Known words', action: '#a6da95', color: '#a6da95' }],
|
||||
);
|
||||
});
|
||||
|
||||
test('color legend swaps in maturity tiers when maturity coloring is on', () => {
|
||||
const rows = colorLegendRows({
|
||||
sessionBindings: [],
|
||||
subtitleStyle: {
|
||||
knownWordColor: '#a6da95',
|
||||
knownWordMaturityColors: {
|
||||
new: '#ee99a0',
|
||||
learning: '#b7bdf8',
|
||||
young: '#91d7e3',
|
||||
mature: '#f0c6c6',
|
||||
},
|
||||
},
|
||||
knownWordMaturityEnabled: true,
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
rows.filter((row) => row.shortcut.startsWith('Known words')),
|
||||
[
|
||||
{ shortcut: 'Known words (new)', action: '#ee99a0', color: '#ee99a0' },
|
||||
{ shortcut: 'Known words (learning)', action: '#b7bdf8', color: '#b7bdf8' },
|
||||
{ shortcut: 'Known words (young)', action: '#91d7e3', color: '#91d7e3' },
|
||||
{ shortcut: 'Known words (mature)', action: '#f0c6c6', color: '#f0c6c6' },
|
||||
],
|
||||
);
|
||||
assert.ok(rows.some((row) => row.shortcut === 'N+1 words'));
|
||||
});
|
||||
|
||||
test('color legend falls back to default maturity colors when overrides are invalid', () => {
|
||||
const rows = colorLegendRows({
|
||||
sessionBindings: [],
|
||||
subtitleStyle: {
|
||||
knownWordMaturityColors: { new: 'not-a-color', learning: 42 },
|
||||
},
|
||||
knownWordMaturityEnabled: true,
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
rows.filter((row) => row.shortcut.startsWith('Known words')).map((row) => row.color),
|
||||
['#ee99a0', '#b7bdf8', '#91d7e3', '#a6da95'],
|
||||
);
|
||||
});
|
||||
|
||||
function createClassList(initialTokens: string[] = []) {
|
||||
const tokens = new Set(initialTokens);
|
||||
return {
|
||||
@@ -176,6 +273,7 @@ test('modal-layer session help does not focus hidden main overlay and still clos
|
||||
getSubtitleSidebarSnapshot: async () => ({
|
||||
config: { toggleKey: 'Backslash' },
|
||||
}),
|
||||
getRuntimeOptions: async () => [],
|
||||
},
|
||||
focus: () => {},
|
||||
addEventListener: () => {},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ModalStateReader, RendererContext } from '../context';
|
||||
import type { RuntimeOptionId, RuntimeOptionState } from '../../types/runtime-options';
|
||||
import {
|
||||
buildSessionHelpSections,
|
||||
type SessionHelpSection,
|
||||
@@ -19,6 +20,32 @@ type SessionHelpBindingInfo = {
|
||||
fallbackUnavailable: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tiers only render when known-word highlighting is also on, matching
|
||||
* getKnownWordMaturityEnabled in anki-integration/known-word-maturity.
|
||||
*/
|
||||
export function isKnownWordMaturityLegendEnabled(runtimeOptions: RuntimeOptionState[]): boolean {
|
||||
const isOn = (id: RuntimeOptionId): boolean =>
|
||||
runtimeOptions.some((option) => option.id === id && option.value === true);
|
||||
return (
|
||||
isOn('subtitle.annotation.knownWords.highlightEnabled') &&
|
||||
isOn('subtitle.annotation.knownWords.maturityEnabled')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maturity coloring is a live runtime toggle, so the color legend reads it from
|
||||
* runtime options instead of the resolved subtitle style. A missing or failing
|
||||
* runtime-options call falls back to the flat known-word color.
|
||||
*/
|
||||
async function readKnownWordMaturityEnabled(): Promise<boolean> {
|
||||
try {
|
||||
return isKnownWordMaturityLegendEnabled(await window.electronAPI.getRuntimeOptions());
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function formatBindingHint(info: SessionHelpBindingInfo): string {
|
||||
if (info.bindingKey === 'KeyK' && info.fallbackUsed) {
|
||||
return info.fallbackUnavailable ? 'Y-K (fallback and conflict noted)' : 'Y-K (fallback)';
|
||||
@@ -219,22 +246,29 @@ export function createSessionHelpModal(
|
||||
|
||||
async function render(): Promise<boolean> {
|
||||
try {
|
||||
const [sessionBindings, styleConfig, markWatchedKey, subtitleSidebarToggleKey] =
|
||||
await Promise.all([
|
||||
window.electronAPI.getSessionBindings(),
|
||||
window.electronAPI.getSubtitleStyle(),
|
||||
window.electronAPI.getMarkWatchedKey(),
|
||||
window.electronAPI
|
||||
.getSubtitleSidebarSnapshot()
|
||||
.then((snapshot) => snapshot.config.toggleKey)
|
||||
.catch(() => undefined),
|
||||
]);
|
||||
const [
|
||||
sessionBindings,
|
||||
styleConfig,
|
||||
markWatchedKey,
|
||||
subtitleSidebarToggleKey,
|
||||
knownWordMaturityEnabled,
|
||||
] = await Promise.all([
|
||||
window.electronAPI.getSessionBindings(),
|
||||
window.electronAPI.getSubtitleStyle(),
|
||||
window.electronAPI.getMarkWatchedKey(),
|
||||
window.electronAPI
|
||||
.getSubtitleSidebarSnapshot()
|
||||
.then((snapshot) => snapshot.config.toggleKey)
|
||||
.catch(() => undefined),
|
||||
readKnownWordMaturityEnabled(),
|
||||
]);
|
||||
|
||||
helpSections = buildSessionHelpSections({
|
||||
sessionBindings,
|
||||
markWatchedKey,
|
||||
subtitleSidebarToggleKey,
|
||||
subtitleStyle: styleConfig ?? {},
|
||||
knownWordMaturityEnabled,
|
||||
});
|
||||
applyFilterAndRender();
|
||||
return true;
|
||||
|
||||
@@ -116,6 +116,10 @@ export type RendererState = {
|
||||
subtitleSidebarPausedByHover: boolean;
|
||||
|
||||
knownWordColor: string;
|
||||
knownWordMaturityNewColor: string;
|
||||
knownWordMaturityLearningColor: string;
|
||||
knownWordMaturityYoungColor: string;
|
||||
knownWordMaturityMatureColor: string;
|
||||
nPlusOneColor: string;
|
||||
nameMatchEnabled: boolean;
|
||||
nameMatchColor: string;
|
||||
@@ -240,6 +244,10 @@ export function createRendererState(): RendererState {
|
||||
subtitleSidebarPausedByHover: false,
|
||||
|
||||
knownWordColor: '#a6da95',
|
||||
knownWordMaturityNewColor: '#ee99a0',
|
||||
knownWordMaturityLearningColor: '#b7bdf8',
|
||||
knownWordMaturityYoungColor: '#91d7e3',
|
||||
knownWordMaturityMatureColor: '#a6da95',
|
||||
nPlusOneColor: '#c6a0f6',
|
||||
nameMatchEnabled: false,
|
||||
nameMatchColor: '#f5bde6',
|
||||
|
||||
@@ -1503,6 +1503,24 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
|
||||
color: var(--subtitle-known-word-color, #a6da95);
|
||||
}
|
||||
|
||||
/* Anki maturity tiers ride on word-known and only override its color, so
|
||||
hover/selection rules keyed on word-known keep applying. */
|
||||
#subtitleRoot .word.word-known.word-maturity-new {
|
||||
color: var(--subtitle-maturity-new-color, #ee99a0);
|
||||
}
|
||||
|
||||
#subtitleRoot .word.word-known.word-maturity-learning {
|
||||
color: var(--subtitle-maturity-learning-color, #b7bdf8);
|
||||
}
|
||||
|
||||
#subtitleRoot .word.word-known.word-maturity-young {
|
||||
color: var(--subtitle-maturity-young-color, #91d7e3);
|
||||
}
|
||||
|
||||
#subtitleRoot .word.word-known.word-maturity-mature {
|
||||
color: var(--subtitle-maturity-mature-color, #a6da95);
|
||||
}
|
||||
|
||||
#subtitleRoot .word.word-n-plus-one {
|
||||
color: var(--subtitle-n-plus-one-color, #c6a0f6);
|
||||
}
|
||||
@@ -1680,6 +1698,30 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] {
|
||||
-webkit-text-fill-color: var(--subtitle-known-word-color, #a6da95) !important;
|
||||
}
|
||||
|
||||
#subtitleRoot .word.word-known.word-maturity-new::selection,
|
||||
#subtitleRoot .word.word-known.word-maturity-new .c::selection {
|
||||
color: var(--subtitle-maturity-new-color, #ee99a0) !important;
|
||||
-webkit-text-fill-color: var(--subtitle-maturity-new-color, #ee99a0) !important;
|
||||
}
|
||||
|
||||
#subtitleRoot .word.word-known.word-maturity-learning::selection,
|
||||
#subtitleRoot .word.word-known.word-maturity-learning .c::selection {
|
||||
color: var(--subtitle-maturity-learning-color, #b7bdf8) !important;
|
||||
-webkit-text-fill-color: var(--subtitle-maturity-learning-color, #b7bdf8) !important;
|
||||
}
|
||||
|
||||
#subtitleRoot .word.word-known.word-maturity-young::selection,
|
||||
#subtitleRoot .word.word-known.word-maturity-young .c::selection {
|
||||
color: var(--subtitle-maturity-young-color, #91d7e3) !important;
|
||||
-webkit-text-fill-color: var(--subtitle-maturity-young-color, #91d7e3) !important;
|
||||
}
|
||||
|
||||
#subtitleRoot .word.word-known.word-maturity-mature::selection,
|
||||
#subtitleRoot .word.word-known.word-maturity-mature .c::selection {
|
||||
color: var(--subtitle-maturity-mature-color, #a6da95) !important;
|
||||
-webkit-text-fill-color: var(--subtitle-maturity-mature-color, #a6da95) !important;
|
||||
}
|
||||
|
||||
#subtitleRoot .word.word-n-plus-one::selection,
|
||||
#subtitleRoot .word.word-n-plus-one .c::selection {
|
||||
color: var(--subtitle-n-plus-one-color, #c6a0f6) !important;
|
||||
|
||||
@@ -204,3 +204,51 @@ test('computeWordClass skips frequency class when rank is out of topX', () => {
|
||||
|
||||
assert.equal(actual, 'word');
|
||||
});
|
||||
|
||||
test('computeWordClass adds the maturity tier class alongside word-known', () => {
|
||||
const token = createToken({
|
||||
isKnown: true,
|
||||
knownMaturity: 'mature',
|
||||
surface: '猫',
|
||||
});
|
||||
|
||||
assert.equal(computeWordClass(token), 'word word-known word-maturity-mature');
|
||||
});
|
||||
|
||||
test('computeWordClass keeps the plain known class when no maturity tier is set', () => {
|
||||
const token = createToken({
|
||||
isKnown: true,
|
||||
surface: '猫',
|
||||
});
|
||||
|
||||
assert.equal(computeWordClass(token), 'word word-known');
|
||||
});
|
||||
|
||||
test('computeWordClass composes maturity with JLPT classes', () => {
|
||||
const token = createToken({
|
||||
isKnown: true,
|
||||
knownMaturity: 'young',
|
||||
jlptLevel: 'N3',
|
||||
surface: '猫',
|
||||
});
|
||||
|
||||
assert.equal(computeWordClass(token), 'word word-known word-maturity-young word-jlpt-n3');
|
||||
});
|
||||
|
||||
test('computeWordClass gives n+1 and name matches precedence over maturity', () => {
|
||||
const nPlusOne = createToken({
|
||||
isKnown: true,
|
||||
knownMaturity: 'mature',
|
||||
isNPlusOneTarget: true,
|
||||
surface: '犬',
|
||||
});
|
||||
assert.equal(computeWordClass(nPlusOne), 'word word-n-plus-one');
|
||||
|
||||
const nameMatch = createToken({
|
||||
isKnown: true,
|
||||
knownMaturity: 'mature',
|
||||
surface: 'アクア',
|
||||
}) as MergedToken & { isNameMatch?: boolean };
|
||||
nameMatch.isNameMatch = true;
|
||||
assert.equal(computeWordClass(nameMatch, { nameMatchEnabled: true }), 'word word-name-match');
|
||||
});
|
||||
|
||||
@@ -1445,3 +1445,70 @@ test('secondary subtitle root CSS caps height so hover-pause band stays a top st
|
||||
assert.match(secondaryRootBlock, /max-height:\s*6em;/);
|
||||
assert.match(secondaryRootBlock, /overflow:\s*hidden;/);
|
||||
});
|
||||
|
||||
test('applySubtitleStyle sets known-word maturity color variables', () => {
|
||||
const restoreDocument = installFakeDocument();
|
||||
try {
|
||||
const subtitleRoot = new FakeElement('div');
|
||||
const subtitleContainer = new FakeElement('div');
|
||||
const secondarySubRoot = new FakeElement('div');
|
||||
const secondarySubContainer = new FakeElement('div');
|
||||
const ctx = {
|
||||
state: createRendererState(),
|
||||
dom: {
|
||||
subtitleRoot,
|
||||
subtitleContainer,
|
||||
secondarySubRoot,
|
||||
secondarySubContainer,
|
||||
},
|
||||
} as never;
|
||||
|
||||
const renderer = createSubtitleRenderer(ctx);
|
||||
renderer.applySubtitleStyle({
|
||||
knownWordMaturityColors: {
|
||||
new: '#111111',
|
||||
learning: '#222222',
|
||||
young: '#333333',
|
||||
mature: '#444444',
|
||||
},
|
||||
} as never);
|
||||
|
||||
const values = (subtitleRoot.style as unknown as { values?: Map<string, string> }).values;
|
||||
assert.equal(values?.get('--subtitle-maturity-new-color'), '#111111');
|
||||
assert.equal(values?.get('--subtitle-maturity-learning-color'), '#222222');
|
||||
assert.equal(values?.get('--subtitle-maturity-young-color'), '#333333');
|
||||
assert.equal(values?.get('--subtitle-maturity-mature-color'), '#444444');
|
||||
} finally {
|
||||
restoreDocument();
|
||||
}
|
||||
});
|
||||
|
||||
test('applySubtitleStyle falls back to default maturity colors', () => {
|
||||
const restoreDocument = installFakeDocument();
|
||||
try {
|
||||
const subtitleRoot = new FakeElement('div');
|
||||
const subtitleContainer = new FakeElement('div');
|
||||
const secondarySubRoot = new FakeElement('div');
|
||||
const secondarySubContainer = new FakeElement('div');
|
||||
const ctx = {
|
||||
state: createRendererState(),
|
||||
dom: {
|
||||
subtitleRoot,
|
||||
subtitleContainer,
|
||||
secondarySubRoot,
|
||||
secondarySubContainer,
|
||||
},
|
||||
} as never;
|
||||
|
||||
const renderer = createSubtitleRenderer(ctx);
|
||||
renderer.applySubtitleStyle({} as never);
|
||||
|
||||
const values = (subtitleRoot.style as unknown as { values?: Map<string, string> }).values;
|
||||
assert.equal(values?.get('--subtitle-maturity-new-color'), '#ee99a0');
|
||||
assert.equal(values?.get('--subtitle-maturity-learning-color'), '#b7bdf8');
|
||||
assert.equal(values?.get('--subtitle-maturity-young-color'), '#91d7e3');
|
||||
assert.equal(values?.get('--subtitle-maturity-mature-color'), '#a6da95');
|
||||
} finally {
|
||||
restoreDocument();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -597,6 +597,11 @@ export function computeWordClass(
|
||||
classes.push('word-n-plus-one');
|
||||
} else if (token.isKnown) {
|
||||
classes.push('word-known');
|
||||
// The maturity class rides on word-known so hover/selection rules keyed
|
||||
// on word-known keep applying; it only overrides the color.
|
||||
if (token.knownMaturity) {
|
||||
classes.push(`word-maturity-${token.knownMaturity}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPrioritizedNameMatch(token, resolvedTokenRenderSettings) && token.jlptLevel) {
|
||||
@@ -867,11 +872,39 @@ export function createSubtitleRenderer(ctx: RendererContext) {
|
||||
: {}),
|
||||
};
|
||||
|
||||
const maturityColorOverrides = style.knownWordMaturityColors;
|
||||
const maturityColors = {
|
||||
new: sanitizeHexColor(maturityColorOverrides?.new, ctx.state.knownWordMaturityNewColor),
|
||||
learning: sanitizeHexColor(
|
||||
maturityColorOverrides?.learning,
|
||||
ctx.state.knownWordMaturityLearningColor,
|
||||
),
|
||||
young: sanitizeHexColor(maturityColorOverrides?.young, ctx.state.knownWordMaturityYoungColor),
|
||||
mature: sanitizeHexColor(
|
||||
maturityColorOverrides?.mature,
|
||||
ctx.state.knownWordMaturityMatureColor,
|
||||
),
|
||||
};
|
||||
|
||||
ctx.state.knownWordColor = knownWordColor;
|
||||
ctx.state.knownWordMaturityNewColor = maturityColors.new;
|
||||
ctx.state.knownWordMaturityLearningColor = maturityColors.learning;
|
||||
ctx.state.knownWordMaturityYoungColor = maturityColors.young;
|
||||
ctx.state.knownWordMaturityMatureColor = maturityColors.mature;
|
||||
ctx.state.nPlusOneColor = nPlusOneColor;
|
||||
ctx.state.nameMatchEnabled = nameMatchEnabled;
|
||||
ctx.state.nameMatchColor = nameMatchColor;
|
||||
ctx.dom.subtitleRoot.style.setProperty('--subtitle-known-word-color', knownWordColor);
|
||||
ctx.dom.subtitleRoot.style.setProperty('--subtitle-maturity-new-color', maturityColors.new);
|
||||
ctx.dom.subtitleRoot.style.setProperty(
|
||||
'--subtitle-maturity-learning-color',
|
||||
maturityColors.learning,
|
||||
);
|
||||
ctx.dom.subtitleRoot.style.setProperty('--subtitle-maturity-young-color', maturityColors.young);
|
||||
ctx.dom.subtitleRoot.style.setProperty(
|
||||
'--subtitle-maturity-mature-color',
|
||||
maturityColors.mature,
|
||||
);
|
||||
ctx.dom.subtitleRoot.style.setProperty('--subtitle-n-plus-one-color', nPlusOneColor);
|
||||
ctx.dom.subtitleRoot.style.setProperty('--subtitle-name-match-color', nameMatchColor);
|
||||
ctx.dom.subtitleRoot.style.setProperty('--subtitle-hover-token-color', hoverTokenColor);
|
||||
|
||||
Reference in New Issue
Block a user