このコミットが含まれているのは:
2026-07-04 19:03:50 +09:00
コミット 6a57dc53bf
22個のファイルの変更1099行の追加114行の削除
+5 -1
ファイルの表示
@@ -2,6 +2,7 @@
import { useRef, useState } from 'react'
import { useUserSettings } from '@/components/users/UserSettingsProvider'
import TagSearchBox from '@/components/TagSearchBox'
import FormField from '@/components/common/FormField'
import TextArea from '@/components/common/TextArea'
@@ -39,6 +40,7 @@ type Props = Omit<ComponentPropsWithoutRef<'textarea'>, 'value' | 'onChange' | '
const PostFormTagsArea: FC<Props> = ({ tags, setTags, errors, ...rest }) => {
const ref = useRef<HTMLTextAreaElement> (null)
const { settings } = useUserSettings ()
const [bounds, setBounds] = useState<{ start: number; end: number }> ({ start: 0, end: 0 })
const [focused, setFocused] = useState (false)
@@ -68,7 +70,9 @@ const PostFormTagsArea: FC<Props> = ({ tags, setTags, errors, ...rest }) => {
setBounds ({ start, end })
const data = await apiGet<Tag[]> ('/tags/autocomplete', { params: { q: token, nico: '0' } })
const data = await apiGet<Tag[]> ('/tags/autocomplete', { params: {
q: token,
nico: settings.tagAutocompleteNico ? '1' : '0' } })
setSuggestions (data.filter (t => t.postCount > 0))
setSuggestionsVsbl (suggestions.length > 0)
}
+11 -2
ファイルの表示
@@ -2,6 +2,7 @@ import { motion } from 'framer-motion'
import { useRef } from 'react'
import { useLocation } from 'react-router-dom'
import { useUserSettings } from '@/components/users/UserSettingsProvider'
import PrefetchLink from '@/components/PrefetchLink'
import { cn } from '@/lib/utils'
import { useSharedTransitionStore } from '@/stores/sharedTransitionStore'
@@ -16,14 +17,19 @@ type Props = { posts: Post[]
const PostList: FC<Props> = ({ posts, onClick }) => {
const location = useLocation ()
const { settings } = useUserSettings ()
const setForLocationKey = useSharedTransitionStore (s => s.setForLocationKey)
const cardRef = useRef<HTMLDivElement> (null)
const visiblePosts =
settings.viewedPostDisplay === 'hide'
? posts.filter (post => !(post.viewed))
: posts
return (
<div className="flex flex-wrap gap-6 p-4">
{posts.map ((post, i) => {
{visiblePosts.map ((post, i) => {
const sharedId = `page-${ post.id }`
const layoutId = sharedId
@@ -42,6 +48,9 @@ const PostList: FC<Props> = ({ posts, onClick }) => {
layoutId={layoutId}
className={cn ('w-full h-full overflow-hidden rounded-xl shadow',
'transform-gpu will-change-transform',
settings.viewedPostDisplay === 'dim'
&& post.viewed
&& 'opacity-40 saturate-50',
(post.childPosts ?? []).length > 0 && 'ring-4 ring-green-500',
(post.parentPosts ?? []).length > 0 && 'ring-4 ring-yellow-500')}
whileHover={{ scale: 1.02 }}
@@ -72,4 +81,4 @@ const PostList: FC<Props> = ({ posts, onClick }) => {
</div>)
}
export default PostList
export default PostList
+5 -1
ファイルの表示
@@ -1,5 +1,6 @@
import { useState } from 'react'
import { useUserSettings } from '@/components/users/UserSettingsProvider'
import TagSearchBox from '@/components/TagSearchBox'
import { apiGet } from '@/lib/api'
import { inputClass } from '@/lib/utils'
@@ -20,6 +21,7 @@ const TagInput: FC<Props> = ({ describedBy, invalid, value, setValue }) => {
const [activeIndex, setActiveIndex] = useState (-1)
const [suggestions, setSuggestions] = useState<Tag[]> ([])
const [suggestionsVsbl, setSuggestionsVsbl] = useState (false)
const { settings } = useUserSettings ()
// TODO: TagSearch からのコピペのため,共通化を考へる.
const whenChanged = async (ev: ChangeEvent<HTMLInputElement>) => {
@@ -32,7 +34,9 @@ const TagInput: FC<Props> = ({ describedBy, invalid, value, setValue }) => {
return
}
const data = await apiGet<Tag[]> ('/tags/autocomplete', { params: { q } })
const data = await apiGet<Tag[]> ('/tags/autocomplete', { params: {
q,
nico: settings.tagAutocompleteNico ? '1' : '0' } })
setSuggestions (data.filter (t => t.postCount > 0))
if (suggestions.length > 0)
setSuggestionsVsbl (true)
+111
ファイルの表示
@@ -0,0 +1,111 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
import { DEFAULT_USER_SETTINGS, fetchUserSettings } from '@/lib/settings'
import type { Dispatch, FC, ReactNode, SetStateAction } from 'react'
import type { User } from '@/types'
import type { UserSettings } from '@/lib/settings'
type ContextValue = {
loaded: boolean
settings: UserSettings
setSettings: Dispatch<SetStateAction<UserSettings>> }
const UserSettingsContext = createContext<ContextValue | null> (null)
const applyTheme = (theme: UserSettings['theme']) => {
const root = document.documentElement
const media = window.matchMedia ('(prefers-color-scheme: dark)')
const sync = () => {
const dark = theme === 'dark' || (theme === 'system' && media.matches)
root.classList.toggle ('dark', dark)
root.style.colorScheme = dark ? 'dark' : 'light'
}
sync ()
if (theme !== 'system')
return () => { }
const onChange = () => sync ()
media.addEventListener ('change', onChange)
return () => {
media.removeEventListener ('change', onChange)
}
}
export const UserSettingsProvider: FC<{
children: ReactNode
user: User | null }> = ({ children, user }) => {
const [loaded, setLoaded] = useState (false)
const [settings, setSettings] = useState<UserSettings> (DEFAULT_USER_SETTINGS)
useEffect (() => {
let cancelled = false
if (!(user))
{
setSettings (DEFAULT_USER_SETTINGS)
setLoaded (true)
return
}
setLoaded (false)
void (async () => {
try
{
const next = await fetchUserSettings ()
if (!(cancelled))
{
setSettings (next)
setLoaded (true)
}
}
catch
{
if (!(cancelled))
{
setSettings (DEFAULT_USER_SETTINGS)
setLoaded (true)
}
}
}) ()
return () => {
cancelled = true
}
}, [user])
useEffect (() => applyTheme (settings.theme), [settings.theme])
useEffect (() => {
document.documentElement.dataset.displayDensity = settings.displayDensity
document.documentElement.dataset.fontSize = settings.fontSize
}, [settings.displayDensity, settings.fontSize])
const value = useMemo<ContextValue> (() => ({
loaded,
settings,
setSettings,
}), [loaded, settings])
return (
<UserSettingsContext.Provider value={value}>
{children}
</UserSettingsContext.Provider>)
}
export const useUserSettings = (): ContextValue => {
const value = useContext (UserSettingsContext)
if (value == null)
throw new Error ('UserSettingsProvider is missing')
return value
}