このコミットが含まれているのは:
@@ -19,6 +19,7 @@ import {
|
||||
LIST_LIMIT_OPTIONS,
|
||||
setClientListSettings,
|
||||
} from '@/lib/settings'
|
||||
import { useKeyboardShortcuts } from '@/lib/useKeyboardShortcuts'
|
||||
import { fetchWikiPageByTitle } from '@/lib/wiki'
|
||||
|
||||
import type { FC } from 'react'
|
||||
@@ -68,6 +69,12 @@ const PostListPage: FC = () => {
|
||||
setClientListSettings ('postList', { limit })
|
||||
}, [limit])
|
||||
|
||||
const updatePage = (nextPage: number) => {
|
||||
const params = new URLSearchParams (location.search)
|
||||
params.set ('page', String (Math.max (1, nextPage)))
|
||||
navigate (`${ location.pathname }?${ params.toString () }`)
|
||||
}
|
||||
|
||||
const updateListQuery = (next: { limit?: 20 | 50 | 100 }) => {
|
||||
const params = new URLSearchParams (location.search)
|
||||
params.set ('limit', String (next.limit ?? limit))
|
||||
@@ -75,6 +82,23 @@ const PostListPage: FC = () => {
|
||||
navigate (`${ location.pathname }?${ params.toString () }`)
|
||||
}
|
||||
|
||||
useKeyboardShortcuts (useMemo (() => ({
|
||||
...(page > 1
|
||||
? {
|
||||
'pagination.previous': () => {
|
||||
updatePage (page - 1)
|
||||
},
|
||||
}
|
||||
: { }),
|
||||
...(totalPages > 0 && page < totalPages
|
||||
? {
|
||||
'pagination.next': () => {
|
||||
updatePage (Math.min (page + 1, totalPages))
|
||||
},
|
||||
}
|
||||
: { }),
|
||||
}), [page, totalPages, location.search]))
|
||||
|
||||
useLayoutEffect (() => {
|
||||
scroll (0, 0)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
LIST_LIMIT_OPTIONS,
|
||||
setClientListSettings,
|
||||
} from '@/lib/settings'
|
||||
import { useKeyboardShortcuts } from '@/lib/useKeyboardShortcuts'
|
||||
import { dateString, inputClass, originalCreatedAtString } from '@/lib/utils'
|
||||
|
||||
import type { FC, FormEvent } from 'react'
|
||||
@@ -171,11 +172,34 @@ const PostSearchPage: FC = () => {
|
||||
navigate (`${ location.pathname }?${ qs.toString () }`)
|
||||
}
|
||||
|
||||
const updatePage = (nextPage: number) => {
|
||||
const qs = new URLSearchParams (location.search)
|
||||
qs.set ('page', String (Math.max (1, nextPage)))
|
||||
navigate (`${ location.pathname }?${ qs.toString () }`)
|
||||
}
|
||||
|
||||
const handleSearch = (e: FormEvent) => {
|
||||
e.preventDefault ()
|
||||
search ()
|
||||
}
|
||||
|
||||
useKeyboardShortcuts (useMemo (() => ({
|
||||
...(page > 1
|
||||
? {
|
||||
'pagination.previous': () => {
|
||||
updatePage (page - 1)
|
||||
},
|
||||
}
|
||||
: { }),
|
||||
...(totalPages > 0 && page < totalPages
|
||||
? {
|
||||
'pagination.next': () => {
|
||||
updatePage (Math.min (page + 1, totalPages))
|
||||
},
|
||||
}
|
||||
: { }),
|
||||
}), [page, totalPages, location.search]))
|
||||
|
||||
const defaultDirection = { title: 'asc',
|
||||
url: 'asc',
|
||||
original_created_at: 'desc',
|
||||
@@ -214,6 +238,7 @@ const PostSearchPage: FC = () => {
|
||||
{({ invalid }) => (
|
||||
<input
|
||||
type="text"
|
||||
data-shortcut-focus-search="true"
|
||||
value={title}
|
||||
onChange={e => setTitle (e.target.value)}
|
||||
className={inputClass (invalid)}/>)}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
LIST_LIMIT_OPTIONS,
|
||||
setClientListSettings,
|
||||
} from '@/lib/settings'
|
||||
import { useKeyboardShortcuts } from '@/lib/useKeyboardShortcuts'
|
||||
import { fetchTags } from '@/lib/tags'
|
||||
import { dateString, inputClass } from '@/lib/utils'
|
||||
|
||||
@@ -180,12 +181,35 @@ const TagListPage: FC = () => {
|
||||
navigate (`${ location.pathname }?${ qs.toString () }`)
|
||||
}
|
||||
|
||||
const updatePage = (nextPage: number) => {
|
||||
const qs = new URLSearchParams (location.search)
|
||||
qs.set ('page', String (Math.max (1, nextPage)))
|
||||
navigate (`${ location.pathname }?${ qs.toString () }`)
|
||||
}
|
||||
|
||||
const defaultDirection = { name: 'asc',
|
||||
category: 'asc',
|
||||
post_count: 'desc',
|
||||
created_at: 'desc',
|
||||
updated_at: 'desc' } as const
|
||||
|
||||
useKeyboardShortcuts (useMemo (() => ({
|
||||
...(page > 1
|
||||
? {
|
||||
'pagination.previous': () => {
|
||||
updatePage (page - 1)
|
||||
},
|
||||
}
|
||||
: { }),
|
||||
...(totalPages > 0 && page < totalPages
|
||||
? {
|
||||
'pagination.next': () => {
|
||||
updatePage (Math.min (page + 1, totalPages))
|
||||
},
|
||||
}
|
||||
: { }),
|
||||
}), [page, totalPages, location.search]))
|
||||
|
||||
return (
|
||||
<MainArea>
|
||||
<Helmet>
|
||||
@@ -218,6 +242,7 @@ const TagListPage: FC = () => {
|
||||
{({ invalid }) => (
|
||||
<input
|
||||
type="text"
|
||||
data-shortcut-focus-search="true"
|
||||
value={name}
|
||||
onChange={e => setName (e.target.value)}
|
||||
className={inputClass (invalid)}/>)}
|
||||
|
||||
@@ -10,6 +10,7 @@ import Label from '@/components/common/Label'
|
||||
import PageTitle from '@/components/common/PageTitle'
|
||||
import MainArea from '@/components/layout/MainArea'
|
||||
import TagLink from '@/components/TagLink'
|
||||
import KeyboardSettingsSection from '@/components/users/KeyboardSettingsSection'
|
||||
import InheritDialogue from '@/components/users/InheritDialogue'
|
||||
import UserCodeDialogue from '@/components/users/UserCodeDialogue'
|
||||
import { CATEGORY_NAMES, CATEGORIES } from '@/consts'
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
setClientAppearanceThemes,
|
||||
updateUserSettings,
|
||||
} from '@/lib/settings'
|
||||
import { useKeyboardShortcutSettings } from '@/lib/useKeyboardShortcuts'
|
||||
import { cn, inputClass } from '@/lib/utils'
|
||||
import { useValidationErrors } from '@/lib/useValidationErrors'
|
||||
|
||||
@@ -100,27 +102,6 @@ const builtinThemeLabel: Record<'light' | 'dark', string> = {
|
||||
dark: 'ダーク・モード',
|
||||
}
|
||||
|
||||
const keyboardShortcutRows = [
|
||||
{
|
||||
action: '投稿詳細を開く',
|
||||
key: 'Enter',
|
||||
scope: '一覧・検索結果',
|
||||
conflict: 'なし',
|
||||
},
|
||||
{
|
||||
action: '候補タグを選ぶ',
|
||||
key: '↑ / ↓ / Enter',
|
||||
scope: 'タグ入力',
|
||||
conflict: 'なし',
|
||||
},
|
||||
{
|
||||
action: '候補タグを閉じる',
|
||||
key: 'Escape',
|
||||
scope: 'タグ入力',
|
||||
conflict: 'なし',
|
||||
},
|
||||
]
|
||||
|
||||
const previewTag = (
|
||||
id: number,
|
||||
name: string,
|
||||
@@ -389,39 +370,6 @@ const ThemeSection: FC<SharedSectionProps> = ({
|
||||
</section>)
|
||||
|
||||
|
||||
const KeyboardSection: FC = () => (
|
||||
<section className={sectionClassName}>
|
||||
<h2 className="text-xl font-bold">キーボード</h2>
|
||||
|
||||
<p className="text-sm text-muted-foreground">
|
||||
現段階では一覧表示のみです.将来は action / key / scope / conflict を持つ
|
||||
key bind 設定へ拡張できる形にしています.
|
||||
</p>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[28rem] table-fixed border-collapse text-sm">
|
||||
<thead className="border-b border-border">
|
||||
<tr>
|
||||
<th className="p-2 text-left">操作</th>
|
||||
<th className="p-2 text-left">キー</th>
|
||||
<th className="p-2 text-left">適用範囲</th>
|
||||
<th className="p-2 text-left">競合</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{keyboardShortcutRows.map (row => (
|
||||
<tr key={row.action} className="border-b border-border/60 last:border-b-0">
|
||||
<td className="p-2">{row.action}</td>
|
||||
<td className="p-2 font-mono">{row.key}</td>
|
||||
<td className="p-2">{row.scope}</td>
|
||||
<td className="p-2">{row.conflict}</td>
|
||||
</tr>))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>)
|
||||
|
||||
|
||||
const SettingPage: FC<Props> = ({ user, setUser }) => {
|
||||
const location = useLocation ()
|
||||
const navigate = useNavigate ()
|
||||
@@ -440,6 +388,7 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
|
||||
const [draftCustomThemes, setDraftCustomThemes] =
|
||||
useState<Record<string, CustomClientTheme>> (getClientCustomThemes ())
|
||||
const [userCodeVsbl, setUserCodeVsbl] = useState (false)
|
||||
const { conflictActionIds } = useKeyboardShortcutSettings ()
|
||||
const {
|
||||
baseErrors: userBaseErrors,
|
||||
fieldErrors: userFieldErrors,
|
||||
@@ -472,8 +421,8 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
|
||||
const settingsTabErrors = useMemo<Record<SettingsTab, boolean>> (() => ({
|
||||
account: Boolean (userFieldErrors.name?.length),
|
||||
theme: Boolean (settingsFieldErrors.theme?.length),
|
||||
keyboard: false,
|
||||
}), [settingsFieldErrors.theme, userFieldErrors.name])
|
||||
keyboard: conflictActionIds.size > 0,
|
||||
}), [conflictActionIds.size, settingsFieldErrors.theme, userFieldErrors.name])
|
||||
|
||||
const applyDraftThemeSelection = (
|
||||
activeThemeId: ActiveThemeId,
|
||||
@@ -780,7 +729,7 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
|
||||
<>
|
||||
<AccountSection {...sectionProps}/>
|
||||
<ThemeSection {...sectionProps}/>
|
||||
<KeyboardSection/>
|
||||
<KeyboardSettingsSection sectionClassName={sectionClassName}/>
|
||||
</>)}
|
||||
</>)}
|
||||
</div>
|
||||
@@ -828,7 +777,9 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
|
||||
<>
|
||||
{activeTab === 'account' && <AccountSection {...sectionProps}/>}
|
||||
{activeTab === 'theme' && <ThemeSection {...sectionProps}/>}
|
||||
{activeTab === 'keyboard' && <KeyboardSection/>}
|
||||
{activeTab === 'keyboard' && (
|
||||
<KeyboardSettingsSection sectionClassName={sectionClassName}/>
|
||||
)}
|
||||
</>)}
|
||||
</div>)}
|
||||
</div>
|
||||
|
||||
新しい課題から参照
ユーザをブロックする