feat(config): add subtitle CSS editor, nPlusOne.enabled flag, and fix se

- subtitleStyle.css / subtitleStyle.secondary.css replace flat style fields in the settings window
- ankiConnect.nPlusOne.enabled gates known-word cache independently of knownWords.highlightEnabled
- Settings search now covers all categories, narrows on multi-word terms, and hides editor-owned fields
- Default note-type picker to Kiku then Lapis; rename isLapis.sentenceCardModel default to "Lapis"
This commit is contained in:
2026-05-17 04:13:02 -07:00
parent a5eba369b4
commit a9f66329ce
39 changed files with 1147 additions and 86 deletions
+49
View File
@@ -426,6 +426,55 @@ test('applySubtitleStyle stores secondary background styles in hover-aware css v
}
});
test('applySubtitleStyle applies primary and secondary css declaration objects', () => {
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({
fontSize: 35,
css: {
'font-size': '42px',
'text-wrap': 'balance',
'--subtitle-outline': '1px',
},
secondary: {
fontSize: 24,
css: {
'font-size': '28px',
'text-transform': 'uppercase',
},
},
} as never);
const primaryValues = (subtitleRoot.style as unknown as { values?: Map<string, string> })
.values;
const secondaryValues = (secondarySubRoot.style as unknown as { values?: Map<string, string> })
.values;
assert.equal(primaryValues?.get('font-size'), '42px');
assert.equal(primaryValues?.get('text-wrap'), 'balance');
assert.equal(primaryValues?.get('--subtitle-outline'), '1px');
assert.equal(secondaryValues?.get('font-size'), '28px');
assert.equal(secondaryValues?.get('text-transform'), 'uppercase');
} finally {
restoreDocument();
}
});
test('annotated subtitle tokens inherit configured base subtitle typography', () => {
const restoreDocument = installFakeDocument();
try {
+45 -5
View File
@@ -158,6 +158,32 @@ function applyInlineStyleDeclarations(
}
}
function normalizeCssDeclarationObject(value: unknown): Record<string, string> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return {};
}
const declarations: Record<string, string> = {};
for (const [key, rawValue] of Object.entries(value)) {
if (typeof rawValue !== 'string') continue;
const cssValue = rawValue.trim();
if (cssValue.length > 0) declarations[key] = cssValue;
}
return declarations;
}
function applySubtitleCssDeclarations(
root: HTMLElement,
container: HTMLElement,
declarations: Record<string, string>,
): void {
applyInlineStyleDeclarations(root, declarations, CONTAINER_STYLE_KEYS);
applyInlineStyleDeclarations(
container,
pickInlineStyleDeclarations(declarations, CONTAINER_STYLE_KEYS),
);
}
function pickInlineStyleDeclarations(
declarations: Record<string, unknown>,
includedKeys: ReadonlySet<string>,
@@ -172,7 +198,9 @@ function pickInlineStyleDeclarations(
const CONTAINER_STYLE_KEYS = new Set<string>([
'background',
'background-color',
'backgroundColor',
'backdrop-filter',
'backdropFilter',
'WebkitBackdropFilter',
'webkitBackdropFilter',
@@ -180,7 +208,7 @@ const CONTAINER_STYLE_KEYS = new Set<string>([
]);
function resolveSecondaryBackgroundColor(declarations: Record<string, unknown>): string {
for (const key of ['backgroundColor', 'background']) {
for (const key of ['backgroundColor', 'background-color', 'background']) {
const value = declarations[key];
if (typeof value === 'string' && value.trim().length > 0) {
return value.trim();
@@ -193,6 +221,7 @@ function resolveSecondaryBackgroundColor(declarations: Record<string, unknown>):
function resolveSecondaryBackdropFilter(declarations: Record<string, unknown>): string {
for (const key of [
'backdropFilter',
'backdrop-filter',
'WebkitBackdropFilter',
'webkitBackdropFilter',
'-webkit-backdrop-filter',
@@ -762,20 +791,26 @@ export function createSubtitleRenderer(ctx: RendererContext) {
'--subtitle-frequency-band-5-color',
frequencyBandedColors[4],
);
applySubtitleCssDeclarations(
ctx.dom.subtitleRoot,
ctx.dom.subtitleContainer,
normalizeCssDeclarationObject(style.css),
);
const secondaryStyle = style.secondary;
if (!secondaryStyle) return;
const secondaryStyleDeclarations = secondaryStyle as Record<string, unknown>;
const secondaryCssDeclarations = normalizeCssDeclarationObject(secondaryStyle.css);
applyInlineStyleDeclarations(
ctx.dom.secondarySubRoot,
secondaryStyleDeclarations,
CONTAINER_STYLE_KEYS,
);
const secondaryContainerStyleDeclarations = pickInlineStyleDeclarations(
secondaryStyleDeclarations,
CONTAINER_STYLE_KEYS,
);
const secondaryContainerStyleDeclarations = {
...pickInlineStyleDeclarations(secondaryStyleDeclarations, CONTAINER_STYLE_KEYS),
...pickInlineStyleDeclarations(secondaryCssDeclarations, CONTAINER_STYLE_KEYS),
};
ctx.dom.secondarySubContainer.style.setProperty(
'--secondary-sub-background-color',
resolveSecondaryBackgroundColor(secondaryContainerStyleDeclarations),
@@ -800,6 +835,11 @@ export function createSubtitleRenderer(ctx: RendererContext) {
if (secondaryStyle.fontStyle) {
ctx.dom.secondarySubRoot.style.fontStyle = secondaryStyle.fontStyle;
}
applySubtitleCssDeclarations(
ctx.dom.secondarySubRoot,
ctx.dom.secondarySubContainer,
secondaryCssDeclarations,
);
}
return {