設定画面 (#34) #397

マージ済み
みてるぞ が 31 個のコミットを feature/034 から main へマージ 2026-07-07 00:48:41 +09:00
3個のファイルの変更338行の追加418行の削除
コミット 8ac56f74ce の変更だけを表示してゐます - すべてのコミットを表示
+103
ファイルの表示
@@ -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 <div/>
}
export default Component
```
Good:
```ts
const Component: FC = () => {
return <div/>
}
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
+132 -316
ファイルの表示
@@ -152,8 +152,7 @@ export type ClientSettings = {
keyboard?: ClientKeyboardSettings
panes?: Record<string, ClientPaneSettings>
lists?: Partial<Record<ClientListKey, ClientListSettings>>
theatre?: {
layoutMode?: TheatreLayoutMode
theatre?: { layoutMode?: TheatreLayoutMode
tagFlow?: TheatreTagFlow }
gekanator?: { backgroundMotion?: GekanatorBackgroundMotionMode }
reducedMotion?: string
@@ -273,14 +272,12 @@ export const DARK_THEME_TOKENS: ThemeTokens = {
topNavMenuLink: '#93c5fd',
tagColours: DEFAULT_DARK_THEME_TAG_COLOURS }
export const BUILTIN_THEMES: Record<BuiltinThemeId, ClientTheme> = {
light: {
id: 'light',
light: { id: 'light',
name: 'ライト・モード',
builtin: true,
baseTheme: 'light',
tokens: LIGHT_THEME_TOKENS },
dark: {
id: 'dark',
dark: { id: 'dark',
name: 'ダーク・モード',
builtin: true,
baseTheme: 'dark',
@@ -288,8 +285,7 @@ export const BUILTIN_THEMES: Record<BuiltinThemeId, ClientTheme> = {
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'
@@ -307,15 +303,13 @@ export const fetchUserSettings = async (): Promise<UserSettings> => {
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,10 +419,9 @@ const normaliseKeyboardBindings = (
if (!(bindings) || typeof bindings !== 'object')
return { }
return Object.keys (DEFAULT_KEY_BINDINGS).reduce<
Partial<Record<ShortcutActionId, KeyBinding | null>>
> (
(normalised, actionId) => {
return (
Object.keys (DEFAULT_KEY_BINDINGS)
.reduce<Partial<Record<ShortcutActionId, KeyBinding | null>>> ((normalised, actionId) => {
const value = (bindings as Record<string, unknown>)[actionId]
if (value === null)
{
@@ -444,9 +434,7 @@ const normaliseKeyboardBindings = (
normalised[actionId as ShortcutActionId] = binding
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
}
@@ -633,8 +591,7 @@ export const getEffectiveKeyBindings = (
: 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
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
)
|| appearance.tagColours != null)
}
@@ -725,9 +671,7 @@ const mixHexColour = (
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,
)
const mixed = Math.round (srcValue + (dstValue - srcValue) * clampedRatio)
return mixed.toString (16).padStart (2, '0')
})
@@ -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,8 +759,7 @@ export const getClientActiveDarkThemeSlotNo = (): UserThemeSlotNo =>
export const setClientThemeAppearance = (
{ themeMode,
activeLightThemeSlotNo,
activeDarkThemeSlotNo }: {
themeMode: ClientThemeMode
activeDarkThemeSlotNo }: { themeMode: ClientThemeMode
activeLightThemeSlotNo: UserThemeSlotNo
activeDarkThemeSlotNo: UserThemeSlotNo },
): void => {
@@ -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 () })
}
@@ -894,8 +822,7 @@ const normaliseThemeTagColours = (
: 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)
return (/^-?\d+(?:\.\d+)?\s+\d+(?:\.\d+)?%\s+\d+(?:\.\d+)?%$/.test (value)
? `hsl(${ value })`
: fallback
: 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 (')')
): { h: number; s: number; l: number } | null => {
const raw = (value.startsWith ('hsl(') && value.endsWith (')')
? value.slice (4, -1)
: value
: 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,
{ rootBackgroundMobile: topNavRootBackgroundMobile,
rootBackgroundDesktop: topNavRootBackgroundDesktop,
activeBackground: topNavActiveBackground,
brandLink: topNavBrandLink,
},
topNavMenuLink,
)
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,8 +1067,7 @@ const normaliseThemeTokens = (
return buildThemeTokens (
baseTheme,
{
...tokens,
{ ...tokens,
background,
foreground,
card,
@@ -1191,9 +1096,7 @@ const normaliseThemeTokens = (
tokens.topNavMenuLink
?? legacyTopNavTokens.topNavMenuLink
?? link,
tagColours: normaliseThemeTagColours (tokens.tagColours, fallback.tagColours),
},
)
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 && typeof theme.tokens === 'object'
? theme.tokens as Partial<ThemeTokens>
: { }),
tagColours: (
theme.tokens
(theme.tokens
&& typeof theme.tokens === 'object'
&& 'tagColours' in theme.tokens
)
&& 'tagColours' in theme.tokens)
? (theme.tokens as { tagColours?: Partial<ThemeTagColours> }).tagColours
: theme.tagColours as Partial<ThemeTagColours> | undefined,
},
baseTheme,
),
}
: theme.tagColours as Partial<ThemeTagColours> | undefined) },
baseTheme) }
}
@@ -1352,8 +1243,7 @@ export const getClientCustomThemes = (): Record<string, CustomClientTheme> => {
themes[key] = theme
return themes
},
{ },
)
{ })
}
const legacyTagColours = appearance.tagColours
@@ -1371,11 +1261,7 @@ export const getClientCustomThemes = (): Record<string, CustomClientTheme> => {
...(baseTheme === 'dark' ? DARK_THEME_TOKENS : LIGHT_THEME_TOKENS),
tagColours: normaliseThemeTagColours (
legacyTagColours,
defaultThemeTagColoursFor (baseTheme),
),
},
},
}
defaultThemeTagColoursFor (baseTheme)) } } }
}
@@ -1385,12 +1271,11 @@ export const fetchUserThemeSlots = async (): Promise<UserThemeSlot[]> => {
slotNo: UserThemeSlotNo
tokens: ThemeTokens
createdAt: string
updatedAt: string
}>> ('/users/theme_slots')
updatedAt: string }>> ('/users/theme_slots')
return raw
return (raw
.map (slot => normaliseUserThemeSlot (slot))
.filter ((slot): slot is UserThemeSlot => slot != null)
.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,8 +1311,7 @@ export const getCachedUserThemeSlots = (): UserThemeSlotMap =>
export const resolveDisplayedTheme = (
{ themeMode,
activeLightThemeSlotNo,
activeDarkThemeSlotNo }: {
themeMode: ClientThemeMode
activeDarkThemeSlotNo }: { themeMode: ClientThemeMode
activeLightThemeSlotNo: UserThemeSlotNo
activeDarkThemeSlotNo: UserThemeSlotNo },
themeSlots: UserThemeSlotMap = getCachedUserThemeSlots (),
@@ -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'
if (typeof appearance.theme === 'string'
&& (appearance.theme === 'system'
|| appearance.theme === 'light'
|| appearance.theme === 'dark'
))
|| 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<string, CustomClientTheme> = 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,
{ ...customThemes,
[activeThemeId]: {
...customThemes[activeThemeId],
tokens: {
...customThemes[activeThemeId].tokens,
tagColours,
},
},
},
)
tokens: { ...customThemes[activeThemeId].tokens, tagColours } } })
}
@@ -1651,8 +1508,7 @@ export const applyThemeTagColours = (tagColours: ThemeTagColours): void => {
root.style.setProperty (`--tag-colour-${ category }`, colour)
root.style.setProperty (
`--tag-colour-${ category }-hover`,
mixHexColour (colour, '#ffffff', .18),
)
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
activeDarkThemeSlotNo }: { themeMode: ClientThemeMode
activeLightThemeSlotNo: UserThemeSlotNo
activeDarkThemeSlotNo: UserThemeSlotNo },
themeSlots: UserThemeSlotMap = getCachedUserThemeSlots (),
): void => {
const theme = resolveDisplayedTheme ({
themeMode,
const theme = resolveDisplayedTheme ({ themeMode,
activeLightThemeSlotNo,
activeDarkThemeSlotNo,
}, themeSlots)
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'
return ((value === 'threeColumns'
|| value === 'tagsBottom'
|| value === 'commentsBottom'
) ? value : null
|| value === 'commentsBottom')
? value
: null)
}
@@ -1841,8 +1666,7 @@ export const setClientTheatreTagFlow = (tagFlow: TheatreTagFlow): void => {
}
export const getClientGekanatorBackgroundMotion =
(): GekanatorBackgroundMotionMode =>
export const getClientGekanatorBackgroundMotion = (): GekanatorBackgroundMotionMode =>
loadClientSettings ().gekanator?.backgroundMotion
?? legacyGekanatorBackgroundMotion ()
?? 'on'
@@ -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 } } }))
}
+1
ファイルの表示
@@ -1445,4 +1445,5 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
</MainArea>)
}
export default SettingPage