@@ -1,16 +1,24 @@ | |||||
class WikiPagesController < ApplicationController | class WikiPagesController < ApplicationController | ||||
def index | |||||
end | |||||
def show | def show | ||||
end | |||||
def create | |||||
p params | |||||
wiki_page = WikiPage.find_by(title: params[:title]) | |||||
if wiki_page | |||||
render plain: wiki_page.markdown | |||||
else | |||||
head :not_found | |||||
end | |||||
end | end | ||||
def update | def update | ||||
end | |||||
return head :unauthorized unless current_user | |||||
def destroy | |||||
title = params[:title] | |||||
wiki_page = WikiPage.find_by(title: title) | |||||
unless wiki_page | |||||
wiki_page = WikiPage.new(title: title, created_user: current_user, updated_user: current_user) | |||||
end | |||||
wiki_page.markdown = params[:markdown] | |||||
wiki_page.save! | |||||
head :ok | |||||
end | end | ||||
end | end |
@@ -1,31 +0,0 @@ | |||||
require 'net/http' | |||||
require 'uri' | |||||
class WikiProxyController < ApplicationController | |||||
def edit | |||||
tag = params[:tag] | |||||
uri = "http://localhost:4567/gollum/edit/#{ URI.encode_www_form_component(tag) }" | |||||
begin | |||||
res = fetch_with_redirect(uri) | |||||
render html: res.body.html_safe, content_type: res.content_type | |||||
rescue => e | |||||
render plain: "Wiki システムとの通信に失敗しました:#{ e.message }", status: 502 | |||||
end | |||||
end | |||||
private | |||||
def fetch_with_redirect uri_str, limit = 5 | |||||
raise 'Too many redirects' if limit == 0 | |||||
uri = URI.parse(uri_str) | |||||
res = Net::HTTP.get_response(uri) | |||||
if res.is_a? Net::HTTPRedirection | |||||
location = res['location'] | |||||
fetch_with_redirect(location, limit - 1) | |||||
else | |||||
res | |||||
end | |||||
end | |||||
end |
@@ -1,11 +1,34 @@ | |||||
require 'gollum-lib' | |||||
class WikiPage < ApplicationRecord | class WikiPage < ApplicationRecord | ||||
WIKI_PATH = Rails.root.join('wiki').to_s | |||||
belongs_to :tag, optional: true | belongs_to :tag, optional: true | ||||
belongs_to :created_user, class_name: 'User', foreign_key: 'created_user_id' | belongs_to :created_user, class_name: 'User', foreign_key: 'created_user_id' | ||||
belongs_to :updated_user, class_name: 'User', foreign_key: 'updated_user_id' | belongs_to :updated_user, class_name: 'User', foreign_key: 'updated_user_id' | ||||
validates :title, presence: true, length: { maximum: 255 }, uniqueness: true | validates :title, presence: true, length: { maximum: 255 }, uniqueness: true | ||||
def gollum_path | |||||
"wiki/#{ title.parameterize }.md" | |||||
def markdown | |||||
wiki = Gollum::Wiki.new(WIKI_PATH) | |||||
page = wiki.page(title) | |||||
page&.raw_data | |||||
end | |||||
def markdown= content | |||||
wiki = Gollum::Wiki.new(WIKI_PATH) | |||||
page = wiki.page(title) | |||||
commit_info = { message: "Update #{ title }", | |||||
name: current_user.id, | |||||
email: 'dummy@example.com' } | |||||
if page | |||||
page.update(content, commit: commit_info) | |||||
else | |||||
wiki.write_page(title, :markdown, content, commit_info) | |||||
end | |||||
end | end | ||||
end | end |
@@ -1,62 +1,4 @@ | |||||
Rails.application.routes.draw do | Rails.application.routes.draw do | ||||
get "wiki_pages/index" | |||||
get "wiki_pages/show" | |||||
get "wiki_pages/create" | |||||
get "wiki_pages/update" | |||||
get "wiki_pages/destroy" | |||||
get "users/index" | |||||
get "users/show" | |||||
get "users/create" | |||||
get "users/update" | |||||
get "users/destroy" | |||||
get "user_post_views/index" | |||||
get "user_post_views/show" | |||||
get "user_post_views/create" | |||||
get "user_post_views/update" | |||||
get "user_post_views/destroy" | |||||
get "user_ips/index" | |||||
get "user_ips/show" | |||||
get "user_ips/create" | |||||
get "user_ips/update" | |||||
get "user_ips/destroy" | |||||
get "tags/index" | |||||
get "tags/show" | |||||
get "tags/create" | |||||
get "tags/update" | |||||
get "tags/destroy" | |||||
get 'tags/autocomplete', to: 'tags#autocomplete' | |||||
get "tag_aliases/index" | |||||
get "tag_aliases/show" | |||||
get "tag_aliases/create" | |||||
get "tag_aliases/update" | |||||
get "tag_aliases/destroy" | |||||
get "settings/index" | |||||
get "settings/show" | |||||
get "settings/create" | |||||
get "settings/update" | |||||
get "settings/destroy" | |||||
get "post_tags/index" | |||||
get "post_tags/show" | |||||
get "post_tags/create" | |||||
get "post_tags/update" | |||||
get "post_tags/destroy" | |||||
post 'posts/:id/viewed', to: 'posts#viewed' | |||||
delete 'posts/:id/viewed', to: 'posts#unviewed' | |||||
get "nico_tag_relation/index" | |||||
get "nico_tag_relation/show" | |||||
get "nico_tag_relation/create" | |||||
get "nico_tag_relation/update" | |||||
get "nico_tag_relation/destroy" | |||||
get "ip_addresses/index" | |||||
get "ip_addresses/show" | |||||
get "ip_addresses/create" | |||||
get "ip_addresses/update" | |||||
get "ip_addresses/destroy" | |||||
get 'preview/title', to: 'preview#title' | |||||
get 'preview/thumbnail', to: 'preview#thumbnail' | |||||
get '/wiki/edit/:tag', to: 'wiki_proxy#edit' | |||||
root 'home#index' | |||||
resources :posts | resources :posts | ||||
resources :ip_addresses | resources :ip_addresses | ||||
resources :nico_tag_relations | resources :nico_tag_relations | ||||
@@ -72,7 +14,14 @@ Rails.application.routes.draw do | |||||
get :me | get :me | ||||
end | end | ||||
end | end | ||||
resources :wiki_pages | |||||
get 'tags/autocomplete', to: 'tags#autocomplete' | |||||
post 'posts/:id/viewed', to: 'posts#viewed' | |||||
delete 'posts/:id/viewed', to: 'posts#unviewed' | |||||
get 'preview/title', to: 'preview#title' | |||||
get 'preview/thumbnail', to: 'preview#thumbnail' | |||||
get 'wiki/*title', to: 'wiki_pages#show', format: false | |||||
post 'wiki/*title', to: 'wiki_pages#save', format: false | |||||
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html | ||||
@@ -21,6 +21,7 @@ | |||||
"marked": "^15.0.12", | "marked": "^15.0.12", | ||||
"react": "^19.1.0", | "react": "^19.1.0", | ||||
"react-dom": "^19.1.0", | "react-dom": "^19.1.0", | ||||
"react-markdown": "^10.1.0", | |||||
"react-markdown-editor-lite": "^1.3.4", | "react-markdown-editor-lite": "^1.3.4", | ||||
"react-router-dom": "^6.30.0", | "react-router-dom": "^6.30.0", | ||||
"tailwind-merge": "^3.3.0" | "tailwind-merge": "^3.3.0" | ||||
@@ -7,7 +7,7 @@ import TagSidebar from './components/TagSidebar' | |||||
import PostPage from './pages/PostPage' | import PostPage from './pages/PostPage' | ||||
import PostNewPage from './pages/PostNewPage' | import PostNewPage from './pages/PostNewPage' | ||||
import PostDetailPage from './pages/PostDetailPage' | import PostDetailPage from './pages/PostDetailPage' | ||||
import WikiEditPage from './pages/WikiEditPage' | |||||
import WikiShowPage from './pages/WikiShowPage' | |||||
import { API_BASE_URL } from './config' | import { API_BASE_URL } from './config' | ||||
import axios from 'axios' | import axios from 'axios' | ||||
import { Toaster } from '@/components/ui/toaster' | import { Toaster } from '@/components/ui/toaster' | ||||
@@ -81,7 +81,7 @@ const App = () => { | |||||
<Route path="/posts/new" element={<PostNewPage />} /> | <Route path="/posts/new" element={<PostNewPage />} /> | ||||
<Route path="/posts/:id" element={<PostDetailPage posts={posts} setPosts={setPosts} />} /> | <Route path="/posts/:id" element={<PostDetailPage posts={posts} setPosts={setPosts} />} /> | ||||
<Route path="/tags/:tag" element={<TagPage />} /> | <Route path="/tags/:tag" element={<TagPage />} /> | ||||
<Route path="/wiki/:name" element={<WikiEditPage />} /> | |||||
<Route path="/wiki/:name" element={<WikiShowPage />} /> | |||||
</Routes> | </Routes> | ||||
</main> | </main> | ||||
</div> | </div> | ||||
@@ -0,0 +1,19 @@ | |||||
import MdEditor from 'react-markdown-editor-lite' | |||||
import 'react-markdown-editor-lite/lib/index.css' | |||||
import { marked } from 'marked' | |||||
type Props = { value: string | |||||
onChange: (text: string) => void | |||||
onSave: () => void } | |||||
const WikiEditor = ({ value, onChange, onSave }) => ( | |||||
<div className="wiki-editor"> | |||||
<MdEditor value={value} | |||||
style={{ height: '500px' }} | |||||
renderHTML={text => marked (text)} /> | |||||
<button onClick={onSave}>保存</button> | |||||
</div>) | |||||
export default WikiEditor |
@@ -1,40 +0,0 @@ | |||||
import { useParams } from 'react-router-dom' | |||||
import { useEffect, useState } from 'react' | |||||
import axios from 'axios' | |||||
const WikiEditPage = () => { | |||||
const { name } = useParams<{ name: string }> () | |||||
const [html, setHtml] = useState('') | |||||
const [loading, setLoading] = useState(true) | |||||
useEffect(() => { | |||||
const link = document.createElement('link') | |||||
link.rel = 'stylesheet' | |||||
link.href = '/wiki/custom.css' // Gollum の静的 CSS にアクセスできるようにしとく | |||||
document.head.appendChild(link) | |||||
return () => { | |||||
document.head.removeChild(link) | |||||
} | |||||
}, []) | |||||
useEffect(() => { | |||||
setLoading(true) | |||||
axios | |||||
.get(`/api/wiki/edit/${encodeURIComponent(name)}`, { | |||||
headers: { 'X-Transfer-Code': localStorage.getItem('user_code') || '' } | |||||
}) | |||||
.then((res) => setHtml(res.data)) | |||||
.finally(() => setLoading(false)) | |||||
}, [name]) | |||||
return loading ? ( | |||||
<p>読み込み中...</p> | |||||
) : ( | |||||
<div dangerouslySetInnerHTML={{ __html: html }} /> | |||||
) | |||||
} | |||||
export default WikiEditPage |
@@ -0,0 +1,22 @@ | |||||
import axios from 'axios' | |||||
import { useState, useEffect } from 'react' | |||||
import WikiEditor from '@/components/WikiEditor' | |||||
import { API_BASE_URL } from '../config' | |||||
const WikiPage = () => { | |||||
const [text, setText] = useState ('') | |||||
useEffect (() => { | |||||
void (axios.get (`${ API_BASE_URL }/wiki/load`, { params: { page: 'xxx' } }) | |||||
.then (res => setText (res.data.markdown))) | |||||
}, []) | |||||
const save = () => { | |||||
void (axios.post ('/api/wiki/save', { page: 'xxx', | |||||
markdown: text }) | |||||
.catch (err => console.error ('保存失敗', err))) | |||||
} | |||||
return <WikiEditor value={text} onChange={setText} onSave={save} /> | |||||
} |
@@ -0,0 +1,26 @@ | |||||
import { useParams } from 'react-router-dom' | |||||
import ReactMarkdown from 'react-markdown' | |||||
import { useEffect, useState } from 'react' | |||||
import axios from 'axios' | |||||
import { API_BASE_URL } from '@/config' | |||||
const WikiShowPage = () => { | |||||
const { name } = useParams () | |||||
const [markdown, setMarkdown] = useState ('') | |||||
useEffect (() => { | |||||
void (axios.get (`${ API_BASE_URL }/wiki/${ encodeURIComponent (name) }`) | |||||
.then (res => setMarkdown (res.data)) | |||||
.catch (() => setMarkdown ('# ページが存在しません'))) | |||||
}, [name]) | |||||
return ( | |||||
<div className="prose mx-auto p-4"> | |||||
<ReactMarkdown>{markdown}</ReactMarkdown> | |||||
</div>) | |||||
} | |||||
export default WikiShowPage |