Browse Source

#45 完了

#23
みてるぞ 3 weeks ago
parent
commit
6e6ee73857
7 changed files with 115 additions and 33 deletions
  1. +14
    -17
      backend/app/controllers/wiki_pages_controller.rb
  2. +5
    -0
      backend/app/models/wiki_page.rb
  3. +3
    -1
      frontend/src/App.tsx
  4. +34
    -11
      frontend/src/pages/WikiDetailPage.tsx
  5. +37
    -0
      frontend/src/pages/WikiDiffPage.tsx
  6. +6
    -4
      frontend/src/pages/WikiHistoryPage.tsx
  7. +16
    -0
      frontend/src/types.ts

+ 14
- 17
backend/app/controllers/wiki_pages_controller.rb View File

@@ -1,25 +1,10 @@
class WikiPagesController < ApplicationController class WikiPagesController < ApplicationController
def show def show
wiki_page = WikiPage.find(params[:id])
return head :not_found unless wiki_page

render json: wiki_page.as_json.merge(body: wiki_page.body)
render_wiki_page_or_404 WikiPage.find(params[:id])
end end


def show_by_title def show_by_title
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:)
render_wiki_page_or_404 WikiPage.find_by(title: params[:title])
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

+ 5
- 0
backend/app/models/wiki_page.rb View File

@@ -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')


+ 3
- 1
frontend/src/App.tsx View File

@@ -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>


+ 34
- 11
frontend/src/pages/WikiDetailPage.tsx View File

@@ -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 ('')))
}, [name])
.catch (() => setWikiPage (null)))
}, [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">
&lt; 前
</Link>) : <>&lt; 前</>}

<span>{wikiPage.updated_at}</span>

{wikiPage.succ ? (
<Link to={`/wiki/${ title }?version=${ wikiPage.succ }`}
className="text-blue-400 hover:underline">
後 &gt;
</Link>) : <>後 &gt;</>}
</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>


+ 37
- 0
frontend/src/pages/WikiDiffPage.tsx View File

@@ -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>)
}

+ 6
- 4
frontend/src/pages/WikiHistoryPage.tsx View File

@@ -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' && (
<Link>
差分
</Link>)}</td>
<td>
{change.change_type === 'update' && (
<Link to={`/wiki/${ change.wiki_page.id }/diff?from=${ change.pred }&to=${ change.sha }`}>
差分
</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">


+ 16
- 0
frontend/src/types.ts View File

@@ -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]

Loading…
Cancel
Save