コミットを比較

...

6 コミット

作成者 SHA1 メッセージ 日付
みてるぞ 250ee3f011 Merge branch 'main' into '#106' 2026-03-04 23:33:27 +09:00
みてるぞ 7d1a87f452 表のレーアウト統一(#194) (#271)
#194

#194

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #271
2026-02-24 21:29:00 +09:00
みてるぞ a0d6aeb91e タグ補完ウィンドウ(#103) (#269)
#103

Merge remote-tracking branch 'origin/main' into feature/103

#103

#103 タグ補完からニコタグ除外

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #269
2026-02-22 23:32:01 +09:00
みてるぞ be983e4ad1 オリジナルの投稿日時 Safari でのバグ修正(#129) (#265)
Merge branch 'main' into feature/129

#129

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #265
2026-02-22 02:15:07 +09:00
みてるぞ 82302cd3d1 Representations 追加(#241) (#267)
#241

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #267
2026-02-22 01:50:56 +09:00
みてるぞ a01c63d972 PostEmbed の安全性強化(#130) (#263)
#130

#130

#130

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #263
2026-02-13 12:39:51 +09:00
16個のファイルの変更198行の追加102行の削除
+3 -5
ファイルの表示
@@ -1,6 +1,4 @@
class NicoTagsController < ApplicationController class NicoTagsController < ApplicationController
TAG_JSON = { only: [:id, :category, :post_count], methods: [:name, :has_wiki] }.freeze
def index def index
limit = (params[:limit] || 20).to_i limit = (params[:limit] || 20).to_i
cursor = params[:cursor].presence cursor = params[:cursor].presence
@@ -19,8 +17,8 @@ class NicoTagsController < ApplicationController
end end
render json: { tags: tags.map { |tag| render json: { tags: tags.map { |tag|
tag.as_json(TAG_JSON).merge(linked_tags: tag.linked_tags.map { |lt| TagRepr.base(tag).merge(linked_tags: tag.linked_tags.map { |lt|
lt.as_json(TAG_JSON) TagRepr.base(lt)
}) })
}, next_cursor: } }, next_cursor: }
end end
@@ -41,6 +39,6 @@ class NicoTagsController < ApplicationController
tag.linked_tags = linked_tags tag.linked_tags = linked_tags
tag.save! tag.save!
render json: tag.linked_tags.map { |t| t.as_json(TAG_JSON) }, status: :ok render json: tag.linked_tags.map { |t| TagRepr.base(t) }, status: :ok
end end
end end
+6 -14
ファイルの表示
@@ -36,8 +36,7 @@ class PostsController < ApplicationController
end end
render json: { posts: posts.map { |post| render json: { posts: posts.map { |post|
post.as_json(include: { tags: { only: [:id, :category, :post_count], PostRepr.base(post).tap do |json|
methods: [:name, :has_wiki] } }).tap do |json|
json['thumbnail'] = json['thumbnail'] =
if post.thumbnail.attached? if post.thumbnail.attached?
rails_storage_proxy_url(post.thumbnail, only_path: false) rails_storage_proxy_url(post.thumbnail, only_path: false)
@@ -60,10 +59,7 @@ class PostsController < ApplicationController
viewed = current_user&.viewed?(post) || false viewed = current_user&.viewed?(post) || false
render json: (post render json: PostRepr.base(post).merge(viewed:)
.as_json(include: { tags: { only: [:id, :category, :post_count],
methods: [:name, :has_wiki] } })
.merge(viewed:))
end end
def show def show
@@ -102,9 +98,7 @@ class PostsController < ApplicationController
sync_post_tags!(post, tags) sync_post_tags!(post, tags)
post.reload post.reload
render json: post.as_json(include: { tags: { only: [:id, :category, :post_count], render json: PostRepr.base(post), status: :created
methods: [:name, :has_wiki] } }),
status: :created
else else
render json: { errors: post.errors.full_messages }, status: :unprocessable_entity render json: { errors: post.errors.full_messages }, status: :unprocessable_entity
end end
@@ -170,7 +164,7 @@ class PostsController < ApplicationController
events = [] events = []
pts.each do |pt| pts.each do |pt|
tag = pt.tag.as_json(only: [:id, :category], methods: [:name, :has_wiki]) tag = TagRepr.base(pt.tag)
post = pt.post post = pt.post
events << Event.new( events << Event.new(
@@ -269,8 +263,7 @@ class PostsController < ApplicationController
return nil unless tag return nil unless tag
if path.include?(tag_id) if path.include?(tag_id)
return tag.as_json(only: [:id, :category, :post_count], return TagRepr.base(tag).merge(children: [])
methods: [:name, :has_wiki]).merge(children: [])
end end
if memo.key?(tag_id) if memo.key?(tag_id)
@@ -282,8 +275,7 @@ class PostsController < ApplicationController
children = child_ids.filter_map { |cid| build_node.(cid, new_path) } children = child_ids.filter_map { |cid| build_node.(cid, new_path) }
memo[tag_id] = tag.as_json(only: [:id, :category, :post_count], memo[tag_id] = TagRepr.base(tag).merge(children:)
methods: [:name, :has_wiki]).merge(children:)
end end
root_ids.filter_map { |id| build_node.call(id, []) } root_ids.filter_map { |id| build_node.call(id, []) }
+5 -6
ファイルの表示
@@ -13,7 +13,7 @@ class TagsController < ApplicationController
tags = tags.where(posts: { id: post_id }) tags = tags.where(posts: { id: post_id })
end end
render json: tags.as_json(only: [:id, :category, :post_count], methods: [:name, :has_wiki]) render json: TagRepr.base(tags)
end end
def autocomplete def autocomplete
@@ -57,8 +57,7 @@ class TagsController < ApplicationController
tags = tags.order(Arel.sql('post_count DESC, tag_names.name')).limit(20).to_a tags = tags.order(Arel.sql('post_count DESC, tag_names.name')).limit(20).to_a
render json: tags.map { |tag| render json: tags.map { |tag|
tag.as_json(only: [:id, :category, :post_count], methods: [:name, :has_wiki]) TagRepr.base(tag).merge(matched_alias: matched_alias_by_tag_name_id[tag.tag_name_id])
.merge(matched_alias: matched_alias_by_tag_name_id[tag.tag_name_id])
} }
end end
@@ -67,7 +66,7 @@ class TagsController < ApplicationController
.includes(:tag_name, tag_name: :wiki_page) .includes(:tag_name, tag_name: :wiki_page)
.find_by(id: params[:id]) .find_by(id: params[:id])
if tag if tag
render json: tag.as_json(only: [:id, :category, :post_count], methods: [:name, :has_wiki]) render json: TagRepr.base(tag)
else else
head :not_found head :not_found
end end
@@ -81,7 +80,7 @@ class TagsController < ApplicationController
.includes(:tag_name, tag_name: :wiki_page) .includes(:tag_name, tag_name: :wiki_page)
.find_by(tag_names: { name: }) .find_by(tag_names: { name: })
if tag if tag
render json: tag.as_json(only: [:id, :category, :post_count], methods: [:name, :has_wiki]) render json: TagRepr.base(tag)
else else
head :not_found head :not_found
end end
@@ -104,6 +103,6 @@ class TagsController < ApplicationController
tag.update!(category:) tag.update!(category:)
end end
render json: tag.as_json(methods: [:name]) render json: TagRepr.base(tag)
end end
end end
+4 -7
ファイルの表示
@@ -4,14 +4,12 @@ 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: WikiPage.joins(:tag_name) return render json: WikiPageRepr.base(WikiPage.joins(:tag_name).includes(:tag_name))
.includes(:tag_name)
.as_json(methods: [:title])
end end
q = WikiPage.joins(:tag_name).includes(:tag_name) q = WikiPage.joins(:tag_name).includes(:tag_name)
.where('tag_names.name LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%") .where('tag_names.name LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%")
render json: q.limit(20).as_json(methods: [:title]) render json: WikiPageRepr.base(q.limit(20))
end end
def show def show
@@ -98,7 +96,7 @@ class WikiPagesController < ApplicationController
message = params[:message].presence message = params[:message].presence
Wiki::Commit.content!(page:, body:, created_user: current_user, message:) Wiki::Commit.content!(page:, body:, created_user: current_user, message:)
render json: page.as_json(methods: [:title]), status: :created render json: WikiPageRepr.base(page), status: :created
else else
render json: { errors: page.errors.full_messages }, render json: { errors: page.errors.full_messages },
status: :unprocessable_entity status: :unprocessable_entity
@@ -174,8 +172,7 @@ class WikiPagesController < ApplicationController
succ = page.succ_revision_id(revision_id) succ = page.succ_revision_id(revision_id)
updated_at = rev.created_at updated_at = rev.created_at
render json: page.as_json(methods: [:title]) render json: WikiPageRepr.base(page).merge(body:, revision_id:, pred:, succ:, updated_at:)
.merge(body:, revision_id:, pred:, succ:, updated_at:)
end end
def find_revision page def find_revision page
+16
ファイルの表示
@@ -0,0 +1,16 @@
# frozen_string_literal: true
module WikiPageRepr
BASE = { methods: [:title] }.freeze
module_function
def base wiki_page
wiki_page.as_json(BASE)
end
def many wiki_pages
wiki_pages.map { |p| base(p) }
end
end
+16
ファイルの表示
@@ -0,0 +1,16 @@
# frozen_string_literal: true
module PostRepr
BASE = { include: { tags: TagRepr::BASE } }.freeze
module_function
def base post
post.as_json(BASE)
end
def many posts
posts.map { |p| base(p) }
end
end
+16
ファイルの表示
@@ -0,0 +1,16 @@
# frozen_string_literal: true
module TagRepr
BASE = { only: [:id, :category, :post_count], methods: [:name, :has_wiki] }.freeze
module_function
def base tag
tag.as_json(BASE)
end
def many tags
tags.map { |t| base(t) }
end
end
+23 -5
ファイルの表示
@@ -18,17 +18,35 @@ export default (({ post }: Props) => {
{ {
case 'nicovideo.jp': case 'nicovideo.jp':
{ {
const [videoId] = url.pathname.match (/(?<=\/watch\/)[a-zA-Z0-9]+?(?=\/|$)/)! const mVideoId = url.pathname.match (/(?<=\/watch\/)[a-zA-Z0-9]+?(?=\/|$)/)
if (!(mVideoId))
break
const [videoId] = mVideoId
return <NicoViewer id={videoId} width={640} height={360}/> return <NicoViewer id={videoId} width={640} height={360}/>
} }
case 'twitter.com': case 'twitter.com':
case 'x.com': case 'x.com':
const [userId] = url.pathname.match (/(?<=\/)[^\/]+?(?=\/|$|\?)/)! {
const [statusId] = url.pathname.match (/(?<=\/status\/)\d+?(?=\/|$|\?)/)! const mUserId = url.pathname.match (/(?<=\/)[^\/]+?(?=\/|$|\?)/)
return <TwitterEmbed userId={userId} statusId={statusId}/> const mStatusId = url.pathname.match (/(?<=\/status\/)\d+?(?=\/|$|\?)/)
if (!(mUserId) || !(mStatusId))
break
const [userId] = mUserId
const [statusId] = mStatusId
return <TwitterEmbed userId={userId} statusId={statusId}/>
}
case 'youtube.com': case 'youtube.com':
{ {
const videoId = url.searchParams.get ('v')! const videoId = url.searchParams.get ('v')
if (!(videoId))
break
return ( return (
<YoutubeEmbed videoId={videoId} opts={{ playerVars: { <YoutubeEmbed videoId={videoId} opts={{ playerVars: {
playsinline: 1, playsinline: 1,
+28 -11
ファイルの表示
@@ -25,8 +25,8 @@ const getTokenAt = (value: string, pos: number) => {
} }
const replaceToken = (value: string, start: number, end: number, text: string) => ( const replaceToken = (value: string, start: number, end: number, text: string) =>
`${ value.slice (0, start) }${ text }${ value.slice (end) }`) `${ value.slice (0, start) }${ text }${ value.slice (end) }`
type Props = { type Props = {
@@ -38,16 +38,17 @@ export default (({ tags, setTags }: Props) => {
const ref = useRef<HTMLTextAreaElement> (null) const ref = useRef<HTMLTextAreaElement> (null)
const [bounds, setBounds] = useState<{ start: number; end: number }> ({ start: 0, end: 0 }) const [bounds, setBounds] = useState<{ start: number; end: number }> ({ start: 0, end: 0 })
const [focused, setFocused] = useState (false)
const [suggestions, setSuggestions] = useState<Tag[]> ([]) const [suggestions, setSuggestions] = useState<Tag[]> ([])
const [suggestionsVsbl, setSuggestionsVsbl] = useState (false) const [suggestionsVsbl, setSuggestionsVsbl] = useState (false)
const handleTagSelect = (tag: Tag) => { const handleTagSelect = (tag: Tag) => {
setSuggestionsVsbl (false) setSuggestionsVsbl (false)
const textarea = ref.current! const textarea = ref.current!
const newValue = replaceToken (tags, bounds.start, bounds.end, tag.name) const newValue = replaceToken (tags, bounds.start, bounds.end, tag.name + ' ')
setTags (newValue) setTags (newValue)
requestAnimationFrame (async () => { requestAnimationFrame (async () => {
const p = bounds.start + tag.name.length const p = bounds.start + tag.name.length + 1
textarea.selectionStart = textarea.selectionEnd = p textarea.selectionStart = textarea.selectionEnd = p
textarea.focus () textarea.focus ()
await recompute (p, newValue) await recompute (p, newValue)
@@ -56,14 +57,21 @@ export default (({ tags, setTags }: Props) => {
const recompute = async (pos: number, v: string = tags) => { const recompute = async (pos: number, v: string = tags) => {
const { start, end, token } = getTokenAt (v, pos) const { start, end, token } = getTokenAt (v, pos)
if (!(token.trim ()))
{
setSuggestionsVsbl (false)
return
}
setBounds ({ start, end }) setBounds ({ start, end })
const data = await apiGet<Tag[]> ('/tags/autocomplete', { params: { q: token } })
const data = await apiGet<Tag[]> ('/tags/autocomplete', { params: { q: token, nico: '0' } })
setSuggestions (data.filter (t => t.postCount > 0)) setSuggestions (data.filter (t => t.postCount > 0))
setSuggestionsVsbl (suggestions.length > 0) setSuggestionsVsbl (suggestions.length > 0)
} }
return ( return (
<div> <div className="relative w-full">
<Label></Label> <Label></Label>
<TextArea <TextArea
ref={ref} ref={ref}
@@ -72,11 +80,20 @@ export default (({ tags, setTags }: Props) => {
onSelect={async (ev: SyntheticEvent<HTMLTextAreaElement>) => { onSelect={async (ev: SyntheticEvent<HTMLTextAreaElement>) => {
const pos = (ev.target as HTMLTextAreaElement).selectionStart const pos = (ev.target as HTMLTextAreaElement).selectionStart
await recompute (pos) await recompute (pos)
}}
onFocus={() => {
setFocused (true)
}}
onBlur={() => {
setFocused (false)
setSuggestionsVsbl (false)
}}/> }}/>
<TagSearchBox suggestions={suggestionsVsbl && suggestions.length {focused && (
? suggestions <TagSearchBox
: [] as Tag[]} suggestions={suggestionsVsbl && suggestions.length > 0
activeIndex={-1} ? suggestions
onSelect={handleTagSelect}/> : [] as Tag[]}
activeIndex={-1}
onSelect={handleTagSelect}/>)}
</div>) </div>)
}) satisfies FC<Props> }) satisfies FC<Props>
+47 -28
ファイルの表示
@@ -1,5 +1,6 @@
import DateTimeField from '@/components/common/DateTimeField' import DateTimeField from '@/components/common/DateTimeField'
import Label from '@/components/common/Label' import Label from '@/components/common/Label'
import { Button } from '@/components/ui/button'
import type { FC } from 'react' import type { FC } from 'react'
@@ -16,34 +17,52 @@ export default (({ originalCreatedFrom,
setOriginalCreatedBefore }: Props) => ( setOriginalCreatedBefore }: Props) => (
<div> <div>
<Label></Label> <Label></Label>
<div className="my-1"> <div className="my-1 flex">
<DateTimeField <div className="w-80">
className="mr-2" <DateTimeField
value={originalCreatedFrom ?? undefined} className="mr-2"
onChange={setOriginalCreatedFrom} value={originalCreatedFrom ?? undefined}
onBlur={ev => { onChange={setOriginalCreatedFrom}
const v = ev.target.value onBlur={ev => {
if (!(v)) const v = ev.target.value
return if (!(v))
const d = new Date (v) return
if (d.getSeconds () === 0)
{ const d = new Date (v)
if (d.getMinutes () === 0 && d.getHours () === 0) if (d.getMinutes () === 0 && d.getHours () === 0)
d.setDate (d.getDate () + 1) d.setDate (d.getDate () + 1)
else else
d.setMinutes (d.getMinutes () + 1) d.setMinutes (d.getMinutes () + 1)
} setOriginalCreatedBefore (d.toISOString ())
else }}/>
d.setSeconds (d.getSeconds () + 1)
setOriginalCreatedBefore (d.toISOString ()) </div>
}}/> <div>
<Button
className="bg-gray-600 text-white rounded"
onClick={() => {
setOriginalCreatedFrom (null)
}}>
</Button>
</div>
</div> </div>
<div className="my-1"> <div className="my-1 flex">
<DateTimeField <div className="w-80">
className="mr-2" <DateTimeField
value={originalCreatedBefore ?? undefined} className="mr-2"
onChange={setOriginalCreatedBefore}/> value={originalCreatedBefore ?? undefined}
onChange={setOriginalCreatedBefore}/>
</div>
<div>
<Button
className="bg-gray-600 text-white rounded"
onClick={() => {
setOriginalCreatedBefore (null)
}}>
</Button>
</div>
</div> </div>
</div>)) satisfies FC<Props> </div>)) satisfies FC<Props>
+2 -4
ファイルの表示
@@ -5,7 +5,7 @@ import { cn } from '@/lib/utils'
import type { FC, FocusEvent } from 'react' import type { FC, FocusEvent } from 'react'
const pad = (n: number) => n.toString ().padStart (2, '0') const pad = (n: number): string => n.toString ().padStart (2, '0')
const toDateTimeLocalValue = (d: Date) => { const toDateTimeLocalValue = (d: Date) => {
@@ -14,8 +14,7 @@ const toDateTimeLocalValue = (d: Date) => {
const day = pad (d.getDate ()) const day = pad (d.getDate ())
const h = pad (d.getHours ()) const h = pad (d.getHours ())
const min = pad (d.getMinutes ()) const min = pad (d.getMinutes ())
const s = pad (d.getSeconds ()) return `${ y }-${ m }-${ day }T${ h }:${ min }:00`
return `${ y }-${ m }-${ day }T${ h }:${ min }:${ s }`
} }
@@ -37,7 +36,6 @@ export default (({ value, onChange, className, onBlur }: Props) => {
<input <input
className={cn ('border rounded p-2', className)} className={cn ('border rounded p-2', className)}
type="datetime-local" type="datetime-local"
step={1}
value={local} value={local}
onChange={ev => { onChange={ev => {
const v = ev.target.value const v = ev.target.value
+5 -4
ファイルの表示
@@ -1,6 +1,7 @@
import { clsx, type ClassValue } from 'clsx' import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
export function cn (...inputs: ClassValue[]) { import type { ClassValue } from 'clsx'
return twMerge(clsx(...inputs))
}
export const cn = (...inputs: ClassValue[]) => twMerge (clsx (...inputs))
+9 -5
ファイルの表示
@@ -10,6 +10,7 @@ import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config' import { SITE_TITLE } from '@/config'
import { fetchPostChanges } from '@/lib/posts' import { fetchPostChanges } from '@/lib/posts'
import { postsKeys } from '@/lib/queryKeys' import { postsKeys } from '@/lib/queryKeys'
import { cn } from '@/lib/utils'
import type { FC } from 'react' import type { FC } from 'react'
@@ -44,7 +45,7 @@ export default (() => {
{loading ? 'Loading...' : ( {loading ? 'Loading...' : (
<> <>
<table className="table-auto w-full border-collapse"> <table className="table-auto w-full border-collapse">
<thead> <thead className="border-b-2 border-black dark:border-white">
<tr> <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>
@@ -64,9 +65,12 @@ export default (() => {
++rowsCnt ++rowsCnt
} }
return ( return (
<tr key={`${ change.timestamp }-${ change.post.id }-${ change.tag.id }`}> <tr key={`${ change.timestamp }-${ change.post.id }-${ change.tag.id }`}
className={cn ('even:bg-gray-100 dark:even:bg-gray-700',
withPost && 'border-t')}>
{withPost && ( {withPost && (
<td className="align-top" rowSpan={rowsCnt}> <td className="align-top p-2 bg-white dark:bg-[#242424] border-r"
rowSpan={rowsCnt}>
<PrefetchLink to={`/posts/${ change.post.id }`}> <PrefetchLink to={`/posts/${ change.post.id }`}>
<img src={change.post.thumbnail || change.post.thumbnailBase || undefined} <img src={change.post.thumbnail || change.post.thumbnailBase || undefined}
alt={change.post.title || change.post.url} alt={change.post.title || change.post.url}
@@ -74,11 +78,11 @@ export default (() => {
className="w-40"/> className="w-40"/>
</PrefetchLink> </PrefetchLink>
</td>)} </td>)}
<td> <td className="p-2">
<TagLink tag={change.tag} withWiki={false} withCount={false}/> <TagLink tag={change.tag} withWiki={false} withCount={false}/>
{`${ change.changeType === 'add' ? '記載' : '消除' }`} {`${ change.changeType === 'add' ? '記載' : '消除' }`}
</td> </td>
<td> <td className="p-2">
{change.user ? ( {change.user ? (
<PrefetchLink to={`/users/${ change.user.id }`}> <PrefetchLink to={`/users/${ change.user.id }`}>
{change.user.name} {change.user.name}
+5 -5
ファイルの表示
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import TagLink from '@/components/TagLink' import TagLink from '@/components/TagLink'
import SectionTitle from '@/components/common/SectionTitle' import PageTitle from '@/components/common/PageTitle'
import TextArea from '@/components/common/TextArea' import TextArea from '@/components/common/TextArea'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import { toast } from '@/components/ui/use-toast' import { toast } from '@/components/ui/use-toast'
@@ -92,13 +92,13 @@ export default ({ user }: Props) => {
</Helmet> </Helmet>
<div className="max-w-xl"> <div className="max-w-xl">
<SectionTitle></SectionTitle> <PageTitle></PageTitle>
</div> </div>
<div className="mt-4"> <div className="mt-4">
{nicoTags.length > 0 && ( {nicoTags.length > 0 && (
<table className="table-auto w-full border-collapse mb-4"> <table className="table-auto w-full border-collapse mb-4">
<thead> <thead className="border-b-2 border-black dark:border-white">
<tr> <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>
@@ -107,7 +107,7 @@ export default ({ user }: Props) => {
</thead> </thead>
<tbody> <tbody>
{nicoTags.map ((tag, i) => ( {nicoTags.map ((tag, i) => (
<tr key={i}> <tr key={i} className="even:bg-gray-100 dark:even:bg-gray-700">
<td className="p-2"> <td className="p-2">
<TagLink tag={tag} withWiki={false} withCount={false}/> <TagLink tag={tag} withWiki={false} withCount={false}/>
</td> </td>
@@ -125,7 +125,7 @@ export default ({ user }: Props) => {
</span>))} </span>))}
</td> </td>
{memberFlg && ( {memberFlg && (
<td> <td className="p-2">
<a href="#" onClick={ev => { <a href="#" onClick={ev => {
ev.preventDefault () ev.preventDefault ()
handleEdit (tag.id) handleEdit (tag.id)
+7 -3
ファイルの表示
@@ -3,6 +3,7 @@ import { Helmet } from 'react-helmet-async'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import PrefetchLink from '@/components/PrefetchLink' import PrefetchLink from '@/components/PrefetchLink'
import PageTitle from '@/components/common/PageTitle'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config' import { SITE_TITLE } from '@/config'
import { apiGet } from '@/lib/api' import { apiGet } from '@/lib/api'
@@ -28,8 +29,11 @@ export default () => {
<Helmet> <Helmet>
<title>{`Wiki 変更履歴 | ${ SITE_TITLE }`}</title> <title>{`Wiki 変更履歴 | ${ SITE_TITLE }`}</title>
</Helmet> </Helmet>
<PageTitle>Wiki </PageTitle>
<table className="table-auto w-full border-collapse"> <table className="table-auto w-full border-collapse">
<thead> <thead className="border-b-2 border-black dark:border-white">
<tr> <tr>
<th></th> <th></th>
<th className="p-2 text-left"></th> <th className="p-2 text-left"></th>
@@ -39,8 +43,8 @@ export default () => {
</thead> </thead>
<tbody> <tbody>
{changes.map (change => ( {changes.map (change => (
<tr key={change.revisionId}> <tr key={change.revisionId} className="even:bg-gray-100 dark:even:bg-gray-700">
<td> <td className="p-2">
{change.pred != null && ( {change.pred != null && (
<PrefetchLink <PrefetchLink
to={`/wiki/${ change.wikiPage.id }/diff?from=${ change.pred }&to=${ change.revisionId }`}> to={`/wiki/${ change.wikiPage.id }/diff?from=${ change.pred }&to=${ change.revisionId }`}>
+6 -5
ファイルの表示
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import PrefetchLink from '@/components/PrefetchLink' import PrefetchLink from '@/components/PrefetchLink'
import SectionTitle from '@/components/common/SectionTitle' import PageTitle from '@/components/common/PageTitle'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import { SITE_TITLE } from '@/config' import { SITE_TITLE } from '@/config'
import { apiGet } from '@/lib/api' import { apiGet } from '@/lib/api'
@@ -35,8 +35,9 @@ export default () => {
<Helmet> <Helmet>
<title>Wiki | {SITE_TITLE}</title> <title>Wiki | {SITE_TITLE}</title>
</Helmet> </Helmet>
<div className="max-w-xl"> <div className="max-w-xl">
<SectionTitle>Wiki</SectionTitle> <PageTitle>Wiki</PageTitle>
<form onSubmit={handleSearch} className="space-y-2"> <form onSubmit={handleSearch} className="space-y-2">
{/* タイトル */} {/* タイトル */}
<div> <div>
@@ -68,7 +69,7 @@ export default () => {
<div className="mt-4"> <div className="mt-4">
<table className="table-auto w-full border-collapse"> <table className="table-auto w-full border-collapse">
<thead> <thead className="border-b-2 border-black dark:border-white">
<tr> <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>
@@ -76,13 +77,13 @@ export default () => {
</thead> </thead>
<tbody> <tbody>
{results.map (page => ( {results.map (page => (
<tr key={page.id}> <tr key={page.id} className="even:bg-gray-100 dark:even:bg-gray-700">
<td className="p-2"> <td className="p-2">
<PrefetchLink to={`/wiki/${ encodeURIComponent (page.title) }`}> <PrefetchLink to={`/wiki/${ encodeURIComponent (page.title) }`}>
{page.title} {page.title}
</PrefetchLink> </PrefetchLink>
</td> </td>
<td className="p-2 text-gray-100 text-sm"> <td className="p-2">
{page.updatedAt} {page.updatedAt}
</td> </td>
</tr>))} </tr>))}