@@ -1,15 +1,16 @@ | |||
class WikiPagesController < ApplicationController | |||
def show | |||
wiki_page = WikiPage.find(params[:id]) | |||
render json: wiki_page.as_json | |||
return head :not_found unless wiki_page | |||
render json: wiki_page.as_json.merge(body: wiki_page.body) | |||
end | |||
def show_by_title | |||
wiki_page = WikiPage.find_by(title: params[:title]) | |||
body = wiki_page&.body | |||
return head :not_found unless body | |||
return head :not_found unless wiki_page | |||
render plain: body | |||
render json: wiki_page.as_json.merge(body: wiki_page.body) | |||
end | |||
def create | |||
@@ -29,7 +30,7 @@ class WikiPagesController < ApplicationController | |||
return head :unauthorized unless current_user | |||
wiki_page = WikiPage.find(params[:id]) | |||
return head :not_found unless wiki_pages | |||
return head :not_found unless wiki_page | |||
wiki_page.updated_user = current_user | |||
wiki_page.set_body params[:body], user: current_user | |||
@@ -21,7 +21,7 @@ class WikiPage < ApplicationRecord | |||
email: 'dummy@example.com' } | |||
if page | |||
page.update(content, commit: commit_info) | |||
wiki.update_page(page, id.to_s, :markdown, content, commit_info) | |||
else | |||
wiki.write_page(id.to_s, :markdown, content, commit_info) | |||
end | |||
@@ -8,6 +8,7 @@ import PostPage from '@/pages/PostPage' | |||
import PostNewPage from '@/pages/PostNewPage' | |||
import PostDetailPage from '@/pages/PostDetailPage' | |||
import WikiNewPage from '@/pages/WikiNewPage' | |||
import WikiEditPage from '@/pages/WikiEditPage' | |||
import WikiDetailPage from '@/pages/WikiDetailPage' | |||
import { API_BASE_URL } from '@/config' | |||
import axios from 'axios' | |||
@@ -62,7 +63,7 @@ export default () => { | |||
<Route path="/tags/:tag" element={<TagPage />} /> | |||
<Route path="/wiki/:name" element={<WikiDetailPage />} /> | |||
<Route path="/wiki/new" element={<WikiNewPage />} /> | |||
{/* <Route path="/wiki/:id/edit" element={<WikiEditPage />} /> */} | |||
<Route path="/wiki/:id/edit" element={<WikiEditPage />} /> | |||
</Routes> | |||
</div> | |||
</div> | |||
@@ -3,6 +3,7 @@ import { Link, useLocation, useParams } from 'react-router-dom' | |||
import SettingsDialogue from './SettingsDialogue' | |||
import { Button } from './ui/button' | |||
import clsx from 'clsx' | |||
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus' | |||
import type { User } from '@/types' | |||
@@ -21,6 +22,7 @@ const TopNav: React.FC = ({ user, setUser }: Props) => { | |||
const [settingsVisible, setSettingsVisible] = useState (false) | |||
const [selectedMenu, setSelectedMenu] = useState<Menu> (Menu.None) | |||
const [wikiId, setWikiId] = useState (WikiIdBus.get ()) | |||
const MyLink = ({ to, title, menu, base }: { to: string | |||
title: string | |||
@@ -33,6 +35,8 @@ const TopNav: React.FC = ({ user, setUser }: Props) => { | |||
{title} | |||
</Link>) | |||
useEffect (() => WikiIdBus.subscribe (setWikiId), []) | |||
useEffect (() => { | |||
if (location.pathname.startsWith ('/posts')) | |||
setSelectedMenu (Menu.Post) | |||
@@ -90,8 +94,8 @@ const TopNav: React.FC = ({ user, setUser }: Props) => { | |||
<> | |||
<Separator /> | |||
<Link to={`/posts?tags=${ location.pathname.split ('/')[2] }`} className={subClass}>投稿</Link> | |||
<Link to={`/wiki/${ location.pathname.split ('/')[2] }/history`} className={subClass}>履歴</Link> | |||
<Link to={`/wiki/${ location.pathname.split ('/')[2] }/edit`} className={subClass}>編輯</Link> | |||
<Link to={`/wiki/${ wikiId || location.pathname.split ('/')[2] }/history`} className={subClass}>履歴</Link> | |||
<Link to={`/wiki/${ wikiId || location.pathname.split ('/')[2] }/edit`} className={subClass}>編輯</Link> | |||
</>} | |||
</div>) | |||
} | |||
@@ -0,0 +1,35 @@ | |||
export class EventBus<T> | |||
{ | |||
private value: T | |||
private subscribers: ((val: T) => void)[] = [] | |||
constructor ( | |||
initialValue: T) | |||
{ | |||
this.value = initialValue | |||
} | |||
get () | |||
: T | |||
{ | |||
return this.value | |||
} | |||
set ( | |||
val: T) | |||
: void | |||
{ | |||
this.value = val | |||
this.subscribers.forEach (f => f (val)) | |||
} | |||
subscribe ( | |||
func: (val: T) => void) | |||
: () => void | |||
{ | |||
this.subscribers.push (func) | |||
return () => { | |||
this.subscribers = this.subscribers.filter (sub => sub !== func) | |||
} | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
import { EventBus } from './EventBus' | |||
export const WikiIdBus = new EventBus<number | null> (null) |
@@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown' | |||
import axios from 'axios' | |||
import { API_BASE_URL } from '@/config' | |||
import MainArea from '@/components/layout/MainArea' | |||
import { WikiIdBus } from '@/lib/eventBus/WikiIdBus' | |||
export default () => { | |||
@@ -22,7 +23,10 @@ export default () => { | |||
} | |||
void (axios.get (`${ API_BASE_URL }/wiki/title/${ encodeURIComponent (name) }`) | |||
.then (res => setMarkdown (res.data)) | |||
.then (res => { | |||
setMarkdown (res.data.body) | |||
WikiIdBus.set (res.data.id) | |||
}) | |||
.catch (() => setMarkdown (null))) | |||
}, [name]) | |||
@@ -30,7 +34,7 @@ export default () => { | |||
<MainArea> | |||
<div className="prose mx-auto p-4"> | |||
<ReactMarkdown components={{ a: ( | |||
({ href, children }) => (href?.startsWith ('/') | |||
({ href, children }) => (['/', '.'].some (e => href?.startsWith (e)) | |||
? <Link to={href!}>{children}</Link> | |||
: <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>)) }}> | |||
{markdown || `このページは存在しません。[新規作成してください](/wiki/new?title=${ name })。`} | |||
@@ -0,0 +1,85 @@ | |||
import React, { useEffect, useState, useRef } from 'react' | |||
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom' | |||
import axios from 'axios' | |||
import { API_BASE_URL, SITE_TITLE } from '@/config' | |||
import NicoViewer from '@/components/NicoViewer' | |||
import { Button } from '@/components/ui/button' | |||
import { toast } from '@/components/ui/use-toast' | |||
import { cn } from '@/lib/utils' | |||
import MarkdownIt from 'markdown-it' | |||
import MdEditor from 'react-markdown-editor-lite' | |||
import 'react-markdown-editor-lite/lib/index.css' | |||
import MainArea from '@/components/layout/MainArea' | |||
import type { Tag } from '@/types' | |||
const mdParser = new MarkdownIt | |||
export default () => { | |||
const { id } = useParams () | |||
const location = useLocation () | |||
const navigate = useNavigate () | |||
const [title, setTitle] = useState ('') | |||
const [body, setBody] = useState ('') | |||
const handleSubmit = () => { | |||
const formData = new FormData () | |||
formData.append ('title', title) | |||
formData.append ('body', body) | |||
void (axios.put (`${ API_BASE_URL }/wiki/${ id }`, formData, { headers: { | |||
'Content-Type': 'multipart/form-data', | |||
'X-Transfer-Code': localStorage.getItem ('user_code') || '' } }) | |||
.then (res => { | |||
toast ({ title: '投稿成功!' }) | |||
navigate (`/wiki/${ title }`) | |||
}) | |||
.catch (e => toast ({ title: '投稿失敗', | |||
description: '入力を確認してください。' }))) | |||
} | |||
useEffect (() => { | |||
void (axios.get (`${ API_BASE_URL }/wiki/${ id }`) | |||
.then (res => { | |||
setTitle (res.data.title) | |||
setBody (res.data.body) | |||
})) | |||
document.title = `Wiki ページを編輯 | ${ SITE_TITLE }` | |||
}, [id]) | |||
return ( | |||
<MainArea> | |||
<div className="max-w-xl mx-auto p-4 space-y-4"> | |||
<h1 className="text-2xl font-bold mb-2">Wiki ページを編輯</h1> | |||
{/* タイトル */} | |||
{/* TODO: タグ補完 */} | |||
<div> | |||
<label className="block font-semibold mb-1">タイトル</label> | |||
<input type="text" | |||
value={title} | |||
onChange={e => setTitle (e.target.value)} | |||
className="w-full border p-2 rounded" /> | |||
</div> | |||
{/* 本文 */} | |||
<div> | |||
<label className="block font-semibold mb-1">本文</label> | |||
<MdEditor value={body} | |||
style={{ height: '500px' }} | |||
renderHTML={text => mdParser.render (text)} | |||
onChange={({ text }) => setBody (text)} /> | |||
</div> | |||
{/* 送信 */} | |||
<button onClick={handleSubmit} | |||
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400"> | |||
追加 | |||
</button> | |||
</div> | |||
</MainArea>) | |||
} |