このコミットが含まれているのは:
2026-06-21 15:02:13 +09:00
コミット 8eb8fb355b
17個のファイルの変更210行の追加37行の削除
+8 -1
ファイルの表示
@@ -17,6 +17,10 @@ const mWiki = match<{ title: string }> ('/wiki/:title')
const mTag = match<{ id: string }> ('/tags/:id')
const boolFromQuery = (value: string | null): boolean =>
['1', 'true', 'on', 'yes', ''].includes ((value ?? '').toLowerCase ())
const prefetchWikiPagesIndex: Prefetcher = async (qc, url) => {
const title = url.searchParams.get ('title') ?? ''
@@ -156,13 +160,16 @@ const prefetchTagsIndex: Prefetcher = async (qc, url) => {
const createdTo = url.searchParams.get ('created_to') ?? ''
const updatedFrom = url.searchParams.get ('updated_from') ?? ''
const updatedTo = url.searchParams.get ('updated_to') ?? ''
const deprecated = url.searchParams.has ('deprecated')
? boolFromQuery (url.searchParams.get ('deprecated'))
: null
const page = Number (url.searchParams.get ('page') || 1)
const limit = Number (url.searchParams.get ('limit') || 20)
const order = (url.searchParams.get ('order') ?? 'post_count:desc') as FetchTagsOrder
const keys = {
post, name, category, postCountGTE, postCountLTE, createdFrom, createdTo,
updatedFrom, updatedTo, page, limit, order }
updatedFrom, updatedTo, deprecated, page, limit, order }
await qc.prefetchQuery ({
queryKey: tagsKeys.index (keys),
+1
ファイルの表示
@@ -20,6 +20,7 @@ const baseParams: FetchTagsParams = {
createdTo: '',
updatedFrom: '',
updatedTo: '',
deprecated: null,
page: 1,
limit: 30,
order: 'updated_at:desc',
+3 -1
ファイルの表示
@@ -10,7 +10,8 @@ import type { Deerjikist,
export const fetchTags = async (
{ post, name, category, postCountGTE, postCountLTE, createdFrom, createdTo,
updatedFrom, updatedTo, page, limit, order }: FetchTagsParams,
updatedFrom, updatedTo, deprecated,
page, limit, order }: FetchTagsParams,
): Promise<{ tags: Tag[]
count: number }> =>
await apiGet ('/tags', { params: {
@@ -23,6 +24,7 @@ export const fetchTags = async (
...(createdTo && { created_to: createdTo }),
...(updatedFrom && { updated_from: updatedFrom }),
...(updatedTo && { updated_to: updatedTo }),
...(deprecated != null && { deprecated: deprecated ? '1' : '0' }),
...(page && { page }),
...(limit && { limit }),
...(order && { order }) } })
+21 -1
ファイルの表示
@@ -19,7 +19,12 @@ import type { FC, FormEvent } from 'react'
import type { Category, Tag } from '@/types'
type TagFormField = 'name' | 'category' | 'aliases' | 'parentTags'
type TagFormField =
| 'name'
| 'category'
| 'aliases'
| 'parentTags'
| 'deprecated'
const TagDetailPage: FC = () => {
@@ -35,6 +40,7 @@ const TagDetailPage: FC = () => {
const [category, setCategory] = useState<Category> ('general')
const [aliases, setAliases] = useState ('')
const [parentTags, setParentTags] = useState ('')
const [deprecated, setDeprecated] = useState (false)
const [disabled, setDisabled] = useState (true)
const { baseErrors, fieldErrors, clearValidationErrors, applyValidationError } =
useValidationErrors<TagFormField> ()
@@ -50,6 +56,7 @@ const TagDetailPage: FC = () => {
formData.append ('category', category)
formData.append ('aliases', aliases)
formData.append ('parent_tags', parentTags)
formData.append ('deprecated', deprecated ? '1' : '0')
try
{
@@ -59,6 +66,7 @@ const TagDetailPage: FC = () => {
setCategory (data.category as Category)
setAliases (data.aliases.join (' '))
setParentTags (data.parents.map (t => t.name).join (' '))
setDeprecated (Boolean (data.deprecatedAt))
qc.invalidateQueries ({ queryKey: postsKeys.root })
qc.invalidateQueries ({ queryKey: tagsKeys.root })
@@ -82,6 +90,7 @@ const TagDetailPage: FC = () => {
setCategory (tag.category as Category)
setAliases (tag.aliases.join (' '))
setParentTags (tag.parents.map (t => t.name).join (' '))
setDeprecated (Boolean (tag.deprecatedAt))
setDisabled (tag.category === 'nico')
}, [tag])
@@ -165,6 +174,17 @@ const TagDetailPage: FC = () => {
</>)}
</FormField>
<FormField label="廃止済" messages={fieldErrors.deprecated}>
{({ describedBy, invalid }) => (
<input
type="checkbox"
disabled={disabled}
checked={deprecated}
onChange={e => setDeprecated (e.target.checked)}
aria-describedby={describedBy}
aria-invalid={invalid}/>)}
</FormField>
<div className="py-3">
<button
type="submit"
+25 -6
ファイルの表示
@@ -20,17 +20,28 @@ import type { FC } from 'react'
const renderDiff = (diff: { current: string | null; prev: string | null }) => (
<>
{(diff.prev && diff.prev !== diff.current) && (
{diff.prev !== diff.current
? (
<>
<del className="text-red-600 dark:text-red-400">
{diff.prev}
{diff.prev && <>{diff.prev}<br/></>}
</del>
{diff.current && <br/>}
</>)}
{diff.current}
<ins className="text-green-600 dark:text-green-400">
{diff.current}
</ins>
</>)
: diff.current}
</>)
const tagStateLabel = (deprecatedAt: string | null) => deprecatedAt ? '廃止' : ''
const renderStateDiff = (diff: { current: string | null; prev: string | null }) =>
renderDiff ({ current: tagStateLabel (diff.current),
prev: tagStateLabel (diff.prev) })
const TagHistoryPage: FC = () => {
const location = useLocation ()
const query = new URLSearchParams (location.search)
@@ -72,6 +83,8 @@ const TagHistoryPage: FC = () => {
<col className="w-96"/>
{/* カテゴリ */}
<col className="w-96"/>
{/* 状態 */}
<col className="w-32"/>
{/* 別名 */}
<col className="w-[48rem]"/>
{/* 上位タグ */}
@@ -87,6 +100,7 @@ const TagHistoryPage: FC = () => {
<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>
<th className="p-2 text-left"></th>
<th className="p-2 text-left"></th>
<th className="p-2 text-left"></th>
@@ -106,6 +120,9 @@ const TagHistoryPage: FC = () => {
prev: (change.category.prev
&& CATEGORY_NAMES[change.category.prev]) })}
</td>
<td className="p-2 break-all">
{renderStateDiff (change.deprecatedAt)}
</td>
<td className="p-2">
{change.aliases.map ((tag, i) => (
tag.type === 'added'
@@ -178,6 +195,7 @@ const TagHistoryPage: FC = () => {
`/tags/${ change.tagId }`,
{ name: change.name.current,
category: change.category.current,
deprecated: change.deprecatedAt.current ? '1' : '0',
aliases:
change.aliases
.filter (t => t.type !== 'removed')
@@ -211,4 +229,5 @@ const TagHistoryPage: FC = () => {
</MainArea>)
}
export default TagHistoryPage
export default TagHistoryPage
+35 -2
ファイルの表示
@@ -29,6 +29,13 @@ const setIf = (qs: URLSearchParams, k: string, v: string | null) => {
}
const boolFromQuery = (value: string | null): boolean =>
['1', 'true', 'on', 'yes', ''].includes ((value ?? '').toLowerCase ())
const tagStateLabel = (deprecatedAt: string | null) => deprecatedAt ? '廃止' : ''
const TagListPage: FC = () => {
const location = useLocation ()
@@ -48,6 +55,9 @@ const TagListPage: FC = () => {
const qCreatedTo = query.get ('created_to') ?? ''
const qUpdatedFrom = query.get ('updated_from') ?? ''
const qUpdatedTo = query.get ('updated_to') ?? ''
const qDeprecated = query.has ('deprecated')
? boolFromQuery (query.get ('deprecated'))
: null
const order = (query.get ('order') || 'post_count:desc') as FetchTagsOrder
const [name, setName] = useState ('')
@@ -58,6 +68,7 @@ const TagListPage: FC = () => {
const [createdTo, setCreatedTo] = useState<string | null> (null)
const [updatedFrom, setUpdatedFrom] = useState<string | null> (null)
const [updatedTo, setUpdatedTo] = useState<string | null> (null)
const [deprecated, setDeprecated] = useState<boolean | null> (null)
const keys = {
page, limit, order,
@@ -69,7 +80,8 @@ const TagListPage: FC = () => {
createdFrom: qCreatedFrom,
createdTo: qCreatedTo,
updatedFrom: qUpdatedFrom,
updatedTo: qUpdatedTo }
updatedTo: qUpdatedTo,
deprecated: qDeprecated }
const { data, isLoading: loading } = useQuery ({
queryKey: tagsKeys.index (keys),
queryFn: () => fetchTags (keys) })
@@ -85,10 +97,11 @@ const TagListPage: FC = () => {
setCreatedTo (qCreatedTo)
setUpdatedFrom (qUpdatedFrom)
setUpdatedTo (qUpdatedTo)
setDeprecated (qDeprecated)
document.querySelector ('table')?.scrollIntoView ({ behavior: 'smooth' })
}, [location.search, qCategory, qCreatedFrom, qCreatedTo, qName, qPostCountGTE,
qPostCountLTE, qUpdatedFrom, qUpdatedTo])
qPostCountLTE, qUpdatedFrom, qUpdatedTo, qDeprecated])
const handleSearch = (e: FormEvent) => {
e.preventDefault ()
@@ -104,6 +117,8 @@ const TagListPage: FC = () => {
setIf (qs, 'created_to', createdTo)
setIf (qs, 'updated_from', updatedFrom)
setIf (qs, 'updated_to', updatedTo)
if (deprecated != null)
qs.set ('deprecated', deprecated ? '1' : '0')
qs.set ('page', '1')
qs.set ('order', order)
@@ -201,6 +216,21 @@ const TagListPage: FC = () => {
</>)}
</FormField>
<FormField label="状態">
{({ invalid }) => (
<select
value={deprecated == null ? '' : (deprecated ? '1' : '0')}
onChange={e => setDeprecated (
e.target.value === ''
? null
: e.target.value === '1')}
className={inputClass (invalid)}>
<option value="">&nbsp;</option>
<option value="0"></option>
<option value="1"></option>
</select>)}
</FormField>
<div className="py-3">
<button
type="submit"
@@ -219,6 +249,7 @@ const TagListPage: FC = () => {
<col className="w-72"/>
<col className="w-16"/>
<col className="w-48"/>
<col className="w-32"/>
<col className="w-72"/>
<col className="w-48"/>
<col className="w-56"/>
@@ -249,6 +280,7 @@ const TagListPage: FC = () => {
currentOrder={order}
defaultDirection={defaultDirection}/>
</th>
<th className="p-2 text-left whitespace-nowrap"></th>
<th className="p-2 text-left whitespace-nowrap"></th>
<th className="p-2 text-left whitespace-nowrap"></th>
<th className="p-2 text-left whitespace-nowrap">
@@ -280,6 +312,7 @@ const TagListPage: FC = () => {
</td>
<td className="p-2 text-right">{row.postCount}</td>
<td className="p-2">{CATEGORY_NAMES[row.category]}</td>
<td className="p-2">{tagStateLabel (row.deprecatedAt)}</td>
<td className="p-2">{row.aliases.join (' ')}</td>
<td className="p-2">
{row.parents.map (t => (
+1
ファイルの表示
@@ -13,6 +13,7 @@ export const buildTag = (overrides: Partial<Tag> = {}): Tag => ({
id: 1,
name: 'テストタグ',
category: 'general',
deprecatedAt: null,
aliases: [],
parents: [],
postCount: 12,
+15 -12
ファイルの表示
@@ -39,18 +39,19 @@ export type FetchTagsOrderField =
| 'updated_at'
export type FetchTagsParams = {
post: number | null
name: string
category: Category | null
postCountGTE: number
postCountLTE: number | null
createdFrom: string
createdTo: string
updatedFrom: string
updatedTo: string
page: number
limit: number
order: FetchTagsOrder }
post: number | null
name: string
category: Category | null
postCountGTE: number
postCountLTE: number | null
createdFrom: string
createdTo: string
updatedFrom: string
updatedTo: string
deprecated: boolean | null
page: number
limit: number
order: FetchTagsOrder }
export type FetchNicoTagsParams = {
name: string
@@ -196,6 +197,7 @@ export type Tag = {
id: number
name: string
category: Category
deprecatedAt: string | null
aliases: string[]
parents: Tag[]
postCount: number
@@ -213,6 +215,7 @@ export type TagVersion = {
eventType: 'create' | 'update' | 'discard' | 'restore'
name: { current: string; prev: string | null }
category: { current: Category; prev: Category | null }
deprecatedAt: { current: string | null; prev: string | null }
aliases: { name: string; type: 'context' | 'added' | 'removed' }[]
parentTags: { tag: Tag; type: 'context' | 'added' | 'removed' }[]
createdAt: string