diff --git a/changes/settings-modal-layout.md b/changes/settings-modal-layout.md index 15479a99..8ec6c8b2 100644 --- a/changes/settings-modal-layout.md +++ b/changes/settings-modal-layout.md @@ -1 +1,4 @@ +type: changed +area: config + - Settings: reorganized playback, shortcut, WebSocket, tracking, Jellyfin, character dictionary, and Discord presence controls in the settings modal. diff --git a/src/config/anki-connect-nplusone-migration.ts b/src/config/anki-connect-nplusone-migration.ts index aeee284d..5c0b8d76 100644 --- a/src/config/anki-connect-nplusone-migration.ts +++ b/src/config/anki-connect-nplusone-migration.ts @@ -30,6 +30,8 @@ const LEGACY_N_PLUS_ONE_PATH_MAP = { nPlusOne: 'subtitleStyle.nPlusOneColor', } as const; +const hexColorPattern = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/; + function propertyKey(propertyNode: JsoncNode): string | undefined { return propertyNode.children?.[0]?.value; } @@ -82,6 +84,12 @@ function normalizeLegacyDecks(value: unknown): unknown { return normalized; } +function asLegacyColor(value: unknown): string | undefined { + if (typeof value !== 'string') return undefined; + const text = value.trim(); + return hexColorPattern.test(text) ? text : undefined; +} + function buildLegacyNPlusOneMigrationOperations(root: JsoncNode | undefined): { operations: ConfigSettingsPatchOperation[]; hasLegacy: boolean; @@ -90,9 +98,9 @@ function buildLegacyNPlusOneMigrationOperations(root: JsoncNode | undefined): { const ankiConnect = propertyValue(findLastProperty(root, 'ankiConnect')); const nPlusOneProperties = findProperties(ankiConnect, 'nPlusOne'); const nPlusOneObjects = nPlusOneProperties.map(propertyValue).filter(Boolean) as JsoncNode[]; - if (nPlusOneObjects.length === 0) { - return { operations, hasLegacy: false }; - } + const knownWords = propertyValue(findLastProperty(ankiConnect, 'knownWords')); + const knownWordsColorNode = propertyValue(findLastProperty(knownWords, 'color')); + const knownWordsColor = knownWordsColorNode ? getNodeValue(knownWordsColorNode) : undefined; const canonicalNPlusOneValues = new Map(); const legacyValues = new Map(); @@ -144,6 +152,22 @@ function buildLegacyNPlusOneMigrationOperations(root: JsoncNode | undefined): { }); } + const legacyKnownWordsColor = asLegacyColor(knownWordsColor); + if (legacyKnownWordsColor !== undefined) { + hasLegacy = true; + if (!hasPath(root, 'subtitleStyle.knownWordColor')) { + operations.push({ + op: 'set', + path: 'subtitleStyle.knownWordColor', + value: legacyKnownWordsColor, + }); + } + operations.push({ + op: 'reset', + path: 'ankiConnect.knownWords.color', + }); + } + return { operations, hasLegacy }; } diff --git a/src/config/config.test.ts b/src/config/config.test.ts index cb3d1889..cb58e9bc 100644 --- a/src/config/config.test.ts +++ b/src/config/config.test.ts @@ -2096,6 +2096,7 @@ test('migrates legacy ankiConnect n+1 color value to subtitleStyle', () => { subtitleStyle: { nPlusOneColor?: string; knownWordColor?: string }; }; assert.equal(parsed.subtitleStyle.nPlusOneColor, '#c6a0f6'); + assert.equal(parsed.subtitleStyle.knownWordColor, '#a6da95'); assert.equal(Object.hasOwn(parsed.ankiConnect.nPlusOne ?? {}, 'nPlusOne'), false); }); diff --git a/src/config/settings/registry.ts b/src/config/settings/registry.ts index 5b23ec6d..093a5668 100644 --- a/src/config/settings/registry.ts +++ b/src/config/settings/registry.ts @@ -229,7 +229,7 @@ const LABEL_OVERRIDES: Record = { 'mpv.pauseUntilOverlayReady': 'Pause Until Overlay Ready', 'mpv.aniskipEnabled': 'Enable AniSkip', 'mpv.aniskipButtonKey': 'AniSkip Button Key', - 'discordPresence.updateIntervalMs': 'Update Interval Seconds', + 'discordPresence.updateIntervalMs': 'Update Interval (ms)', }; const DESCRIPTION_OVERRIDES: Record = { @@ -248,7 +248,7 @@ const DESCRIPTION_OVERRIDES: Record = { 'subtitleSidebar.css': 'CSS declarations applied to the subtitle sidebar. Includes color, background-color, all font properties, and sidebar CSS variables.', 'discordPresence.updateIntervalMs': - 'Minimum interval between presence payload updates, in seconds.', + 'Minimum interval between presence payload updates, in milliseconds.', }; function isRecord(value: unknown): value is Record {