mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 12:55:16 -07:00
feat(config): add configuration window (#70)
This commit is contained in:
@@ -84,6 +84,29 @@ test('accepts knownWords.addMinedWordsImmediately boolean override', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('enables n+1 for existing configs with known-word highlighting enabled', () => {
|
||||
const { context } = makeContext({
|
||||
knownWords: { highlightEnabled: true },
|
||||
});
|
||||
|
||||
applyAnkiConnectResolution(context);
|
||||
|
||||
assert.equal(context.resolved.ankiConnect.knownWords.highlightEnabled, true);
|
||||
assert.equal(context.resolved.ankiConnect.nPlusOne.enabled, true);
|
||||
});
|
||||
|
||||
test('keeps explicit n+1 disabled when known-word highlighting is enabled', () => {
|
||||
const { context } = makeContext({
|
||||
knownWords: { highlightEnabled: true },
|
||||
nPlusOne: { enabled: false },
|
||||
});
|
||||
|
||||
applyAnkiConnectResolution(context);
|
||||
|
||||
assert.equal(context.resolved.ankiConnect.knownWords.highlightEnabled, true);
|
||||
assert.equal(context.resolved.ankiConnect.nPlusOne.enabled, false);
|
||||
});
|
||||
|
||||
test('converts legacy knownWords.decks array to object with default fields', () => {
|
||||
const { context, warnings } = makeContext({
|
||||
knownWords: { decks: ['Core Deck'] },
|
||||
|
||||
@@ -654,7 +654,6 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
||||
const nPlusOneConfig = isObject(ac.nPlusOne) ? (ac.nPlusOne as Record<string, unknown>) : {};
|
||||
|
||||
const knownWordsHighlightEnabled = asBoolean(knownWordsConfig.highlightEnabled);
|
||||
const legacyNPlusOneHighlightEnabled = asBoolean(nPlusOneConfig.highlightEnabled);
|
||||
if (knownWordsHighlightEnabled !== undefined) {
|
||||
context.resolved.ankiConnect.knownWords.highlightEnabled = knownWordsHighlightEnabled;
|
||||
} else if (knownWordsConfig.highlightEnabled !== undefined) {
|
||||
@@ -666,23 +665,6 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
||||
);
|
||||
context.resolved.ankiConnect.knownWords.highlightEnabled =
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.highlightEnabled;
|
||||
} else if (legacyNPlusOneHighlightEnabled !== undefined) {
|
||||
context.resolved.ankiConnect.knownWords.highlightEnabled = legacyNPlusOneHighlightEnabled;
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.highlightEnabled',
|
||||
nPlusOneConfig.highlightEnabled,
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.highlightEnabled,
|
||||
'Legacy key is deprecated; use ankiConnect.knownWords.highlightEnabled',
|
||||
);
|
||||
} else if (nPlusOneConfig.highlightEnabled !== undefined) {
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.highlightEnabled',
|
||||
nPlusOneConfig.highlightEnabled,
|
||||
context.resolved.ankiConnect.knownWords.highlightEnabled,
|
||||
'Expected boolean.',
|
||||
);
|
||||
context.resolved.ankiConnect.knownWords.highlightEnabled =
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.highlightEnabled;
|
||||
} else {
|
||||
const legacyBehaviorNPlusOneHighlightEnabled = asBoolean(behavior.nPlusOneHighlightEnabled);
|
||||
if (legacyBehaviorNPlusOneHighlightEnabled !== undefined) {
|
||||
@@ -701,15 +683,10 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
||||
}
|
||||
|
||||
const knownWordsRefreshMinutes = asNumber(knownWordsConfig.refreshMinutes);
|
||||
const legacyNPlusOneRefreshMinutes = asNumber(nPlusOneConfig.refreshMinutes);
|
||||
const hasValidKnownWordsRefreshMinutes =
|
||||
knownWordsRefreshMinutes !== undefined &&
|
||||
Number.isInteger(knownWordsRefreshMinutes) &&
|
||||
knownWordsRefreshMinutes > 0;
|
||||
const hasValidLegacyNPlusOneRefreshMinutes =
|
||||
legacyNPlusOneRefreshMinutes !== undefined &&
|
||||
Number.isInteger(legacyNPlusOneRefreshMinutes) &&
|
||||
legacyNPlusOneRefreshMinutes > 0;
|
||||
if (knownWordsRefreshMinutes !== undefined) {
|
||||
if (hasValidKnownWordsRefreshMinutes) {
|
||||
context.resolved.ankiConnect.knownWords.refreshMinutes = knownWordsRefreshMinutes;
|
||||
@@ -723,25 +700,6 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
||||
context.resolved.ankiConnect.knownWords.refreshMinutes =
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.refreshMinutes;
|
||||
}
|
||||
} else if (legacyNPlusOneRefreshMinutes !== undefined) {
|
||||
if (hasValidLegacyNPlusOneRefreshMinutes) {
|
||||
context.resolved.ankiConnect.knownWords.refreshMinutes = legacyNPlusOneRefreshMinutes;
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.refreshMinutes',
|
||||
nPlusOneConfig.refreshMinutes,
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.refreshMinutes,
|
||||
'Legacy key is deprecated; use ankiConnect.knownWords.refreshMinutes',
|
||||
);
|
||||
} else {
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.refreshMinutes',
|
||||
nPlusOneConfig.refreshMinutes,
|
||||
context.resolved.ankiConnect.knownWords.refreshMinutes,
|
||||
'Expected a positive integer.',
|
||||
);
|
||||
context.resolved.ankiConnect.knownWords.refreshMinutes =
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.refreshMinutes;
|
||||
}
|
||||
} else if (asNumber(behavior.nPlusOneRefreshMinutes) !== undefined) {
|
||||
const legacyBehaviorNPlusOneRefreshMinutes = asNumber(behavior.nPlusOneRefreshMinutes);
|
||||
const hasValidLegacyRefreshMinutes =
|
||||
@@ -789,6 +747,23 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.addMinedWordsImmediately;
|
||||
}
|
||||
|
||||
const nPlusOneEnabled = asBoolean(nPlusOneConfig.enabled);
|
||||
if (nPlusOneEnabled !== undefined) {
|
||||
context.resolved.ankiConnect.nPlusOne.enabled = nPlusOneEnabled;
|
||||
} else if (nPlusOneConfig.enabled !== undefined) {
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.enabled',
|
||||
nPlusOneConfig.enabled,
|
||||
context.resolved.ankiConnect.nPlusOne.enabled,
|
||||
'Expected boolean.',
|
||||
);
|
||||
context.resolved.ankiConnect.nPlusOne.enabled = DEFAULT_CONFIG.ankiConnect.nPlusOne.enabled;
|
||||
} else if (context.resolved.ankiConnect.knownWords.highlightEnabled === true) {
|
||||
context.resolved.ankiConnect.nPlusOne.enabled = true;
|
||||
} else {
|
||||
context.resolved.ankiConnect.nPlusOne.enabled = DEFAULT_CONFIG.ankiConnect.nPlusOne.enabled;
|
||||
}
|
||||
|
||||
const nPlusOneMinSentenceWords = asNumber(nPlusOneConfig.minSentenceWords);
|
||||
const hasValidNPlusOneMinSentenceWords =
|
||||
nPlusOneMinSentenceWords !== undefined &&
|
||||
@@ -813,12 +788,9 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
||||
}
|
||||
|
||||
const knownWordsMatchMode = asString(knownWordsConfig.matchMode);
|
||||
const legacyNPlusOneMatchMode = asString(nPlusOneConfig.matchMode);
|
||||
const legacyBehaviorNPlusOneMatchMode = asString(behavior.nPlusOneMatchMode);
|
||||
const hasValidKnownWordsMatchMode =
|
||||
knownWordsMatchMode === 'headword' || knownWordsMatchMode === 'surface';
|
||||
const hasValidLegacyNPlusOneMatchMode =
|
||||
legacyNPlusOneMatchMode === 'headword' || legacyNPlusOneMatchMode === 'surface';
|
||||
const hasValidLegacyMatchMode =
|
||||
legacyBehaviorNPlusOneMatchMode === 'headword' || legacyBehaviorNPlusOneMatchMode === 'surface';
|
||||
if (hasValidKnownWordsMatchMode) {
|
||||
@@ -832,25 +804,6 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
||||
);
|
||||
context.resolved.ankiConnect.knownWords.matchMode =
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.matchMode;
|
||||
} else if (legacyNPlusOneMatchMode !== undefined) {
|
||||
if (hasValidLegacyNPlusOneMatchMode) {
|
||||
context.resolved.ankiConnect.knownWords.matchMode = legacyNPlusOneMatchMode;
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.matchMode',
|
||||
nPlusOneConfig.matchMode,
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.matchMode,
|
||||
'Legacy key is deprecated; use ankiConnect.knownWords.matchMode',
|
||||
);
|
||||
} else {
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.matchMode',
|
||||
nPlusOneConfig.matchMode,
|
||||
context.resolved.ankiConnect.knownWords.matchMode,
|
||||
"Expected 'headword' or 'surface'.",
|
||||
);
|
||||
context.resolved.ankiConnect.knownWords.matchMode =
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.matchMode;
|
||||
}
|
||||
} else if (legacyBehaviorNPlusOneMatchMode !== undefined) {
|
||||
if (hasValidLegacyMatchMode) {
|
||||
context.resolved.ankiConnect.knownWords.matchMode = legacyBehaviorNPlusOneMatchMode;
|
||||
@@ -882,7 +835,6 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
||||
'Word Reading',
|
||||
];
|
||||
const knownWordsDecks = knownWordsConfig.decks;
|
||||
const legacyNPlusOneDecks = nPlusOneConfig.decks;
|
||||
if (isObject(knownWordsDecks)) {
|
||||
const resolved: Record<string, string[]> = {};
|
||||
for (const [deck, fields] of Object.entries(knownWordsDecks as Record<string, unknown>)) {
|
||||
@@ -926,67 +878,31 @@ export function applyAnkiConnectResolution(context: ResolveContext): void {
|
||||
context.resolved.ankiConnect.knownWords.decks,
|
||||
'Expected an object mapping deck names to field arrays.',
|
||||
);
|
||||
} else if (Array.isArray(legacyNPlusOneDecks)) {
|
||||
const normalized = legacyNPlusOneDecks
|
||||
.filter((entry): entry is string => typeof entry === 'string')
|
||||
.map((entry) => entry.trim())
|
||||
.filter((entry) => entry.length > 0);
|
||||
const resolved: Record<string, string[]> = {};
|
||||
for (const deck of new Set(normalized)) {
|
||||
resolved[deck] = DEFAULT_FIELDS;
|
||||
}
|
||||
context.resolved.ankiConnect.knownWords.decks = resolved;
|
||||
if (normalized.length > 0) {
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.decks',
|
||||
legacyNPlusOneDecks,
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.decks,
|
||||
'Legacy key is deprecated; use ankiConnect.knownWords.decks with object format',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const nPlusOneHighlightColor = asColor(nPlusOneConfig.nPlusOne);
|
||||
if (nPlusOneHighlightColor !== undefined) {
|
||||
context.resolved.ankiConnect.nPlusOne.nPlusOne = nPlusOneHighlightColor;
|
||||
} else if (nPlusOneConfig.nPlusOne !== undefined) {
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.nPlusOne',
|
||||
nPlusOneConfig.nPlusOne,
|
||||
context.resolved.ankiConnect.nPlusOne.nPlusOne,
|
||||
'Expected a hex color value.',
|
||||
);
|
||||
context.resolved.ankiConnect.nPlusOne.nPlusOne = DEFAULT_CONFIG.ankiConnect.nPlusOne.nPlusOne;
|
||||
}
|
||||
const rawSubtitleStyle = isObject(context.src.subtitleStyle)
|
||||
? (context.src.subtitleStyle as Record<string, unknown>)
|
||||
: {};
|
||||
const hasCanonicalKnownWordColor = rawSubtitleStyle.knownWordColor !== undefined;
|
||||
|
||||
const knownWordsColor = asColor(knownWordsConfig.color);
|
||||
const legacyNPlusOneKnownWordColor = asColor(nPlusOneConfig.knownWord);
|
||||
if (knownWordsColor !== undefined) {
|
||||
context.resolved.ankiConnect.knownWords.color = knownWordsColor;
|
||||
if (!hasCanonicalKnownWordColor) {
|
||||
context.resolved.subtitleStyle.knownWordColor = knownWordsColor;
|
||||
}
|
||||
context.warn(
|
||||
'ankiConnect.knownWords.color',
|
||||
knownWordsConfig.color,
|
||||
context.resolved.subtitleStyle.knownWordColor,
|
||||
'Legacy key is deprecated; use subtitleStyle.knownWordColor',
|
||||
);
|
||||
} else if (knownWordsConfig.color !== undefined) {
|
||||
context.warn(
|
||||
'ankiConnect.knownWords.color',
|
||||
knownWordsConfig.color,
|
||||
context.resolved.ankiConnect.knownWords.color,
|
||||
context.resolved.subtitleStyle.knownWordColor,
|
||||
'Expected a hex color value.',
|
||||
);
|
||||
context.resolved.ankiConnect.knownWords.color = DEFAULT_CONFIG.ankiConnect.knownWords.color;
|
||||
} else if (legacyNPlusOneKnownWordColor !== undefined) {
|
||||
context.resolved.ankiConnect.knownWords.color = legacyNPlusOneKnownWordColor;
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.knownWord',
|
||||
nPlusOneConfig.knownWord,
|
||||
DEFAULT_CONFIG.ankiConnect.knownWords.color,
|
||||
'Legacy key is deprecated; use ankiConnect.knownWords.color',
|
||||
);
|
||||
} else if (nPlusOneConfig.knownWord !== undefined) {
|
||||
context.warn(
|
||||
'ankiConnect.nPlusOne.knownWord',
|
||||
nPlusOneConfig.knownWord,
|
||||
context.resolved.ankiConnect.knownWords.color,
|
||||
'Expected a hex color value.',
|
||||
);
|
||||
context.resolved.ankiConnect.knownWords.color = DEFAULT_CONFIG.ankiConnect.knownWords.color;
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -273,13 +273,6 @@ export function applyCoreDomainConfig(context: ResolveContext): void {
|
||||
}
|
||||
|
||||
if (isObject(src.subsync)) {
|
||||
const mode = src.subsync.defaultMode;
|
||||
if (mode === 'auto' || mode === 'manual') {
|
||||
resolved.subsync.defaultMode = mode;
|
||||
} else if (mode !== undefined) {
|
||||
warn('subsync.defaultMode', mode, resolved.subsync.defaultMode, 'Expected auto or manual.');
|
||||
}
|
||||
|
||||
const alass = asString(src.subsync.alass_path);
|
||||
if (alass !== undefined) resolved.subsync.alass_path = alass;
|
||||
const ffsubsync = asString(src.subsync.ffsubsync_path);
|
||||
|
||||
@@ -253,6 +253,97 @@ export function applyIntegrationConfig(context: ResolveContext): void {
|
||||
`Expected one of: ${MPV_LAUNCH_MODE_VALUES.map((value) => `'${value}'`).join(', ')}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const socketPath = asString(src.mpv.socketPath);
|
||||
if (socketPath !== undefined && socketPath.trim().length > 0) {
|
||||
resolved.mpv.socketPath = socketPath.trim();
|
||||
} else if (src.mpv.socketPath !== undefined) {
|
||||
warn(
|
||||
'mpv.socketPath',
|
||||
src.mpv.socketPath,
|
||||
resolved.mpv.socketPath,
|
||||
'Expected non-empty string.',
|
||||
);
|
||||
}
|
||||
|
||||
const backend = asString(src.mpv.backend);
|
||||
if (
|
||||
backend === 'auto' ||
|
||||
backend === 'hyprland' ||
|
||||
backend === 'sway' ||
|
||||
backend === 'x11' ||
|
||||
backend === 'macos' ||
|
||||
backend === 'windows'
|
||||
) {
|
||||
resolved.mpv.backend = backend;
|
||||
} else if (src.mpv.backend !== undefined) {
|
||||
warn(
|
||||
'mpv.backend',
|
||||
src.mpv.backend,
|
||||
resolved.mpv.backend,
|
||||
'Expected auto, hyprland, sway, x11, macos, or windows.',
|
||||
);
|
||||
}
|
||||
|
||||
const autoStartSubMiner = asBoolean(src.mpv.autoStartSubMiner);
|
||||
if (autoStartSubMiner !== undefined) {
|
||||
resolved.mpv.autoStartSubMiner = autoStartSubMiner;
|
||||
} else if (src.mpv.autoStartSubMiner !== undefined) {
|
||||
warn(
|
||||
'mpv.autoStartSubMiner',
|
||||
src.mpv.autoStartSubMiner,
|
||||
resolved.mpv.autoStartSubMiner,
|
||||
'Expected boolean.',
|
||||
);
|
||||
}
|
||||
|
||||
const pauseUntilOverlayReady = asBoolean(src.mpv.pauseUntilOverlayReady);
|
||||
if (pauseUntilOverlayReady !== undefined) {
|
||||
resolved.mpv.pauseUntilOverlayReady = pauseUntilOverlayReady;
|
||||
} else if (src.mpv.pauseUntilOverlayReady !== undefined) {
|
||||
warn(
|
||||
'mpv.pauseUntilOverlayReady',
|
||||
src.mpv.pauseUntilOverlayReady,
|
||||
resolved.mpv.pauseUntilOverlayReady,
|
||||
'Expected boolean.',
|
||||
);
|
||||
}
|
||||
|
||||
const subminerBinaryPath = asString(src.mpv.subminerBinaryPath);
|
||||
if (subminerBinaryPath !== undefined) {
|
||||
resolved.mpv.subminerBinaryPath = subminerBinaryPath.trim();
|
||||
} else if (src.mpv.subminerBinaryPath !== undefined) {
|
||||
warn(
|
||||
'mpv.subminerBinaryPath',
|
||||
src.mpv.subminerBinaryPath,
|
||||
resolved.mpv.subminerBinaryPath,
|
||||
'Expected string.',
|
||||
);
|
||||
}
|
||||
|
||||
const aniskipEnabled = asBoolean(src.mpv.aniskipEnabled);
|
||||
if (aniskipEnabled !== undefined) {
|
||||
resolved.mpv.aniskipEnabled = aniskipEnabled;
|
||||
} else if (src.mpv.aniskipEnabled !== undefined) {
|
||||
warn(
|
||||
'mpv.aniskipEnabled',
|
||||
src.mpv.aniskipEnabled,
|
||||
resolved.mpv.aniskipEnabled,
|
||||
'Expected boolean.',
|
||||
);
|
||||
}
|
||||
|
||||
const aniskipButtonKey = asString(src.mpv.aniskipButtonKey);
|
||||
if (aniskipButtonKey !== undefined && aniskipButtonKey.trim().length > 0) {
|
||||
resolved.mpv.aniskipButtonKey = aniskipButtonKey.trim();
|
||||
} else if (src.mpv.aniskipButtonKey !== undefined) {
|
||||
warn(
|
||||
'mpv.aniskipButtonKey',
|
||||
src.mpv.aniskipButtonKey,
|
||||
resolved.mpv.aniskipButtonKey,
|
||||
'Expected non-empty string.',
|
||||
);
|
||||
}
|
||||
} else if (src.mpv !== undefined) {
|
||||
warn('mpv', src.mpv, resolved.mpv, 'Expected object.');
|
||||
}
|
||||
|
||||
@@ -10,6 +10,40 @@ import {
|
||||
isObject,
|
||||
} from './shared';
|
||||
|
||||
function asCssDeclarations(value: unknown): Record<string, string> | undefined {
|
||||
if (!isObject(value)) return undefined;
|
||||
|
||||
const declarations: Record<string, string> = {};
|
||||
for (const [property, declarationValue] of Object.entries(value)) {
|
||||
if (typeof declarationValue !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
if (declarationValue.trim().length > 0) {
|
||||
declarations[property] = declarationValue.trim();
|
||||
}
|
||||
}
|
||||
return declarations;
|
||||
}
|
||||
|
||||
const SUBTITLE_HOVER_TOKEN_COLOR_CSS_PROPERTY = '--subtitle-hover-token-color';
|
||||
const SUBTITLE_HOVER_TOKEN_BACKGROUND_CSS_PROPERTY = '--subtitle-hover-token-background-color';
|
||||
|
||||
function applySubtitleHoverTokenCssCompatibility(
|
||||
subtitleStyle: ResolvedConfig['subtitleStyle'],
|
||||
): void {
|
||||
const hoverTokenColor = asCssColor(subtitleStyle.css[SUBTITLE_HOVER_TOKEN_COLOR_CSS_PROPERTY]);
|
||||
if (hoverTokenColor !== undefined) {
|
||||
subtitleStyle.hoverTokenColor = hoverTokenColor;
|
||||
}
|
||||
|
||||
const hoverTokenBackgroundColor = asCssColor(
|
||||
subtitleStyle.css[SUBTITLE_HOVER_TOKEN_BACKGROUND_CSS_PROPERTY],
|
||||
);
|
||||
if (hoverTokenBackgroundColor !== undefined) {
|
||||
subtitleStyle.hoverTokenBackgroundColor = hoverTokenBackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
export function applySubtitleDomainConfig(context: ResolveContext): void {
|
||||
const { src, resolved, warn } = context;
|
||||
|
||||
@@ -157,6 +191,10 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
|
||||
resolved.subtitleStyle.hoverTokenBackgroundColor;
|
||||
const fallbackSubtitleStyleNameMatchEnabled = resolved.subtitleStyle.nameMatchEnabled;
|
||||
const fallbackSubtitleStyleNameMatchColor = resolved.subtitleStyle.nameMatchColor;
|
||||
const fallbackSubtitleStyleKnownWordColor = resolved.subtitleStyle.knownWordColor;
|
||||
const fallbackSubtitleStyleNPlusOneColor = resolved.subtitleStyle.nPlusOneColor;
|
||||
const fallbackSubtitleStyleCss = { ...resolved.subtitleStyle.css };
|
||||
const fallbackSubtitleStyleSecondaryCss = { ...resolved.subtitleStyle.secondary.css };
|
||||
const fallbackFrequencyDictionary = {
|
||||
...resolved.subtitleStyle.frequencyDictionary,
|
||||
};
|
||||
@@ -209,6 +247,35 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
|
||||
);
|
||||
}
|
||||
|
||||
const css = asCssDeclarations((src.subtitleStyle as { css?: unknown }).css);
|
||||
if (css !== undefined) {
|
||||
resolved.subtitleStyle.css = css;
|
||||
} else if ((src.subtitleStyle as { css?: unknown }).css !== undefined) {
|
||||
resolved.subtitleStyle.css = fallbackSubtitleStyleCss;
|
||||
warn(
|
||||
'subtitleStyle.css',
|
||||
(src.subtitleStyle as { css?: unknown }).css,
|
||||
resolved.subtitleStyle.css,
|
||||
'Expected an object whose values are CSS declaration strings.',
|
||||
);
|
||||
}
|
||||
|
||||
const rawSecondary = isObject(src.subtitleStyle.secondary)
|
||||
? (src.subtitleStyle.secondary as { css?: unknown })
|
||||
: undefined;
|
||||
const secondaryCss = asCssDeclarations(rawSecondary?.css);
|
||||
if (secondaryCss !== undefined) {
|
||||
resolved.subtitleStyle.secondary.css = secondaryCss;
|
||||
} else if (rawSecondary?.css !== undefined) {
|
||||
resolved.subtitleStyle.secondary.css = fallbackSubtitleStyleSecondaryCss;
|
||||
warn(
|
||||
'subtitleStyle.secondary.css',
|
||||
rawSecondary.css,
|
||||
resolved.subtitleStyle.secondary.css,
|
||||
'Expected an object whose values are CSS declaration strings.',
|
||||
);
|
||||
}
|
||||
|
||||
const preserveLineBreaks = asBoolean(
|
||||
(src.subtitleStyle as { preserveLineBreaks?: unknown }).preserveLineBreaks,
|
||||
);
|
||||
@@ -301,6 +368,8 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
|
||||
);
|
||||
}
|
||||
|
||||
applySubtitleHoverTokenCssCompatibility(resolved.subtitleStyle);
|
||||
|
||||
const nameMatchColor = asColor(
|
||||
(src.subtitleStyle as { nameMatchColor?: unknown }).nameMatchColor,
|
||||
);
|
||||
@@ -333,6 +402,34 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
|
||||
);
|
||||
}
|
||||
|
||||
const knownWordColor = asColor(
|
||||
(src.subtitleStyle as { knownWordColor?: unknown }).knownWordColor,
|
||||
);
|
||||
if (knownWordColor !== undefined) {
|
||||
resolved.subtitleStyle.knownWordColor = knownWordColor;
|
||||
} else if ((src.subtitleStyle as { knownWordColor?: unknown }).knownWordColor !== undefined) {
|
||||
resolved.subtitleStyle.knownWordColor = fallbackSubtitleStyleKnownWordColor;
|
||||
warn(
|
||||
'subtitleStyle.knownWordColor',
|
||||
(src.subtitleStyle as { knownWordColor?: unknown }).knownWordColor,
|
||||
resolved.subtitleStyle.knownWordColor,
|
||||
'Expected hex color.',
|
||||
);
|
||||
}
|
||||
|
||||
const nPlusOneColor = asColor((src.subtitleStyle as { nPlusOneColor?: unknown }).nPlusOneColor);
|
||||
if (nPlusOneColor !== undefined) {
|
||||
resolved.subtitleStyle.nPlusOneColor = nPlusOneColor;
|
||||
} else if ((src.subtitleStyle as { nPlusOneColor?: unknown }).nPlusOneColor !== undefined) {
|
||||
resolved.subtitleStyle.nPlusOneColor = fallbackSubtitleStyleNPlusOneColor;
|
||||
warn(
|
||||
'subtitleStyle.nPlusOneColor',
|
||||
(src.subtitleStyle as { nPlusOneColor?: unknown }).nPlusOneColor,
|
||||
resolved.subtitleStyle.nPlusOneColor,
|
||||
'Expected hex color.',
|
||||
);
|
||||
}
|
||||
|
||||
const frequencyDictionary = isObject(
|
||||
(src.subtitleStyle as { frequencyDictionary?: unknown }).frequencyDictionary,
|
||||
)
|
||||
@@ -445,6 +542,19 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
|
||||
...(src.subtitleSidebar as ResolvedConfig['subtitleSidebar']),
|
||||
};
|
||||
|
||||
const css = asCssDeclarations((src.subtitleSidebar as { css?: unknown }).css);
|
||||
if (css !== undefined) {
|
||||
resolved.subtitleSidebar.css = css;
|
||||
} else if ((src.subtitleSidebar as { css?: unknown }).css !== undefined) {
|
||||
resolved.subtitleSidebar.css = fallback.css;
|
||||
warn(
|
||||
'subtitleSidebar.css',
|
||||
(src.subtitleSidebar as { css?: unknown }).css,
|
||||
resolved.subtitleSidebar.css,
|
||||
'Expected an object whose values are CSS declaration strings.',
|
||||
);
|
||||
}
|
||||
|
||||
const enabled = asBoolean((src.subtitleSidebar as { enabled?: unknown }).enabled);
|
||||
if (enabled !== undefined) {
|
||||
resolved.subtitleSidebar.enabled = enabled;
|
||||
|
||||
@@ -55,6 +55,33 @@ test('subtitleSidebar accepts zero opacity', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('subtitleSidebar css declarations accept string declaration maps and warn on invalid values', () => {
|
||||
const valid = createResolveContext({
|
||||
subtitleSidebar: {
|
||||
css: {
|
||||
'font-size': '18px',
|
||||
color: '#ffffff',
|
||||
},
|
||||
},
|
||||
});
|
||||
applySubtitleDomainConfig(valid.context);
|
||||
assert.deepEqual(valid.context.resolved.subtitleSidebar.css, {
|
||||
'font-size': '18px',
|
||||
color: '#ffffff',
|
||||
});
|
||||
|
||||
const invalid = createResolveContext({
|
||||
subtitleSidebar: {
|
||||
css: {
|
||||
color: 42,
|
||||
} as never,
|
||||
},
|
||||
});
|
||||
applySubtitleDomainConfig(invalid.context);
|
||||
assert.deepEqual(invalid.context.resolved.subtitleSidebar.css, {});
|
||||
assert.ok(invalid.warnings.some((warning) => warning.path === 'subtitleSidebar.css'));
|
||||
});
|
||||
|
||||
test('subtitleSidebar falls back and warns on invalid values', () => {
|
||||
const { context, warnings } = createResolveContext({
|
||||
subtitleSidebar: {
|
||||
|
||||
@@ -28,6 +28,68 @@ test('subtitleStyle preserveLineBreaks falls back while merge is preserved', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('subtitleStyle css declarations accept string declaration maps and warn on invalid values', () => {
|
||||
const valid = createResolveContext({
|
||||
subtitleStyle: {
|
||||
css: {
|
||||
'font-size': '42px',
|
||||
'text-wrap': 'balance',
|
||||
'--subtitle-hover-token-color': '#c6a0f6',
|
||||
'--subtitle-hover-token-background-color': 'transparent',
|
||||
},
|
||||
secondary: {
|
||||
css: {
|
||||
'text-transform': 'uppercase',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
applySubtitleDomainConfig(valid.context);
|
||||
assert.deepEqual(valid.context.resolved.subtitleStyle.css, {
|
||||
'font-size': '42px',
|
||||
'text-wrap': 'balance',
|
||||
'--subtitle-hover-token-color': '#c6a0f6',
|
||||
'--subtitle-hover-token-background-color': 'transparent',
|
||||
});
|
||||
assert.equal(valid.context.resolved.subtitleStyle.hoverTokenColor, '#c6a0f6');
|
||||
assert.equal(valid.context.resolved.subtitleStyle.hoverTokenBackgroundColor, 'transparent');
|
||||
assert.deepEqual(valid.context.resolved.subtitleStyle.secondary.css, {
|
||||
'text-transform': 'uppercase',
|
||||
});
|
||||
|
||||
const invalid = createResolveContext({
|
||||
subtitleStyle: {
|
||||
css: {
|
||||
'font-size': 42,
|
||||
} as never,
|
||||
secondary: {
|
||||
css: 'font-size: 28px;' as never,
|
||||
},
|
||||
},
|
||||
});
|
||||
applySubtitleDomainConfig(invalid.context);
|
||||
assert.deepEqual(invalid.context.resolved.subtitleStyle.css, {});
|
||||
assert.deepEqual(invalid.context.resolved.subtitleStyle.secondary.css, {});
|
||||
assert.ok(invalid.warnings.some((warning) => warning.path === 'subtitleStyle.css'));
|
||||
assert.ok(invalid.warnings.some((warning) => warning.path === 'subtitleStyle.secondary.css'));
|
||||
});
|
||||
|
||||
test('subtitleStyle hover css compatibility ignores invalid color declarations', () => {
|
||||
const { context } = createResolveContext({
|
||||
subtitleStyle: {
|
||||
css: {
|
||||
'--subtitle-hover-token-color': 'purple',
|
||||
'--subtitle-hover-token-background-color': '#363a4fd6',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
applySubtitleDomainConfig(context);
|
||||
|
||||
assert.equal(context.resolved.subtitleStyle.hoverTokenColor, '#f4dbd6');
|
||||
assert.equal(context.resolved.subtitleStyle.hoverTokenBackgroundColor, '#363a4fd6');
|
||||
});
|
||||
|
||||
test('subtitleStyle autoPauseVideoOnHover falls back on invalid value', () => {
|
||||
const { context, warnings } = createResolveContext({
|
||||
subtitleStyle: {
|
||||
@@ -100,7 +162,7 @@ test('subtitleStyle nameMatchEnabled falls back on invalid value', () => {
|
||||
|
||||
applySubtitleDomainConfig(context);
|
||||
|
||||
assert.equal(context.resolved.subtitleStyle.nameMatchEnabled, true);
|
||||
assert.equal(context.resolved.subtitleStyle.nameMatchEnabled, false);
|
||||
assert.ok(
|
||||
warnings.some(
|
||||
(warning) =>
|
||||
@@ -155,6 +217,55 @@ test('subtitleStyle nameMatchColor accepts valid values and warns on invalid', (
|
||||
);
|
||||
});
|
||||
|
||||
test('subtitleStyle knownWordColor accepts valid values and warns on invalid', () => {
|
||||
const valid = createResolveContext({
|
||||
subtitleStyle: {
|
||||
knownWordColor: '#ed8796',
|
||||
},
|
||||
});
|
||||
applySubtitleDomainConfig(valid.context);
|
||||
assert.equal(valid.context.resolved.subtitleStyle.knownWordColor, '#ed8796');
|
||||
|
||||
const invalid = createResolveContext({
|
||||
subtitleStyle: {
|
||||
knownWordColor: 'pink',
|
||||
},
|
||||
});
|
||||
applySubtitleDomainConfig(invalid.context);
|
||||
assert.equal(invalid.context.resolved.subtitleStyle.knownWordColor, '#a6da95');
|
||||
assert.ok(
|
||||
invalid.warnings.some(
|
||||
(warning) =>
|
||||
warning.path === 'subtitleStyle.knownWordColor' &&
|
||||
warning.message === 'Expected hex color.',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('subtitleStyle nPlusOneColor accepts valid values and warns on invalid', () => {
|
||||
const valid = createResolveContext({
|
||||
subtitleStyle: {
|
||||
nPlusOneColor: '#ed8796',
|
||||
},
|
||||
});
|
||||
applySubtitleDomainConfig(valid.context);
|
||||
assert.equal(valid.context.resolved.subtitleStyle.nPlusOneColor, '#ed8796');
|
||||
|
||||
const invalid = createResolveContext({
|
||||
subtitleStyle: {
|
||||
nPlusOneColor: 'pink',
|
||||
},
|
||||
});
|
||||
applySubtitleDomainConfig(invalid.context);
|
||||
assert.equal(invalid.context.resolved.subtitleStyle.nPlusOneColor, '#c6a0f6');
|
||||
assert.ok(
|
||||
invalid.warnings.some(
|
||||
(warning) =>
|
||||
warning.path === 'subtitleStyle.nPlusOneColor' && warning.message === 'Expected hex color.',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('subtitleStyle frequencyDictionary.matchMode accepts valid values and warns on invalid', () => {
|
||||
const valid = createResolveContext({
|
||||
subtitleStyle: {
|
||||
|
||||
Reference in New Issue
Block a user