diff --git a/AGENTS.md b/AGENTS.md index 2c1f926..5917b9c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -376,6 +376,23 @@ const value = short object literals, or short callback bodies that already fit the local line-length and delimiter rules. Expanding compact local code without a readability need is a minor style violation. +- In TypeScript and TSX, treat short object literals, short returned objects, + short typed object shapes, and short function-call object arguments as + compact associative syntax by default. If they fit locally, keep the opening + `{` on the same line as the first property and keep the closing `}` on the + same line as the final property. Do not explode them into vertical + Prettier-style blocks without a concrete readability or line-length reason. +- In TypeScript and TSX, when a compact associative form is broken across two + lines, keep the opening `{` with the first property and the closing `}` with + the final property. A shape like `{\n key: value,\n}` is wrong even when + line length would permit it. +- In TypeScript and TSX, when a short call such as `apiGet<...> (...)`, + `apiPatch<...> (...)`, `setState (...)`, `updateClientSettings (...)`, or + `window.dispatchEvent (...)` already fits within the local style, do not + add extra vertical layers around the object argument or callback body. +- In TypeScript and TSX, after editing a file, inspect the file tail and + remove meaningless blank lines before `export default` or the end of file. + A stray empty line before `export default` is a minor style offence. - Use correct British English spelling for new identifiers, filenames, component names, helper names, comments, and developer-facing prose unless editing an already established American-English API that must keep its @@ -470,6 +487,92 @@ setDirtyStates ( Penalty: minor offence when semantics stay intact. It is not necessarily broken, but it ignores the repository's compact associative style. +#### Prettier-style exploded object literals that should stay compact + +Bad: + +```ts +const next = { + enabled: keyboard?.enabled !== false, + bindings: normaliseKeyboardBindings (keyboard?.bindings), +} +``` + +Good: + +```ts +const next = { + enabled: keyboard?.enabled !== false, + bindings: normaliseKeyboardBindings (keyboard?.bindings) } +``` + +Also good when short enough: + +```ts +const next = { enabled: keyboard?.enabled !== false, + bindings: normaliseKeyboardBindings (keyboard?.bindings) } +``` + +Rule: for compact associative syntax, keep `{` with the first property and `}` +with the final property unless line length truly forces a different shape. +Do not mechanically apply the common Prettier block form. +Penalty: medium-severity offence. The code still runs, but it violates a core +repository formatting habit that must be preserved consistently. + +#### Exploded object arguments inside short calls + +Bad: + +```ts +updateClientSettings (settings => ({ + ...settings, + keyboard: next, +})) +``` + +Good: + +```ts +updateClientSettings (settings => ({ + ...settings, + keyboard: next })) +``` + +Rule: a call-expression closing `)` must not drift onto its own line, and a +short object argument should not be exploded more than necessary. Keep the +callback's returned object compact when it fits. +Penalty: medium-severity offence. This usually means the edit was formatted +mechanically instead of by reading the local style. + +#### Stray blank line before `export default` + +Bad: + +```ts +const Component: FC = () => { + return
+} + + +export default Component +``` + +Good: + +```ts +const Component: FC = () => { + return
+} + +export default Component +``` + +Rule: do not leave an extra empty line between the last top-level declaration +and `export default` unless some surrounding file structure genuinely requires +visual separation. +Penalty: minor offence. It is small, but it is easy to avoid and should not +recur. + ### Frontend delimiter decision table Use this table before accepting any edited TypeScript or TSX hunk. The table is diff --git a/frontend/src/lib/settings.ts b/frontend/src/lib/settings.ts index 3714bcf..8b943e2 100644 --- a/frontend/src/lib/settings.ts +++ b/frontend/src/lib/settings.ts @@ -152,10 +152,9 @@ export type ClientSettings = { keyboard?: ClientKeyboardSettings panes?: Record lists?: Partial> - theatre?: { - layoutMode?: TheatreLayoutMode - tagFlow?: TheatreTagFlow } - gekanator?: { backgroundMotion?: GekanatorBackgroundMotionMode } + theatre?: { layoutMode?: TheatreLayoutMode + tagFlow?: TheatreTagFlow } + gekanator?: { backgroundMotion?: GekanatorBackgroundMotionMode } reducedMotion?: string embedAutoLoad?: string thumbnailMode?: string } @@ -273,23 +272,20 @@ export const DARK_THEME_TOKENS: ThemeTokens = { topNavMenuLink: '#93c5fd', tagColours: DEFAULT_DARK_THEME_TAG_COLOURS } export const BUILTIN_THEMES: Record = { - light: { - id: 'light', - name: 'ライト・モード', - builtin: true, - baseTheme: 'light', - tokens: LIGHT_THEME_TOKENS }, - dark: { - id: 'dark', - name: 'ダーク・モード', - builtin: true, - baseTheme: 'dark', - tokens: DARK_THEME_TOKENS } } + light: { id: 'light', + name: 'ライト・モード', + builtin: true, + baseTheme: 'light', + tokens: LIGHT_THEME_TOKENS }, + dark: { id: 'dark', + name: 'ダーク・モード', + builtin: true, + baseTheme: 'dark', + tokens: DARK_THEME_TOKENS } } const LEGACY_THEATRE_LAYOUT_STORAGE_KEY = 'theatre-layout-mode' const LEGACY_THEATRE_TAG_FLOW_STORAGE_KEY = 'theatre-tag-flow' -const LEGACY_GEKANATOR_BACKGROUND_MOTION_STORAGE_KEY = - 'gekanator:background-motion:v1' +const LEGACY_GEKANATOR_BACKGROUND_MOTION_STORAGE_KEY = 'gekanator:background-motion:v1' const LEGACY_MIGRATED_THEME_ID = 'custom:legacy-migrated' @@ -304,18 +300,16 @@ const createCustomThemeId = (): `custom:${ string }` => { export const fetchUserSettings = async (): Promise => { const raw = await apiGet<{ - theme: UserSettings['theme'] + theme: UserSettings['theme'] auto_fetch_title: UserSettings['autoFetchTitle'] auto_fetch_thumbnail: UserSettings['autoFetchThumbnail'] - wiki_editor_mode: UserSettings['wikiEditorMode'] - }> ('/users/settings') + wiki_editor_mode: UserSettings['wikiEditorMode'] }> ('/users/settings') return { theme: raw.theme, autoFetchTitle: raw.auto_fetch_title, autoFetchThumbnail: raw.auto_fetch_thumbnail, - wikiEditorMode: raw.wiki_editor_mode, - } + wikiEditorMode: raw.wiki_editor_mode } } @@ -326,15 +320,13 @@ export const updateUserSettings = async ( theme: UserSettings['theme'] auto_fetch_title: UserSettings['autoFetchTitle'] auto_fetch_thumbnail: UserSettings['autoFetchThumbnail'] - wiki_editor_mode: UserSettings['wikiEditorMode'] - }> ('/users/settings', settings) + wiki_editor_mode: UserSettings['wikiEditorMode'] }> ('/users/settings', settings) return { theme: raw.theme, autoFetchTitle: raw.auto_fetch_title, autoFetchThumbnail: raw.auto_fetch_thumbnail, - wikiEditorMode: raw.wiki_editor_mode, - } + wikiEditorMode: raw.wiki_editor_mode } } @@ -364,8 +356,7 @@ export const saveClientSettings = (settings: ClientSettings): void => { if (typeof window !== 'undefined') { window.dispatchEvent (new CustomEvent (CLIENT_SETTINGS_EVENT, { - detail: settings, - })) + detail: settings })) } } @@ -428,25 +419,22 @@ const normaliseKeyboardBindings = ( if (!(bindings) || typeof bindings !== 'object') return { } - return Object.keys (DEFAULT_KEY_BINDINGS).reduce< - Partial> - > ( - (normalised, actionId) => { - const value = (bindings as Record)[actionId] - if (value === null) - { - normalised[actionId as ShortcutActionId] = null - return normalised - } + return ( + Object.keys (DEFAULT_KEY_BINDINGS) + .reduce>> ((normalised, actionId) => { + const value = (bindings as Record)[actionId] + if (value === null) + { + normalised[actionId as ShortcutActionId] = null + return normalised + } - const binding = normaliseKeyBinding (value) - if (binding != null) - normalised[actionId as ShortcutActionId] = binding + const binding = normaliseKeyBinding (value) + if (binding != null) + normalised[actionId as ShortcutActionId] = binding - return normalised - }, - { }, - ) + return normalised + }, { })) } @@ -455,8 +443,7 @@ export const getClientKeyboardSettings = (): ClientKeyboardSettings => { return { enabled: keyboard?.enabled !== false, - bindings: normaliseKeyboardBindings (keyboard?.bindings), - } + bindings: normaliseKeyboardBindings (keyboard?.bindings) } } @@ -495,10 +482,7 @@ export const applyClientAnimationMode = (mode: ClientAnimationMode): void => { export const setClientAnimationMode = ( animation: ClientAnimationMode, ): ClientBehaviorSettings => - setClientBehaviorSettings ({ - ...getClientBehaviorSettings (), - animation, - }) + setClientBehaviorSettings ({ ...getClientBehaviorSettings (), animation }) export const getClientLinkPreloadMode = (): ClientLinkPreloadMode => @@ -509,10 +493,7 @@ export const getClientLinkPreloadMode = (): ClientLinkPreloadMode => export const setClientLinkPreloadMode = ( linkPreload: ClientLinkPreloadMode, ): ClientBehaviorSettings => - setClientBehaviorSettings ({ - ...getClientBehaviorSettings (), - linkPreload, - }) + setClientBehaviorSettings ({ ...getClientBehaviorSettings (), linkPreload }) export const getClientTagRelationDisplayMode = (): ClientTagRelationDisplayMode => @@ -523,10 +504,7 @@ export const getClientTagRelationDisplayMode = (): ClientTagRelationDisplayMode export const setClientTagRelationDisplayMode = ( tagRelationDisplay: ClientTagRelationDisplayMode, ): ClientBehaviorSettings => - setClientBehaviorSettings ({ - ...getClientBehaviorSettings (), - tagRelationDisplay, - }) + setClientBehaviorSettings ({ ...getClientBehaviorSettings (), tagRelationDisplay }) export const getClientEmbedAutoLoadMode = (): ClientEmbedAutoLoadMode => @@ -538,10 +516,7 @@ export const getClientEmbedAutoLoadMode = (): ClientEmbedAutoLoadMode => export const setClientEmbedAutoLoadMode = ( embedAutoLoad: ClientEmbedAutoLoadMode, ): ClientBehaviorSettings => - setClientBehaviorSettings ({ - ...getClientBehaviorSettings (), - embedAutoLoad, - }) + setClientBehaviorSettings ({ ...getClientBehaviorSettings (), embedAutoLoad }) export const getClientThumbnailMode = (): ClientThumbnailMode => @@ -553,10 +528,7 @@ export const getClientThumbnailMode = (): ClientThumbnailMode => export const setClientThumbnailMode = ( thumbnailMode: ClientThumbnailMode, ): ClientBehaviorSettings => - setClientBehaviorSettings ({ - ...getClientBehaviorSettings (), - thumbnailMode, - }) + setClientBehaviorSettings ({ ...getClientBehaviorSettings (), thumbnailMode }) export const getClientBehaviorSettings = (): ClientBehaviorSettings => ({ @@ -564,8 +536,7 @@ export const getClientBehaviorSettings = (): ClientBehaviorSettings => ({ linkPreload: getClientLinkPreloadMode (), tagRelationDisplay: getClientTagRelationDisplayMode (), embedAutoLoad: getClientEmbedAutoLoadMode (), - thumbnailMode: getClientThumbnailMode (), -}) + thumbnailMode: getClientThumbnailMode () }) export const setClientBehaviorSettings = ( @@ -574,30 +545,21 @@ export const setClientBehaviorSettings = ( const next: ClientBehaviorSettings = { animation: ( normaliseAnimationMode (behavior.animation) - ?? getClientAnimationMode () - ), + ?? getClientAnimationMode ()), linkPreload: ( normaliseLinkPreloadMode (behavior.linkPreload) - ?? getClientLinkPreloadMode () - ), + ?? getClientLinkPreloadMode ()), tagRelationDisplay: ( normaliseTagRelationDisplayMode (behavior.tagRelationDisplay) - ?? getClientTagRelationDisplayMode () - ), + ?? getClientTagRelationDisplayMode ()), embedAutoLoad: ( normaliseEmbedAutoLoadMode (behavior.embedAutoLoad) - ?? getClientEmbedAutoLoadMode () - ), + ?? getClientEmbedAutoLoadMode ()), thumbnailMode: ( normaliseThumbnailMode (behavior.thumbnailMode) - ?? getClientThumbnailMode () - ), - } + ?? getClientThumbnailMode ()) } - updateClientSettings (settings => ({ - ...settings, - behavior: next, - })) + updateClientSettings (settings => ({ ...settings, behavior: next })) applyClientAnimationMode (next.animation ?? 'normal') return next } @@ -608,13 +570,9 @@ export const setClientKeyboardSettings = ( ): ClientKeyboardSettings => { const next: ClientKeyboardSettings = { enabled: keyboard.enabled !== false, - bindings: normaliseKeyboardBindings (keyboard.bindings), - } + bindings: normaliseKeyboardBindings (keyboard.bindings) } - updateClientSettings (settings => ({ - ...settings, - keyboard: next, - })) + updateClientSettings (settings => ({ ...settings, keyboard: next })) return next } @@ -626,15 +584,14 @@ export const getEffectiveKeyBindings = ( ): Record => Object.keys (DEFAULT_KEY_BINDINGS).reduce> ( (effectiveBindings, actionId) => { - const key = actionId as ShortcutActionId - effectiveBindings[key] = - Object.prototype.hasOwnProperty.call (bindings, key) - ? bindings[key] ?? null - : DEFAULT_KEY_BINDINGS[key] - return effectiveBindings + const key = actionId as ShortcutActionId + effectiveBindings[key] = + Object.prototype.hasOwnProperty.call (bindings, key) + ? bindings[key] ?? null + : DEFAULT_KEY_BINDINGS[key] + return effectiveBindings }, - { ...DEFAULT_KEY_BINDINGS }, - ) + { ...DEFAULT_KEY_BINDINGS }) export const setClientKeyBinding = ( @@ -645,11 +602,7 @@ export const setClientKeyBinding = ( return setClientKeyboardSettings ({ ...current, - bindings: { - ...(current.bindings ?? { }), - [actionId]: binding, - }, - }) + bindings: { ...(current.bindings ?? { }), [actionId]: binding } }) } @@ -660,20 +613,14 @@ export const resetClientKeyBinding = ( const nextBindings = { ...(current.bindings ?? { }) } delete nextBindings[actionId] - return setClientKeyboardSettings ({ - ...current, - bindings: nextBindings, - }) + return setClientKeyboardSettings ({ ...current, bindings: nextBindings }) } export const resetAllClientKeyBindings = (): ClientKeyboardSettings => { const current = getClientKeyboardSettings () - return setClientKeyboardSettings ({ - ...current, - bindings: { }, - }) + return setClientKeyboardSettings ({ ...current, bindings: { } }) } @@ -696,21 +643,20 @@ const themeCopyName = (baseTheme: BuiltinThemeId): string => const appearanceFromSettings = (): ClientAppearanceSettings => loadClientSettings ().appearance ?? { } + let cachedUserThemeSlots: UserThemeSlotMap = { } export const hasStoredClientThemeSelection = (): boolean => { const appearance = appearanceFromSettings () - return ( - appearance.themeMode != null - || appearance.activeLightThemeSlotNo != null - || appearance.activeDarkThemeSlotNo != null - || appearance.activeThemeSlot != null - || appearance.activeThemeId != null - || appearance.customThemes != null - || appearance.theme != null - || appearance.tagColours != null - ) + return (appearance.themeMode != null + || appearance.activeLightThemeSlotNo != null + || appearance.activeDarkThemeSlotNo != null + || appearance.activeThemeSlot != null + || appearance.activeThemeId != null + || appearance.customThemes != null + || appearance.theme != null + || appearance.tagColours != null) } @@ -723,12 +669,10 @@ const mixHexColour = ( const src = source.slice (1) const dst = target.slice (1) const channels = [0, 2, 4].map (offset => { - const srcValue = parseInt (src.slice (offset, offset + 2), 16) - const dstValue = parseInt (dst.slice (offset, offset + 2), 16) - const mixed = Math.round ( - srcValue + (dstValue - srcValue) * clampedRatio, - ) - return mixed.toString (16).padStart (2, '0') + const srcValue = parseInt (src.slice (offset, offset + 2), 16) + const dstValue = parseInt (dst.slice (offset, offset + 2), 16) + const mixed = Math.round (srcValue + (dstValue - srcValue) * clampedRatio) + return mixed.toString (16).padStart (2, '0') }) return `#${ channels.join ('') }` @@ -749,10 +693,8 @@ const legacyThemeSlotSelection = (): ThemeSlotSelection => { return 'builtin:light' if (appearance.activeThemeId === 'dark') return 'builtin:dark' - if ( - typeof appearance.activeThemeId === 'string' - && customThemes[appearance.activeThemeId] != null - ) + if (typeof appearance.activeThemeId === 'string' + && customThemes[appearance.activeThemeId] != null) return `builtin:${ customThemes[appearance.activeThemeId].baseTheme }` if (appearance.theme === 'light') return 'builtin:light' @@ -764,11 +706,9 @@ const legacyThemeSlotSelection = (): ThemeSlotSelection => { const legacyThemeMode = (): ClientThemeMode => normaliseThemeMode (appearanceFromSettings ().theme) - ?? ( - legacyThemeSlotSelection () === 'system' + ?? (legacyThemeSlotSelection () === 'system' ? 'system' - : (baseThemeOfSelection (legacyThemeSlotSelection ()) ?? 'system') - ) + : (baseThemeOfSelection (legacyThemeSlotSelection ()) ?? 'system')) const legacyThemeSlotNoFor = ( @@ -778,7 +718,7 @@ const legacyThemeSlotNoFor = ( if (isUserThemeSlotSelection (legacySelection)) { - const [_, selectionBaseTheme, slotNo] = legacySelection.split (':') + const [, selectionBaseTheme, slotNo] = legacySelection.split (':') if (selectionBaseTheme === baseTheme) return Number (slotNo) as UserThemeSlotNo } @@ -798,20 +738,12 @@ export const setClientActiveThemeSlot = ( ): void => { updateClientSettings (settings => ({ ...settings, - appearance: { - ...(settings.appearance ?? { }), - activeThemeSlot, - }, - })) + appearance: { ...(settings.appearance ?? { }), activeThemeSlot } })) } -export const getClientThemeMode = (): UserSettings['theme'] => { - return ( - normaliseThemeMode (appearanceFromSettings ().themeMode) - ?? legacyThemeMode () - ) -} +export const getClientThemeMode = (): UserSettings['theme'] => + normaliseThemeMode (appearanceFromSettings ().themeMode) ?? legacyThemeMode () export const getClientActiveLightThemeSlotNo = (): UserThemeSlotNo => @@ -827,10 +759,9 @@ export const getClientActiveDarkThemeSlotNo = (): UserThemeSlotNo => export const setClientThemeAppearance = ( { themeMode, activeLightThemeSlotNo, - activeDarkThemeSlotNo }: { - themeMode: ClientThemeMode - activeLightThemeSlotNo: UserThemeSlotNo - activeDarkThemeSlotNo: UserThemeSlotNo }, + activeDarkThemeSlotNo }: { themeMode: ClientThemeMode + activeLightThemeSlotNo: UserThemeSlotNo + activeDarkThemeSlotNo: UserThemeSlotNo }, ): void => { updateClientSettings (settings => ({ ...settings, @@ -838,9 +769,7 @@ export const setClientThemeAppearance = ( ...(settings.appearance ?? { }), themeMode, activeLightThemeSlotNo, - activeDarkThemeSlotNo, - }, - })) + activeDarkThemeSlotNo } })) } @@ -848,8 +777,7 @@ export const setClientThemeMode = (theme: UserSettings['theme']): void => { setClientThemeAppearance ({ themeMode: theme, activeLightThemeSlotNo: getClientActiveLightThemeSlotNo (), - activeDarkThemeSlotNo: getClientActiveDarkThemeSlotNo (), - }) + activeDarkThemeSlotNo: getClientActiveDarkThemeSlotNo () }) } @@ -887,15 +815,14 @@ const normaliseThemeTagColours = ( ): ThemeTagColours => CATEGORIES.reduce ( (colours, category) => { - const candidate = input?.[category] - colours[category] = - typeof candidate === 'string' && normaliseHexColour (candidate) != null - ? candidate - : fallback[category] - return colours + const candidate = input?.[category] + colours[category] = + typeof candidate === 'string' && normaliseHexColour (candidate) != null + ? candidate + : fallback[category] + return colours }, - { ...fallback }, - ) + { ...fallback }) const normaliseThemeTokenValue = ( @@ -916,9 +843,9 @@ const cssColourFromThemeToken = ( if (normalisedHex != null) return normalisedHex - return /^-?\d+(?:\.\d+)?\s+\d+(?:\.\d+)?%\s+\d+(?:\.\d+)?%$/.test (value) - ? `hsl(${ value })` - : fallback + return (/^-?\d+(?:\.\d+)?\s+\d+(?:\.\d+)?%\s+\d+(?:\.\d+)?%$/.test (value) + ? `hsl(${ value })` + : fallback) } @@ -928,16 +855,12 @@ const clamp = (value: number, min: number, max: number): number => const parseHslComponents = ( value: string, -): { h: number - s: number - l: number } | null => { - const raw = value.startsWith ('hsl(') && value.endsWith (')') - ? value.slice (4, -1) - : value +): { h: number; s: number; l: number } | null => { + const raw = (value.startsWith ('hsl(') && value.endsWith (')') + ? value.slice (4, -1) + : value) const match = - raw.match ( - /^(-?\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)%$/, - ) + raw.match (/^(-?\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)%$/) if (match == null) return null @@ -945,8 +868,7 @@ const parseHslComponents = ( return { h: Number (match[1]), s: Number (match[2]), - l: Number (match[3]), - } + l: Number (match[3]) } } @@ -987,9 +909,7 @@ const deriveMutedForeground = ( l: clamp ( foregroundHsl.l + (backgroundHsl.l - foregroundHsl.l) * .35, 0, - 100, - ), - }) + 100) }) } @@ -1006,11 +926,9 @@ const deriveTopNavTokens = ( mobileActiveBackground: ( baseTheme === 'dark' ? editable.activeBackground - : editable.rootBackgroundDesktop - ), + : editable.rootBackgroundDesktop), brandLink: editable.brandLink, - menuLink: link, -}) + menuLink: link }) export const buildThemeTokens = ( @@ -1047,41 +965,33 @@ export const buildThemeTokens = ( tokens.topNavRootBackgroundMobile ?? legacyTopNavTokens.topNavRootBackgroundMobile ?? legacyTopNavTokens.topNavBackground, - fallback.topNavRootBackgroundMobile, - ) + fallback.topNavRootBackgroundMobile) const topNavRootBackgroundDesktop = normaliseTopNavColourValue ( tokens.topNavRootBackgroundDesktop ?? legacyTopNavTokens.topNavRootBackgroundDesktop ?? legacyTopNavTokens.topNavBackground, - fallback.topNavRootBackgroundDesktop, - ) + fallback.topNavRootBackgroundDesktop) const topNavActiveBackground = normaliseTopNavColourValue ( tokens.topNavActiveBackground ?? legacyTopNavTokens.topNavActiveBackground ?? legacyTopNavTokens.topNavHighlightBackground ?? legacyTopNavTokens.topNavHighlightBackground, - fallback.topNavActiveBackground, - ) + fallback.topNavActiveBackground) const topNavBrandLink = normaliseTopNavColourValue ( tokens.topNavBrandLink ?? legacyTopNavTokens.topNavBrandLink ?? legacyTopNavTokens.topNavLink, - fallback.topNavBrandLink, - ) + fallback.topNavBrandLink) const topNavMenuLink = cssColourFromThemeToken ( link, - fallback.topNavMenuLink, - ) + fallback.topNavMenuLink) const resolvedTopNav = deriveTopNavTokens ( baseTheme, - { - rootBackgroundMobile: topNavRootBackgroundMobile, - rootBackgroundDesktop: topNavRootBackgroundDesktop, - activeBackground: topNavActiveBackground, - brandLink: topNavBrandLink, - }, - topNavMenuLink, - ) + { rootBackgroundMobile: topNavRootBackgroundMobile, + rootBackgroundDesktop: topNavRootBackgroundDesktop, + activeBackground: topNavActiveBackground, + brandLink: topNavBrandLink }, + topNavMenuLink) return { background, @@ -1098,8 +1008,7 @@ export const buildThemeTokens = ( mutedForeground: deriveMutedForeground ( foreground, background, - fallback.mutedForeground, - ), + fallback.mutedForeground), accent, accentForeground: readableForegroundFor (accent), destructive: fallback.destructive, @@ -1115,17 +1024,14 @@ export const buildThemeTokens = ( topNavMobileActiveBackground: resolvedTopNav.mobileActiveBackground, topNavBrandLink: resolvedTopNav.brandLink, topNavMenuLink: resolvedTopNav.menuLink, - tagColours: normaliseThemeTagColours (tokens.tagColours, fallback.tagColours), - } + tagColours: normaliseThemeTagColours (tokens.tagColours, fallback.tagColours) } } const normaliseTopNavColourValue = ( value: unknown, fallback: string, -): string => { - return cssColourFromThemeToken (value, fallback) -} +): string => cssColourFromThemeToken (value, fallback) const normaliseThemeTokens = ( @@ -1161,39 +1067,36 @@ const normaliseThemeTokens = ( return buildThemeTokens ( baseTheme, - { - ...tokens, - background, - foreground, - card, - muted, - primary, - accent, - border, - link, - topNavRootBackgroundMobile: - tokens.topNavRootBackgroundMobile - ?? legacyTopNavTokens.topNavRootBackgroundMobile - ?? legacyTopNavTokens.topNavBackground, - topNavRootBackgroundDesktop: - tokens.topNavRootBackgroundDesktop - ?? legacyTopNavTokens.topNavRootBackgroundDesktop - ?? legacyTopNavTokens.topNavBackground, - topNavActiveBackground: - tokens.topNavActiveBackground - ?? legacyTopNavTokens.topNavActiveBackground - ?? legacyTopNavTokens.topNavHighlightBackground, - topNavBrandLink: - tokens.topNavBrandLink - ?? legacyTopNavTokens.topNavBrandLink - ?? legacyTopNavTokens.topNavLink, - topNavMenuLink: - tokens.topNavMenuLink - ?? legacyTopNavTokens.topNavMenuLink - ?? link, - tagColours: normaliseThemeTagColours (tokens.tagColours, fallback.tagColours), - }, - ) + { ...tokens, + background, + foreground, + card, + muted, + primary, + accent, + border, + link, + topNavRootBackgroundMobile: + tokens.topNavRootBackgroundMobile + ?? legacyTopNavTokens.topNavRootBackgroundMobile + ?? legacyTopNavTokens.topNavBackground, + topNavRootBackgroundDesktop: + tokens.topNavRootBackgroundDesktop + ?? legacyTopNavTokens.topNavRootBackgroundDesktop + ?? legacyTopNavTokens.topNavBackground, + topNavActiveBackground: + tokens.topNavActiveBackground + ?? legacyTopNavTokens.topNavActiveBackground + ?? legacyTopNavTokens.topNavHighlightBackground, + topNavBrandLink: + tokens.topNavBrandLink + ?? legacyTopNavTokens.topNavBrandLink + ?? legacyTopNavTokens.topNavLink, + topNavMenuLink: + tokens.topNavMenuLink + ?? legacyTopNavTokens.topNavMenuLink + ?? link, + tagColours: normaliseThemeTagColours (tokens.tagColours, fallback.tagColours) }) } @@ -1252,8 +1155,7 @@ export const getDefaultUserThemeSlot = ( ): UserThemeSlot => ({ baseTheme, slotNo, - tokens: normaliseThemeTokens ({ }, baseTheme), -}) + tokens: normaliseThemeTokens ({ }, baseTheme) }) const normaliseUserThemeSlot = ( @@ -1266,12 +1168,10 @@ const normaliseUserThemeSlot = ( const baseTheme = slot.baseTheme const slotNo = slot.slotNo - if ( - baseTheme == null + if (baseTheme == null || !(isBuiltinThemeId (baseTheme)) || slotNo == null - || !(USER_THEME_SLOT_NOS.includes (slotNo as UserThemeSlotNo)) - ) + || !(USER_THEME_SLOT_NOS.includes (slotNo as UserThemeSlotNo))) return null return { @@ -1279,8 +1179,7 @@ const normaliseUserThemeSlot = ( slotNo: slotNo as UserThemeSlotNo, tokens: normaliseThemeTokens (slot.tokens, baseTheme), createdAt: typeof slot.createdAt === 'string' ? slot.createdAt : undefined, - updatedAt: typeof slot.updatedAt === 'string' ? slot.updatedAt : undefined, - } + updatedAt: typeof slot.updatedAt === 'string' ? slot.updatedAt : undefined } } @@ -1292,8 +1191,7 @@ export const normaliseUserThemeSlots = ( map[getUserThemeSlotSelection (slot.baseTheme, slot.slotNo)] = slot return map }, - { }, - ) + { }) const normaliseCustomTheme = ( @@ -1306,13 +1204,11 @@ const normaliseCustomTheme = ( const id = typeof theme.id === 'string' ? theme.id : null const name = typeof theme.name === 'string' ? theme.name : null const baseTheme = typeof theme.baseTheme === 'string' ? theme.baseTheme : null - if ( - id == null + if (id == null || !(isCustomThemeId (id)) || name == null || baseTheme == null - || !(isBuiltinThemeId (baseTheme)) - ) + || !(isBuiltinThemeId (baseTheme))) return null return { @@ -1321,21 +1217,16 @@ const normaliseCustomTheme = ( builtin: false, baseTheme, tokens: normaliseThemeTokens ( - { - ...(theme.tokens && typeof theme.tokens === 'object' - ? theme.tokens as Partial - : { }), - tagColours: ( - theme.tokens - && typeof theme.tokens === 'object' - && 'tagColours' in theme.tokens - ) - ? (theme.tokens as { tagColours?: Partial }).tagColours - : theme.tagColours as Partial | undefined, - }, - baseTheme, - ), - } + { ...(theme.tokens && typeof theme.tokens === 'object' + ? theme.tokens as Partial + : { }), + tagColours: ( + (theme.tokens + && typeof theme.tokens === 'object' + && 'tagColours' in theme.tokens) + ? (theme.tokens as { tagColours?: Partial }).tagColours + : theme.tagColours as Partial | undefined) }, + baseTheme) } } @@ -1347,13 +1238,12 @@ export const getClientCustomThemes = (): Record => { { return Object.entries (customThemes).reduce> ( (themes, [key, rawTheme]) => { - const theme = normaliseCustomTheme (rawTheme) - if (theme != null) - themes[key] = theme - return themes + const theme = normaliseCustomTheme (rawTheme) + if (theme != null) + themes[key] = theme + return themes }, - { }, - ) + { }) } const legacyTagColours = appearance.tagColours @@ -1371,11 +1261,7 @@ export const getClientCustomThemes = (): Record => { ...(baseTheme === 'dark' ? DARK_THEME_TOKENS : LIGHT_THEME_TOKENS), tagColours: normaliseThemeTagColours ( legacyTagColours, - defaultThemeTagColoursFor (baseTheme), - ), - }, - }, - } + defaultThemeTagColoursFor (baseTheme)) } } } } @@ -1385,12 +1271,11 @@ export const fetchUserThemeSlots = async (): Promise => { slotNo: UserThemeSlotNo tokens: ThemeTokens createdAt: string - updatedAt: string - }>> ('/users/theme_slots') + updatedAt: string }>> ('/users/theme_slots') - return raw - .map (slot => normaliseUserThemeSlot (slot)) - .filter ((slot): slot is UserThemeSlot => slot != null) + return (raw + .map (slot => normaliseUserThemeSlot (slot)) + .filter ((slot): slot is UserThemeSlot => slot != null)) } @@ -1404,8 +1289,7 @@ export const upsertUserThemeSlot = async ( slotNo: UserThemeSlotNo tokens: ThemeTokens createdAt: string - updatedAt: string - }> (`/users/theme_slots/${ baseTheme }/${ slotNo }`, { tokens }) + updatedAt: string }> (`/users/theme_slots/${ baseTheme }/${ slotNo }`, { tokens }) const slot = normaliseUserThemeSlot (raw) if (slot == null) @@ -1427,10 +1311,9 @@ export const getCachedUserThemeSlots = (): UserThemeSlotMap => export const resolveDisplayedTheme = ( { themeMode, activeLightThemeSlotNo, - activeDarkThemeSlotNo }: { - themeMode: ClientThemeMode - activeLightThemeSlotNo: UserThemeSlotNo - activeDarkThemeSlotNo: UserThemeSlotNo }, + activeDarkThemeSlotNo }: { themeMode: ClientThemeMode + activeLightThemeSlotNo: UserThemeSlotNo + activeDarkThemeSlotNo: UserThemeSlotNo }, themeSlots: UserThemeSlotMap = getCachedUserThemeSlots (), ): ResolvedDisplayedTheme => { const baseTheme = resolveThemeMode (themeMode) @@ -1445,9 +1328,8 @@ export const resolveDisplayedTheme = ( baseTheme, selection, slotNo, - tokens: themeSlots[selection]?.tokens - ?? getDefaultUserThemeSlot (baseTheme, slotNo).tokens, - } + tokens: (themeSlots[selection]?.tokens + ?? getDefaultUserThemeSlot (baseTheme, slotNo).tokens) } } @@ -1464,28 +1346,21 @@ export const getClientActiveThemeId = (): ActiveThemeId => { const appearance = appearanceFromSettings () const customThemes = getClientCustomThemes () - if ( - typeof appearance.activeThemeId === 'string' - && ( - appearance.activeThemeId === 'system' + if (typeof appearance.activeThemeId === 'string' + && (appearance.activeThemeId === 'system' || isBuiltinThemeId (appearance.activeThemeId) - || customThemes[appearance.activeThemeId] != null - ) - ) + || customThemes[appearance.activeThemeId] != null)) return appearance.activeThemeId - if ( - Object.keys (customThemes).length > 0 + if (Object.keys (customThemes).length > 0 && appearance.tagColours != null - && Object.keys (appearance.tagColours).length > 0 - ) + && Object.keys (appearance.tagColours).length > 0) return LEGACY_MIGRATED_THEME_ID - if (typeof appearance.theme === 'string' && ( - appearance.theme === 'system' - || appearance.theme === 'light' - || appearance.theme === 'dark' - )) + if (typeof appearance.theme === 'string' + && (appearance.theme === 'system' + || appearance.theme === 'light' + || appearance.theme === 'dark')) return appearance.theme return DEFAULT_USER_SETTINGS.theme @@ -1532,8 +1407,7 @@ export const resolveThemeFromSlotSelection = ( selection, baseTheme, builtin: true, - tokens: BUILTIN_THEMES[baseTheme].tokens, - } + tokens: BUILTIN_THEMES[baseTheme].tokens } } if (isBuiltinThemeSlotSelection (selection)) @@ -1543,8 +1417,7 @@ export const resolveThemeFromSlotSelection = ( selection, baseTheme, builtin: true, - tokens: BUILTIN_THEMES[baseTheme].tokens, - } + tokens: BUILTIN_THEMES[baseTheme].tokens } } const baseTheme = baseThemeOfSelection (selection) ?? 'light' @@ -1552,9 +1425,8 @@ export const resolveThemeFromSlotSelection = ( selection, baseTheme, builtin: false, - tokens: themeSlots[selection]?.tokens - ?? getDefaultUserThemeSlot (baseTheme, slotNoOfSelection (selection)).tokens, - } + tokens: (themeSlots[selection]?.tokens + ?? getDefaultUserThemeSlot (baseTheme, slotNoOfSelection (selection)).tokens) } } @@ -1576,21 +1448,15 @@ export const createCustomThemeFromBase = ( name: themeCopyName (baseTheme), builtin: false, baseTheme, - tokens: tokens ?? { - ...(baseTheme === 'dark' ? DARK_THEME_TOKENS : LIGHT_THEME_TOKENS), - }, - } + tokens: tokens ?? { ...(baseTheme === 'dark' ? DARK_THEME_TOKENS : LIGHT_THEME_TOKENS) } } } export const getClientThemeTagColours = ( activeThemeId: ActiveThemeId = getClientActiveThemeId (), customThemes: Record = getClientCustomThemes (), -): ThemeTagColours => { - return { - ...getResolvedThemeFromSelection (activeThemeId, customThemes).tokens.tagColours, - } -} +): ThemeTagColours => + ({ ...getResolvedThemeFromSelection (activeThemeId, customThemes).tokens.tagColours }) export const setClientAppearanceThemes = ( @@ -1604,9 +1470,7 @@ export const setClientAppearanceThemes = ( activeThemeId, customThemes, theme: undefined, - tagColours: undefined, - }, - })) + tagColours: undefined } })) } @@ -1620,17 +1484,10 @@ export const setClientThemeTagColours = ( setClientAppearanceThemes ( activeThemeId, - { - ...customThemes, - [activeThemeId]: { - ...customThemes[activeThemeId], - tokens: { - ...customThemes[activeThemeId].tokens, - tagColours, - }, - }, - }, - ) + { ...customThemes, + [activeThemeId]: { + ...customThemes[activeThemeId], + tokens: { ...customThemes[activeThemeId].tokens, tagColours } } }) } @@ -1647,12 +1504,11 @@ export const applyThemeTagColours = (tagColours: ThemeTagColours): void => { const root = document.documentElement CATEGORIES.forEach (category => { - const colour = tagColours[category] - root.style.setProperty (`--tag-colour-${ category }`, colour) - root.style.setProperty ( - `--tag-colour-${ category }-hover`, - mixHexColour (colour, '#ffffff', .18), - ) + const colour = tagColours[category] + root.style.setProperty (`--tag-colour-${ category }`, colour) + root.style.setProperty ( + `--tag-colour-${ category }-hover`, + mixHexColour (colour, '#ffffff', .18)) }) } @@ -1680,38 +1536,14 @@ export const applyThemeTokens = (tokens: ThemeTokens): void => { root.style.setProperty ('--input', tokens.input) root.style.setProperty ('--ring', tokens.ring) root.style.setProperty ('--theme-link', tokens.link) - root.style.setProperty ( - '--top-nav-root-bg-mobile', - tokens.topNavRootBackgroundMobile, - ) - root.style.setProperty ( - '--top-nav-root-bg-desktop', - tokens.topNavRootBackgroundDesktop, - ) - root.style.setProperty ( - '--top-nav-active-bg', - tokens.topNavActiveBackground, - ) - root.style.setProperty ( - '--top-nav-submenu-bg', - tokens.topNavSubmenuBackground, - ) - root.style.setProperty ( - '--top-nav-mobile-menu-bg', - tokens.topNavRootBackgroundMobile, - ) - root.style.setProperty ( - '--top-nav-mobile-active-bg', - tokens.topNavMobileActiveBackground, - ) - root.style.setProperty ( - '--top-nav-brand-link', - tokens.topNavBrandLink, - ) - root.style.setProperty ( - '--top-nav-menu-link', - tokens.topNavMenuLink, - ) + root.style.setProperty ('--top-nav-root-bg-mobile', tokens.topNavRootBackgroundMobile) + root.style.setProperty ('--top-nav-root-bg-desktop', tokens.topNavRootBackgroundDesktop) + root.style.setProperty ('--top-nav-active-bg', tokens.topNavActiveBackground) + root.style.setProperty ('--top-nav-submenu-bg', tokens.topNavSubmenuBackground) + root.style.setProperty ('--top-nav-mobile-menu-bg', tokens.topNavRootBackgroundMobile) + root.style.setProperty ('--top-nav-mobile-active-bg', tokens.topNavMobileActiveBackground) + root.style.setProperty ('--top-nav-brand-link', tokens.topNavBrandLink) + root.style.setProperty ('--top-nav-menu-link', tokens.topNavMenuLink) applyThemeTagColours (tokens.tagColours) } @@ -1730,17 +1562,14 @@ export const applyThemeSelection = ( export const applyThemeAppearanceState = ( { themeMode, activeLightThemeSlotNo, - activeDarkThemeSlotNo }: { - themeMode: ClientThemeMode - activeLightThemeSlotNo: UserThemeSlotNo - activeDarkThemeSlotNo: UserThemeSlotNo }, + activeDarkThemeSlotNo }: { themeMode: ClientThemeMode + activeLightThemeSlotNo: UserThemeSlotNo + activeDarkThemeSlotNo: UserThemeSlotNo }, themeSlots: UserThemeSlotMap = getCachedUserThemeSlots (), ): void => { - const theme = resolveDisplayedTheme ({ - themeMode, - activeLightThemeSlotNo, - activeDarkThemeSlotNo, - }, themeSlots) + const theme = resolveDisplayedTheme ({ themeMode, + activeLightThemeSlotNo, + activeDarkThemeSlotNo }, themeSlots) applyThemeMode (theme.baseTheme) applyThemeTokens (theme.tokens) @@ -1753,8 +1582,7 @@ export const applyClientAppearance = ( applyThemeAppearanceState ({ themeMode: getClientThemeMode (), activeLightThemeSlotNo: getClientActiveLightThemeSlotNo (), - activeDarkThemeSlotNo: getClientActiveDarkThemeSlotNo (), - }, themeSlots) + activeDarkThemeSlotNo: getClientActiveDarkThemeSlotNo () }, themeSlots) } @@ -1786,20 +1614,17 @@ export const setClientListSettings = ( [listKey]: { ...(settings.lists?.[listKey] ?? { }), ...(limit == null ? { } : { limit }), - ...(order == null ? { } : { order }), - }, - }, - })) + ...(order == null ? { } : { order }) } } })) } const legacyTheatreLayoutMode = (): TheatreLayoutMode | null => { const value = localStorage.getItem (LEGACY_THEATRE_LAYOUT_STORAGE_KEY) - return ( - value === 'threeColumns' - || value === 'tagsBottom' - || value === 'commentsBottom' - ) ? value : null + return ((value === 'threeColumns' + || value === 'tagsBottom' + || value === 'commentsBottom') + ? value + : null) } @@ -1841,11 +1666,10 @@ export const setClientTheatreTagFlow = (tagFlow: TheatreTagFlow): void => { } -export const getClientGekanatorBackgroundMotion = - (): GekanatorBackgroundMotionMode => - loadClientSettings ().gekanator?.backgroundMotion - ?? legacyGekanatorBackgroundMotion () - ?? 'on' +export const getClientGekanatorBackgroundMotion = (): GekanatorBackgroundMotionMode => + loadClientSettings ().gekanator?.backgroundMotion + ?? legacyGekanatorBackgroundMotion () + ?? 'on' export const setClientGekanatorBackgroundMotion = ( @@ -1853,8 +1677,7 @@ export const setClientGekanatorBackgroundMotion = ( ): void => { updateClientSettings (settings => ({ ...settings, - gekanator: { ...(settings.gekanator ?? { }), backgroundMotion }, - })) + gekanator: { ...(settings.gekanator ?? { }), backgroundMotion } })) } @@ -1880,11 +1703,7 @@ export const setClientPaneWidthPx = ( ...(settings.panes?.[paneKey] ?? { }), widthPxByBreakpoint: { ...(settings.panes?.[paneKey]?.widthPxByBreakpoint ?? { }), - [breakpoint]: widthPx, - }, - }, - }, - })) + [breakpoint]: widthPx } } } })) } @@ -1898,8 +1717,5 @@ export const setClientPaneCollapsed = ( ...(settings.panes ?? { }), [paneKey]: { ...(settings.panes?.[paneKey] ?? { }), - collapsed, - }, - }, - })) + collapsed } } })) } diff --git a/frontend/src/pages/users/SettingPage.tsx b/frontend/src/pages/users/SettingPage.tsx index 12b796d..de45233 100644 --- a/frontend/src/pages/users/SettingPage.tsx +++ b/frontend/src/pages/users/SettingPage.tsx @@ -1445,4 +1445,5 @@ const SettingPage: FC = ({ user, setUser }) => { ) } + export default SettingPage