#45 完了
This commit is contained in:
@@ -1,25 +1,10 @@
|
|||||||
class WikiPagesController < ApplicationController
|
class WikiPagesController < ApplicationController
|
||||||
def show
|
def show
|
||||||
wiki_page = WikiPage.find(params[:id])
|
render_wiki_page_or_404 WikiPage.find(params[:id])
|
||||||
return head :not_found unless wiki_page
|
|
||||||
|
|
||||||
render json: wiki_page.as_json.merge(body: wiki_page.body)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def show_by_title
|
def show_by_title
|
||||||
title = params[:title]
|
render_wiki_page_or_404 WikiPage.find_by(title: params[:title])
|
||||||
version = params[:version].presence
|
|
||||||
|
|
||||||
wiki_page = WikiPage.find_by(title:)
|
|
||||||
return head :not_found unless wiki_page
|
|
||||||
|
|
||||||
wiki_page.sha = version
|
|
||||||
|
|
||||||
body = wiki_page.body
|
|
||||||
sha = wiki_page.sha
|
|
||||||
pred = wiki_page.pred
|
|
||||||
succ = wiki_page.succ
|
|
||||||
render json: wiki_page.as_json.merge(body:, sha:, pred:, succ:)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def diff
|
def diff
|
||||||
@@ -117,4 +102,16 @@ class WikiPagesController < ApplicationController
|
|||||||
def wiki
|
def wiki
|
||||||
@wiki ||= Gollum::Wiki.new(WIKI_PATH)
|
@wiki ||= Gollum::Wiki.new(WIKI_PATH)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_wiki_page_or_404 wiki_page
|
||||||
|
return head :not_found unless wiki_page
|
||||||
|
|
||||||
|
wiki_page.sha = params[:version].presence
|
||||||
|
|
||||||
|
body = wiki_page.body
|
||||||
|
sha = wiki_page.sha
|
||||||
|
pred = wiki_page.pred
|
||||||
|
succ = wiki_page.succ
|
||||||
|
render json: wiki_page.as_json.merge(body:, sha:, pred:, succ:)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class WikiPage < ApplicationRecord
|
|||||||
idx = vers.find_index { |ver| ver.id == @sha }
|
idx = vers.find_index { |ver| ver.id == @sha }
|
||||||
@pred = vers[idx + 1]&.id
|
@pred = vers[idx + 1]&.id
|
||||||
@succ = idx.positive? ? vers[idx - 1].id : nil
|
@succ = idx.positive? ? vers[idx - 1].id : nil
|
||||||
|
@updated_at = vers[idx].authored_date
|
||||||
@sha
|
@sha
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -35,6 +36,10 @@ class WikiPage < ApplicationRecord
|
|||||||
@succ
|
@succ
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def updated_at
|
||||||
|
@updated_at
|
||||||
|
end
|
||||||
|
|
||||||
def body
|
def body
|
||||||
sha = nil unless @page
|
sha = nil unless @page
|
||||||
@page&.raw_data&.force_encoding('UTF-8')
|
@page&.raw_data&.force_encoding('UTF-8')
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import PostDetailPage from '@/pages/PostDetailPage'
|
|||||||
import WikiPage from '@/pages/WikiPage'
|
import WikiPage from '@/pages/WikiPage'
|
||||||
import WikiNewPage from '@/pages/WikiNewPage'
|
import WikiNewPage from '@/pages/WikiNewPage'
|
||||||
import WikiEditPage from '@/pages/WikiEditPage'
|
import WikiEditPage from '@/pages/WikiEditPage'
|
||||||
|
import WikiDiffPage from '@/pages/WikiDiffPage'
|
||||||
import WikiDetailPage from '@/pages/WikiDetailPage'
|
import WikiDetailPage from '@/pages/WikiDetailPage'
|
||||||
import WikiHistoryPage from '@/pages/WikiHistoryPage'
|
import WikiHistoryPage from '@/pages/WikiHistoryPage'
|
||||||
import { API_BASE_URL } from '@/config'
|
import { API_BASE_URL } from '@/config'
|
||||||
@@ -64,9 +65,10 @@ export default () => {
|
|||||||
<Route path="/posts/:id" element={<PostDetailPage user={user} />} />
|
<Route path="/posts/:id" element={<PostDetailPage user={user} />} />
|
||||||
<Route path="/tags/:tag" element={<TagPage />} />
|
<Route path="/tags/:tag" element={<TagPage />} />
|
||||||
<Route path="/wiki" element={<WikiPage />} />
|
<Route path="/wiki" element={<WikiPage />} />
|
||||||
<Route path="/wiki/:name" element={<WikiDetailPage />} />
|
<Route path="/wiki/:title" element={<WikiDetailPage />} />
|
||||||
<Route path="/wiki/new" element={<WikiNewPage />} />
|
<Route path="/wiki/new" element={<WikiNewPage />} />
|
||||||
<Route path="/wiki/:id/edit" element={<WikiEditPage />} />
|
<Route path="/wiki/:id/edit" element={<WikiEditPage />} />
|
||||||
|
<Route path="/wiki/:id/diff" element={<WikiDiffPage />} />
|
||||||
<Route path="/wiki/changes" element={<WikiHistoryPage />} />
|
<Route path="/wiki/changes" element={<WikiHistoryPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,45 +1,68 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Link, useParams, useNavigate } from 'react-router-dom'
|
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { API_BASE_URL } from '@/config'
|
import { API_BASE_URL } from '@/config'
|
||||||
import MainArea from '@/components/layout/MainArea'
|
import MainArea from '@/components/layout/MainArea'
|
||||||
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus'
|
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus'
|
||||||
|
|
||||||
|
import type { WikiPage } from '@/types'
|
||||||
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { name } = useParams ()
|
const { title } = useParams ()
|
||||||
|
|
||||||
|
const location = useLocation ()
|
||||||
const navigate = useNavigate ()
|
const navigate = useNavigate ()
|
||||||
|
|
||||||
const [markdown, setMarkdown] = useState<string | null> (null)
|
const [wikiPage, setWikiPage] = useState<WikiPage | null | undefined> (undefined)
|
||||||
|
|
||||||
|
const query = new URLSearchParams (location.search)
|
||||||
|
const version = query.get ('version')
|
||||||
|
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
if (/^\d+$/.test (name))
|
if (/^\d+$/.test (title))
|
||||||
{
|
{
|
||||||
void (axios.get (`${ API_BASE_URL }/wiki/${ name }`)
|
void (axios.get (`${ API_BASE_URL }/wiki/${ title }`)
|
||||||
.then (res => navigate (`/wiki/${ res.data.title }`, { replace: true })))
|
.then (res => navigate (`/wiki/${ res.data.title }`, { replace: true })))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
void (axios.get (`${ API_BASE_URL }/wiki/title/${ encodeURIComponent (name) }`)
|
void (axios.get (`${ API_BASE_URL }/wiki/title/${ encodeURIComponent (title) }`, version && { params: { version } })
|
||||||
.then (res => {
|
.then (res => {
|
||||||
setMarkdown (res.data.body)
|
setWikiPage (res.data)
|
||||||
WikiIdBus.set (res.data.id)
|
WikiIdBus.set (res.data.id)
|
||||||
})
|
})
|
||||||
.catch (() => setMarkdown ('')))
|
.catch (() => setWikiPage (null)))
|
||||||
}, [name])
|
}, [title, location.search])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainArea>
|
<MainArea>
|
||||||
|
{(wikiPage && version) && (
|
||||||
|
<div className="text-sm flex gap-3 items-center justify-center border border-gray-700 rounded px-2 py-1 mb-4">
|
||||||
|
{wikiPage.pred ? (
|
||||||
|
<Link to={`/wiki/${ title }?version=${ wikiPage.pred }`}
|
||||||
|
className="text-blue-400 hover:underline">
|
||||||
|
< 前
|
||||||
|
</Link>) : <>< 前</>}
|
||||||
|
|
||||||
|
<span>{wikiPage.updated_at}</span>
|
||||||
|
|
||||||
|
{wikiPage.succ ? (
|
||||||
|
<Link to={`/wiki/${ title }?version=${ wikiPage.succ }`}
|
||||||
|
className="text-blue-400 hover:underline">
|
||||||
|
後 >
|
||||||
|
</Link>) : <>後 ></>}
|
||||||
|
</div>)}
|
||||||
|
<h1>{title}</h1>
|
||||||
<div className="prose mx-auto p-4">
|
<div className="prose mx-auto p-4">
|
||||||
{markdown == null ? 'Loading...' : (
|
{wikiPage === undefined ? 'Loading...' : (
|
||||||
<>
|
<>
|
||||||
<ReactMarkdown components={{ a: (
|
<ReactMarkdown components={{ a: (
|
||||||
({ href, children }) => (['/', '.'].some (e => href?.startsWith (e))
|
({ href, children }) => (['/', '.'].some (e => href?.startsWith (e))
|
||||||
? <Link to={href!}>{children}</Link>
|
? <Link to={href!}>{children}</Link>
|
||||||
: <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>)) }}>
|
: <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>)) }}>
|
||||||
{markdown || `このページは存在しません。[新規作成してください](/wiki/new?title=${ name })。`}
|
{wikiPage?.body || `このページは存在しません。[新規作成してください](/wiki/new?title=${ title })。`}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</>)}
|
</>)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Link, useLocation, useParams } from 'react-router-dom'
|
||||||
|
import axios from 'axios'
|
||||||
|
import MainArea from '@/components/layout/MainArea'
|
||||||
|
import { API_BASE_URL } from '@/config'
|
||||||
|
|
||||||
|
import type { WikiPageDiff } from '@/types'
|
||||||
|
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const { id } = useParams ()
|
||||||
|
|
||||||
|
const location = useLocation ()
|
||||||
|
|
||||||
|
const [diff, setDiff] = useState<WikiPageDiff | null> (null)
|
||||||
|
|
||||||
|
const query = new URLSearchParams (location.search)
|
||||||
|
const from = query.get ('from')
|
||||||
|
const to = query.get ('to')
|
||||||
|
|
||||||
|
useEffect (() => {
|
||||||
|
void (axios.get (`${ API_BASE_URL }/wiki/${ id }/diff`, { params: { from, to } })
|
||||||
|
.then (res => setDiff (res.data)))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MainArea>
|
||||||
|
<h1>{diff?.title}</h1>
|
||||||
|
<div className="prose mx-auto p-4">
|
||||||
|
{diff ? (
|
||||||
|
diff.diff.map (d => (
|
||||||
|
<span className={d.type === 'added' ? 'bg-green-800' : d.type === 'removed' ? 'bg-red-800' : ''}>
|
||||||
|
{d.content == '\n' ? <br /> : d.content}
|
||||||
|
</span>))) : 'Loading...'}
|
||||||
|
</div>
|
||||||
|
</MainArea>)
|
||||||
|
}
|
||||||
@@ -33,10 +33,12 @@ export default () => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{changes.map (change => (
|
{changes.map (change => (
|
||||||
<tr key={change.sha}>
|
<tr key={change.sha}>
|
||||||
<td>{change.change_type === 'update' && (
|
<td>
|
||||||
<Link>
|
{change.change_type === 'update' && (
|
||||||
|
<Link to={`/wiki/${ change.wiki_page.id }/diff?from=${ change.pred }&to=${ change.sha }`}>
|
||||||
差分
|
差分
|
||||||
</Link>)}</td>
|
</Link>)}
|
||||||
|
</td>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
<Link to={`/wiki/${ encodeURIComponent (change.wiki_page.title) }?version=${ change.sha }`}
|
<Link to={`/wiki/${ encodeURIComponent (change.wiki_page.title) }?version=${ change.sha }`}
|
||||||
className="text-blue-400 hover:underline">
|
className="text-blue-400 hover:underline">
|
||||||
|
|||||||
@@ -25,13 +25,29 @@ export type User = {
|
|||||||
export type WikiPage = {
|
export type WikiPage = {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
|
sha: string
|
||||||
|
pred?: string
|
||||||
|
succ?: string
|
||||||
updated_at?: string }
|
updated_at?: string }
|
||||||
|
|
||||||
export type WikiPageChange = {
|
export type WikiPageChange = {
|
||||||
sha: string
|
sha: string
|
||||||
|
pred?: string
|
||||||
|
succ?: string
|
||||||
wiki_page: WikiPage
|
wiki_page: WikiPage
|
||||||
user: User
|
user: User
|
||||||
change_type: string
|
change_type: string
|
||||||
timestamp: string }
|
timestamp: string }
|
||||||
|
|
||||||
|
export type WikiPageDiff = {
|
||||||
|
wiki_page_id: number
|
||||||
|
title: string
|
||||||
|
older_sha: string
|
||||||
|
newer_sha: string
|
||||||
|
diff: WikiPageDiffDiff[] }
|
||||||
|
|
||||||
|
export type WikiPageDiffDiff = {
|
||||||
|
type: 'context' | 'added' | 'removed'
|
||||||
|
content: string }
|
||||||
|
|
||||||
export type UserRole = typeof USER_ROLES[number]
|
export type UserRole = typeof USER_ROLES[number]
|
||||||
|
|||||||
Reference in New Issue
Block a user