タグ “廃止” 追加 (#378) #379
@@ -4,17 +4,18 @@ class WikiPagesController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
title = params[:title].to_s.strip
|
title = params[:title].to_s.strip
|
||||||
if title.blank?
|
if title.blank?
|
||||||
return render json: WikiPageRepr.base(WikiPage.joins(:tag_name).includes(:tag_name))
|
return render json: WikiPageRepr.base(
|
||||||
|
WikiPage.joins(:tag_name).includes(tag_name: :tag))
|
||||||
end
|
end
|
||||||
|
|
||||||
q = WikiPage.joins(:tag_name).includes(:tag_name)
|
q = WikiPage.joins(:tag_name).includes(tag_name: :tag)
|
||||||
.where('tag_names.name LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%")
|
.where('tag_names.name LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%")
|
||||||
render json: WikiPageRepr.base(q.limit(20))
|
render json: WikiPageRepr.base(q.limit(20))
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
page = WikiPage.joins(:tag_name)
|
page = WikiPage.joins(:tag_name)
|
||||||
.includes(:tag_name)
|
.includes(tag_name: :tag)
|
||||||
.find_by(id: params[:id])
|
.find_by(id: params[:id])
|
||||||
render_wiki_page_or_404 page
|
render_wiki_page_or_404 page
|
||||||
end
|
end
|
||||||
@@ -22,7 +23,7 @@ class WikiPagesController < ApplicationController
|
|||||||
def show_by_title
|
def show_by_title
|
||||||
title = params[:title].to_s.strip
|
title = params[:title].to_s.strip
|
||||||
page = WikiPage.joins(:tag_name)
|
page = WikiPage.joins(:tag_name)
|
||||||
.includes(:tag_name)
|
.includes(tag_name: :tag)
|
||||||
.find_by(tag_name: { name: title })
|
.find_by(tag_name: { name: title })
|
||||||
render_wiki_page_or_404 page
|
render_wiki_page_or_404 page
|
||||||
end
|
end
|
||||||
@@ -51,7 +52,7 @@ class WikiPagesController < ApplicationController
|
|||||||
from = params[:from].presence
|
from = params[:from].presence
|
||||||
to = params[:to].presence
|
to = params[:to].presence
|
||||||
|
|
||||||
page = WikiPage.joins(:tag_name).includes(:tag_name).find(id)
|
page = WikiPage.joins(:tag_name).includes(tag_name: :tag).find(id)
|
||||||
|
|
||||||
from_rev = from && page.wiki_revisions.find(from)
|
from_rev = from && page.wiki_revisions.find(from)
|
||||||
to_rev = to ? page.wiki_revisions.find(to) : page.current_revision
|
to_rev = to ? page.wiki_revisions.find(to) : page.current_revision
|
||||||
@@ -76,6 +77,7 @@ class WikiPagesController < ApplicationController
|
|||||||
|
|
||||||
render json: { wiki_page_id: page.id,
|
render json: { wiki_page_id: page.id,
|
||||||
title: page.title,
|
title: page.title,
|
||||||
|
deprecated_at: page.deprecated_at,
|
||||||
older_revision_id: from_rev&.id,
|
older_revision_id: from_rev&.id,
|
||||||
newer_revision_id: to_rev.id,
|
newer_revision_id: to_rev.id,
|
||||||
diff: diff_json }
|
diff: diff_json }
|
||||||
@@ -157,7 +159,7 @@ class WikiPagesController < ApplicationController
|
|||||||
def changes
|
def changes
|
||||||
id = params[:id].presence
|
id = params[:id].presence
|
||||||
q = WikiRevision.joins(wiki_page: :tag_name)
|
q = WikiRevision.joins(wiki_page: :tag_name)
|
||||||
.includes(:created_user, wiki_page: :tag_name)
|
.includes(:created_user, wiki_page: { tag_name: :tag })
|
||||||
.order(id: :desc)
|
.order(id: :desc)
|
||||||
q = q.where(wiki_page_id: id) if id
|
q = q.where(wiki_page_id: id) if id
|
||||||
|
|
||||||
@@ -165,7 +167,9 @@ class WikiPagesController < ApplicationController
|
|||||||
{ revision_id: rev.id,
|
{ revision_id: rev.id,
|
||||||
pred: rev.base_revision_id,
|
pred: rev.base_revision_id,
|
||||||
succ: nil,
|
succ: nil,
|
||||||
wiki_page: { id: rev.wiki_page_id, title: rev.wiki_page.title },
|
wiki_page: { id: rev.wiki_page_id,
|
||||||
|
title: rev.wiki_page.title,
|
||||||
|
deprecated_at: rev.wiki_page.deprecated_at },
|
||||||
user: rev.created_user && { id: rev.created_user.id, name: rev.created_user.name },
|
user: rev.created_user && { id: rev.created_user.id, name: rev.created_user.name },
|
||||||
kind: rev.kind,
|
kind: rev.kind,
|
||||||
message: rev.message,
|
message: rev.message,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class WikiPage < ApplicationRecord
|
|||||||
validates :body, presence: true
|
validates :body, presence: true
|
||||||
|
|
||||||
def title = tag_name.name
|
def title = tag_name.name
|
||||||
|
def deprecated_at = tag_name.tag&.deprecated_at
|
||||||
|
|
||||||
def title= val
|
def title= val
|
||||||
(self.tag_name ||= build_tag_name).name = val
|
(self.tag_name ||= build_tag_name).name = val
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
module WikiPageRepr
|
module WikiPageRepr
|
||||||
BASE = { methods: [:title] }.freeze
|
BASE = { methods: [:title, :deprecated_at] }.freeze
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,21 @@ describe ('TagLink', () => {
|
|||||||
expect (screen.getByText ('4')).toBeInTheDocument ()
|
expect (screen.getByText ('4')).toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it ('does not append deprecated state to the rendered tag name', () => {
|
||||||
|
renderWithProviders (
|
||||||
|
<TagLink
|
||||||
|
tag={buildTag ({
|
||||||
|
name: '旧タグ',
|
||||||
|
deprecatedAt: '2026-06-01T00:00:00.000Z',
|
||||||
|
})}
|
||||||
|
withWiki={false}
|
||||||
|
withCount={false}/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect (screen.getByRole ('link', { name: '旧タグ' })).toBeInTheDocument ()
|
||||||
|
expect (screen.queryByText ('(廃止)')).not.toBeInTheDocument ()
|
||||||
|
})
|
||||||
|
|
||||||
it ('links wiki markers to the correct detail route', () => {
|
it ('links wiki markers to the correct detail route', () => {
|
||||||
renderWithProviders (
|
renderWithProviders (
|
||||||
<TagLink tag={buildTag ({ hasWiki: true, name: 'a/b' })}/>,
|
<TagLink tag={buildTag ({ hasWiki: true, name: 'a/b' })}/>,
|
||||||
|
|||||||
@@ -128,4 +128,4 @@ const TagLink: FC<Props> = ({ tag,
|
|||||||
</>)
|
</>)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TagLink
|
export default TagLink
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ export const fetchTagByName = async (name: string): Promise<Tag | null> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const fetchTagChanges = async (
|
export const fetchTagChanges = async (
|
||||||
{ id, page, limit }: {
|
{ id, page, limit }: {
|
||||||
id?: string
|
id?: string
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const WikiDetailPage: FC = () => {
|
|||||||
queryFn: () => fetchWikiPageByTitle (title, { version }) })
|
queryFn: () => fetchWikiPageByTitle (title, { version }) })
|
||||||
|
|
||||||
const effectiveTitle = wikiPage?.title ?? title
|
const effectiveTitle = wikiPage?.title ?? title
|
||||||
|
const deprecated = wikiPage?.deprecatedAt != null
|
||||||
|
|
||||||
const { data: tag } = useQuery ({
|
const { data: tag } = useQuery ({
|
||||||
enabled: Boolean (effectiveTitle),
|
enabled: Boolean (effectiveTitle),
|
||||||
@@ -88,7 +89,7 @@ const WikiDetailPage: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<MainArea>
|
<MainArea>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{`${ title } Wiki | ${ SITE_TITLE }`}</title>
|
<title>{`${ effectiveTitle }${ deprecated ? '(廃止)' : '' } Wiki | ${ SITE_TITLE }`}</title>
|
||||||
{!(wikiPage?.body) && <meta name="robots" content="noindex"/>}
|
{!(wikiPage?.body) && <meta name="robots" content="noindex"/>}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
@@ -110,10 +111,13 @@ const WikiDetailPage: FC = () => {
|
|||||||
|
|
||||||
<article className="prose dark:prose-invert mx-auto p-4">
|
<article className="prose dark:prose-invert mx-auto p-4">
|
||||||
<h1 className="prose-a:no-underline">
|
<h1 className="prose-a:no-underline">
|
||||||
<TagLink tag={tag ?? defaultTag}
|
<TagLink tag={tag ?? { ...defaultTag,
|
||||||
|
name: effectiveTitle,
|
||||||
|
deprecatedAt: wikiPage?.deprecatedAt ?? null }}
|
||||||
withWiki={false}
|
withWiki={false}
|
||||||
withCount={false}
|
withCount={false}
|
||||||
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
|
{...(version && { to: `/wiki/${ encodeURIComponent (title) }` })}/>
|
||||||
|
{deprecated && <span>(廃止)</span>}
|
||||||
</h1>
|
</h1>
|
||||||
{loading ? <div>Loading...</div> : <WikiBody title={title} body={wikiPage?.body}/>}
|
{loading ? <div>Loading...</div> : <WikiBody title={title} body={wikiPage?.body}/>}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ const WikiDiffPage: FC = () => {
|
|||||||
const query = new URLSearchParams (location.search)
|
const query = new URLSearchParams (location.search)
|
||||||
const from = query.get ('from')
|
const from = query.get ('from')
|
||||||
const to = query.get ('to')
|
const to = query.get ('to')
|
||||||
|
const displayTitle = diff
|
||||||
|
? `${ diff.title }${ diff.deprecatedAt != null ? '(廃止)' : '' }`
|
||||||
|
: ''
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
@@ -33,9 +36,9 @@ const WikiDiffPage: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<MainArea>
|
<MainArea>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{`Wiki 差分: ${ diff?.title } | ${ SITE_TITLE }`}</title>
|
<title>{`Wiki 差分: ${ displayTitle } | ${ SITE_TITLE }`}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<PageTitle>{diff?.title}</PageTitle>
|
<PageTitle>{displayTitle}</PageTitle>
|
||||||
<div className="prose mx-auto p-4">
|
<div className="prose mx-auto p-4">
|
||||||
{diff
|
{diff
|
||||||
? (
|
? (
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ const WikiHistoryPage: FC = () => {
|
|||||||
to={`/wiki/${ encodeURIComponent (change.wikiPage.title) }?version=${ change.revisionId }`}>
|
to={`/wiki/${ encodeURIComponent (change.wikiPage.title) }?version=${ change.revisionId }`}>
|
||||||
{change.wikiPage.title}
|
{change.wikiPage.title}
|
||||||
</PrefetchLink>
|
</PrefetchLink>
|
||||||
|
{change.wikiPage.deprecatedAt != null && <span>(廃止)</span>}
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
{change.pred == null ? '新規' : '更新'}
|
{change.pred == null ? '新規' : '更新'}
|
||||||
|
|||||||
@@ -42,4 +42,17 @@ describe ('WikiSearchPage', () => {
|
|||||||
})
|
})
|
||||||
expect (await screen.findByRole ('link', { name: '検索結果' })).toBeInTheDocument ()
|
expect (await screen.findByRole ('link', { name: '検索結果' })).toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it ('marks deprecated wiki tags in the result title', async () => {
|
||||||
|
api.apiGet.mockResolvedValueOnce ([
|
||||||
|
buildWikiPage ({
|
||||||
|
title: '旧タグ',
|
||||||
|
deprecatedAt: '2026-06-01T00:00:00.000Z',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
renderWithProviders (<WikiSearchPage/>)
|
||||||
|
|
||||||
|
expect (await screen.findByRole ('link', { name: '旧タグ(廃止)' })).toBeInTheDocument ()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ const WikiSearchPage: FC = () => {
|
|||||||
<PrefetchLink to={`/wiki/${ encodeURIComponent (page.title) }`}>
|
<PrefetchLink to={`/wiki/${ encodeURIComponent (page.title) }`}>
|
||||||
{page.title}
|
{page.title}
|
||||||
</PrefetchLink>
|
</PrefetchLink>
|
||||||
|
{page.deprecatedAt != null && <span>(廃止)</span>}
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
{dateString (page.updatedAt)}
|
{dateString (page.updatedAt)}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export const buildUser = (overrides: Partial<User> = {}): User => ({
|
|||||||
export const buildWikiPage = (overrides: Partial<WikiPage> = {}): WikiPage => ({
|
export const buildWikiPage = (overrides: Partial<WikiPage> = {}): WikiPage => ({
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'テストWiki',
|
title: 'テストWiki',
|
||||||
|
deprecatedAt: null,
|
||||||
createdUserId: 1,
|
createdUserId: 1,
|
||||||
updatedUserId: 1,
|
updatedUserId: 1,
|
||||||
createdAt: '2026-01-02T03:04:05.000Z',
|
createdAt: '2026-01-02T03:04:05.000Z',
|
||||||
|
|||||||
+3
-1
@@ -299,6 +299,7 @@ export type ViewFlagBehavior = typeof ViewFlagBehavior[keyof typeof ViewFlagBeha
|
|||||||
export type WikiPage = {
|
export type WikiPage = {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
|
deprecatedAt: string | null
|
||||||
createdUserId: number
|
createdUserId: number
|
||||||
updatedUserId: number
|
updatedUserId: number
|
||||||
createdAt: string
|
createdAt: string
|
||||||
@@ -312,7 +313,7 @@ export type WikiPageChange = {
|
|||||||
revisionId: number
|
revisionId: number
|
||||||
pred: number | null
|
pred: number | null
|
||||||
succ: null
|
succ: null
|
||||||
wikiPage: Pick<WikiPage, 'id' | 'title'>
|
wikiPage: Pick<WikiPage, 'id' | 'title' | 'deprecatedAt'>
|
||||||
user: Pick<User, 'id' | 'name'>
|
user: Pick<User, 'id' | 'name'>
|
||||||
kind: 'content' | 'redirect'
|
kind: 'content' | 'redirect'
|
||||||
message: string | null
|
message: string | null
|
||||||
@@ -321,6 +322,7 @@ export type WikiPageChange = {
|
|||||||
export type WikiPageDiff = {
|
export type WikiPageDiff = {
|
||||||
wikiPageId: number
|
wikiPageId: number
|
||||||
title: string
|
title: string
|
||||||
|
deprecatedAt: string | null
|
||||||
olderRevisionId: number | null
|
olderRevisionId: number | null
|
||||||
newerRevisionId: number
|
newerRevisionId: number
|
||||||
diff: WikiPageDiffDiff[] }
|
diff: WikiPageDiffDiff[] }
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする