このコミットが含まれているのは:
+92
-70
@@ -1,49 +1,38 @@
|
||||
import { apiGet, apiPatch } from '@/lib/api'
|
||||
|
||||
import type { FetchPostsOrder } from '@/types'
|
||||
|
||||
// DB-backed user settings. These are shared across browsers for the same user.
|
||||
export type UserPostListOrder =
|
||||
| 'title_asc'
|
||||
| 'title_desc'
|
||||
| 'url_asc'
|
||||
| 'url_desc'
|
||||
| 'original_created_at_asc'
|
||||
| 'original_created_at_desc'
|
||||
| 'created_at_asc'
|
||||
| 'created_at_desc'
|
||||
| 'updated_at_asc'
|
||||
| 'updated_at_desc'
|
||||
import type { FetchPostsOrder, FetchTagsOrder } from '@/types'
|
||||
|
||||
// DB-backed user settings. These are worth sharing across browsers for one user.
|
||||
export type UserSettings = {
|
||||
theme: 'system' | 'light' | 'dark'
|
||||
displayDensity: 'comfortable' | 'compact'
|
||||
fontSize: 'small' | 'normal' | 'large'
|
||||
postListLimit: 20 | 50 | 100
|
||||
postListOrder: UserPostListOrder
|
||||
viewedPostDisplay: 'show' | 'dim'
|
||||
tagAutocompleteNico: boolean
|
||||
autoFetchTitle: 'auto' | 'manual' | 'off'
|
||||
autoFetchThumbnail: 'auto' | 'manual' | 'off'
|
||||
wikiEditorMode: 'split' | 'write' | 'preview' }
|
||||
theme: 'system' | 'light' | 'dark'
|
||||
autoFetchTitle: 'auto' | 'manual' | 'off'
|
||||
autoFetchThumbnail: 'auto' | 'manual' | 'off'
|
||||
wikiEditorMode: 'split' | 'write' | 'preview' }
|
||||
|
||||
export type TheatreLayoutMode = 'threeColumns' | 'tagsBottom' | 'commentsBottom'
|
||||
export type TheatreTagFlow = 'vertical' | 'horizontal'
|
||||
export type GekanatorBackgroundMotionMode = 'on' | 'calm' | 'off'
|
||||
export type ClientPaneBreakpoint = 'desktop' | 'tablet'
|
||||
export type ClientListKey = 'postList' | 'postSearch' | 'tagList'
|
||||
export type ClientListLimit = 20 | 50 | 100
|
||||
|
||||
// Browser-local settings. These stay in localStorage and are never synced to DB.
|
||||
type ClientPaneSettings = {
|
||||
widthPxByBreakpoint?: Partial<Record<ClientPaneBreakpoint, number>>
|
||||
collapsed?: boolean }
|
||||
|
||||
type ClientListSettings = {
|
||||
limit?: ClientListLimit
|
||||
order?: string }
|
||||
|
||||
// Browser-local settings. These depend on device or screen context.
|
||||
export type ClientSettings = {
|
||||
panes?: Record<string, ClientPaneSettings>
|
||||
theatre?: {
|
||||
panes?: Record<string, ClientPaneSettings>
|
||||
lists?: Partial<Record<ClientListKey, ClientListSettings>>
|
||||
theatre?: {
|
||||
layoutMode?: TheatreLayoutMode
|
||||
tagFlow?: TheatreTagFlow
|
||||
}
|
||||
gekanator?: {
|
||||
gekanator?: {
|
||||
backgroundMotion?: GekanatorBackgroundMotionMode
|
||||
}
|
||||
reducedMotion?: string
|
||||
@@ -52,36 +41,42 @@ export type ClientSettings = {
|
||||
|
||||
export const CLIENT_SETTINGS_STORAGE_KEY = 'btrc_hub.client_settings'
|
||||
export const DEFAULT_USER_SETTINGS: UserSettings = {
|
||||
theme: 'system',
|
||||
displayDensity: 'comfortable',
|
||||
fontSize: 'normal',
|
||||
postListLimit: 50,
|
||||
postListOrder: 'created_at_desc',
|
||||
viewedPostDisplay: 'show',
|
||||
tagAutocompleteNico: true,
|
||||
autoFetchTitle: 'manual',
|
||||
autoFetchThumbnail: 'manual',
|
||||
wikiEditorMode: 'split' }
|
||||
theme: 'system',
|
||||
autoFetchTitle: 'manual',
|
||||
autoFetchThumbnail: 'manual',
|
||||
wikiEditorMode: 'split' }
|
||||
|
||||
export const THEME_OPTIONS = ['system', 'light', 'dark'] as const
|
||||
export const DISPLAY_DENSITY_OPTIONS = ['comfortable', 'compact'] as const
|
||||
export const FONT_SIZE_OPTIONS = ['small', 'normal', 'large'] as const
|
||||
export const POST_LIST_LIMIT_OPTIONS = [20, 50, 100] as const
|
||||
export const POST_LIST_ORDER_OPTIONS = [
|
||||
'title_asc',
|
||||
'title_desc',
|
||||
'url_asc',
|
||||
'url_desc',
|
||||
'original_created_at_asc',
|
||||
'original_created_at_desc',
|
||||
'created_at_asc',
|
||||
'created_at_desc',
|
||||
'updated_at_asc',
|
||||
'updated_at_desc',
|
||||
] as const
|
||||
export const VIEWED_POST_DISPLAY_OPTIONS = ['show', 'dim'] as const
|
||||
export const AUTO_FETCH_OPTIONS = ['auto', 'manual', 'off'] as const
|
||||
export const WIKI_EDITOR_MODE_OPTIONS = ['split', 'write', 'preview'] as const
|
||||
export const LIST_LIMIT_OPTIONS = [20, 50, 100] as const
|
||||
export const POST_LIST_ORDER_OPTIONS = [
|
||||
'title:asc',
|
||||
'title:desc',
|
||||
'url:asc',
|
||||
'url:desc',
|
||||
'original_created_at:asc',
|
||||
'original_created_at:desc',
|
||||
'created_at:asc',
|
||||
'created_at:desc',
|
||||
'updated_at:asc',
|
||||
'updated_at:desc',
|
||||
] as const satisfies readonly FetchPostsOrder[]
|
||||
export const TAG_LIST_ORDER_OPTIONS = [
|
||||
'name:asc',
|
||||
'name:desc',
|
||||
'category:asc',
|
||||
'category:desc',
|
||||
'post_count:asc',
|
||||
'post_count:desc',
|
||||
'created_at:asc',
|
||||
'created_at:desc',
|
||||
'updated_at:asc',
|
||||
'updated_at:desc',
|
||||
] as const satisfies readonly FetchTagsOrder[]
|
||||
export const DEFAULT_POST_LIST_LIMIT: ClientListLimit = 20
|
||||
export const DEFAULT_POST_LIST_ORDER: FetchPostsOrder = 'original_created_at:desc'
|
||||
export const DEFAULT_TAG_LIST_ORDER: FetchTagsOrder = 'name:asc'
|
||||
|
||||
const LEGACY_THEATRE_LAYOUT_STORAGE_KEY = 'theatre-layout-mode'
|
||||
const LEGACY_THEATRE_TAG_FLOW_STORAGE_KEY = 'theatre-tag-flow'
|
||||
@@ -95,26 +90,14 @@ export const fetchUserSettings = async (): Promise<UserSettings> =>
|
||||
|
||||
export const updateUserSettings = async (
|
||||
settings: Partial<{
|
||||
theme: UserSettings['theme']
|
||||
display_density: UserSettings['displayDensity']
|
||||
font_size: UserSettings['fontSize']
|
||||
post_list_limit: UserSettings['postListLimit']
|
||||
post_list_order: UserSettings['postListOrder']
|
||||
viewed_post_display: UserSettings['viewedPostDisplay']
|
||||
tag_autocomplete_nico: UserSettings['tagAutocompleteNico']
|
||||
auto_fetch_title: UserSettings['autoFetchTitle']
|
||||
auto_fetch_thumbnail: UserSettings['autoFetchThumbnail']
|
||||
wiki_editor_mode: UserSettings['wikiEditorMode']
|
||||
theme: UserSettings['theme']
|
||||
auto_fetch_title: UserSettings['autoFetchTitle']
|
||||
auto_fetch_thumbnail: UserSettings['autoFetchThumbnail']
|
||||
wiki_editor_mode: UserSettings['wikiEditorMode']
|
||||
}>,
|
||||
): Promise<UserSettings> => await apiPatch<UserSettings> ('/users/settings', settings)
|
||||
|
||||
|
||||
export const toFetchPostsOrder = (
|
||||
value: UserSettings['postListOrder'],
|
||||
): FetchPostsOrder =>
|
||||
value.replace (/_(asc|desc)$/, ':$1') as FetchPostsOrder
|
||||
|
||||
|
||||
const safeParseClientSettings = (raw: string | null): ClientSettings => {
|
||||
if (!(raw))
|
||||
return { }
|
||||
@@ -149,6 +132,45 @@ export const updateClientSettings = (
|
||||
}
|
||||
|
||||
|
||||
const validListLimit = (value: unknown): ClientListLimit | null =>
|
||||
value === 20 || value === 50 || value === 100 ? value : null
|
||||
|
||||
|
||||
export const getClientListLimit = (
|
||||
listKey: ClientListKey,
|
||||
): ClientListLimit | null => {
|
||||
const value = loadClientSettings ().lists?.[listKey]?.limit
|
||||
return validListLimit (value)
|
||||
}
|
||||
|
||||
|
||||
export const getClientListOrder = <T extends string>(
|
||||
listKey: ClientListKey,
|
||||
): T | null => {
|
||||
const value = loadClientSettings ().lists?.[listKey]?.order
|
||||
return typeof value === 'string' ? value as T : null
|
||||
}
|
||||
|
||||
|
||||
export const setClientListSettings = (
|
||||
listKey: ClientListKey,
|
||||
{ limit, order }: { limit?: ClientListLimit
|
||||
order?: string },
|
||||
): void => {
|
||||
updateClientSettings (settings => ({
|
||||
...settings,
|
||||
lists: {
|
||||
...(settings.lists ?? { }),
|
||||
[listKey]: {
|
||||
...(settings.lists?.[listKey] ?? { }),
|
||||
...(limit == null ? { } : { limit }),
|
||||
...(order == null ? { } : { order }),
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
const legacyTheatreLayoutMode = (): TheatreLayoutMode | null => {
|
||||
const value = localStorage.getItem (LEGACY_THEATRE_LAYOUT_STORAGE_KEY)
|
||||
return (
|
||||
|
||||
新しい課題から参照
ユーザをブロックする