@@ -1,16 +1,24 @@ | |||
class WikiPagesController < ApplicationController | |||
def index | |||
end | |||
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 | |||
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 |
@@ -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 | |||
WIKI_PATH = Rails.root.join('wiki').to_s | |||
belongs_to :tag, optional: true | |||
belongs_to :created_user, class_name: 'User', foreign_key: 'created_user_id' | |||
belongs_to :updated_user, class_name: 'User', foreign_key: 'updated_user_id' | |||
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 |
@@ -1,62 +1,4 @@ | |||
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 :ip_addresses | |||
resources :nico_tag_relations | |||
@@ -72,7 +14,14 @@ Rails.application.routes.draw do | |||
get :me | |||
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 | |||
@@ -21,6 +21,7 @@ | |||
"marked": "^15.0.12", | |||
"react": "^19.1.0", | |||
"react-dom": "^19.1.0", | |||
"react-markdown": "^10.1.0", | |||
"react-markdown-editor-lite": "^1.3.4", | |||
"react-router-dom": "^6.30.0", | |||
"tailwind-merge": "^3.3.0" | |||
@@ -7,7 +7,7 @@ import TagSidebar from './components/TagSidebar' | |||
import PostPage from './pages/PostPage' | |||
import PostNewPage from './pages/PostNewPage' | |||
import PostDetailPage from './pages/PostDetailPage' | |||
import WikiEditPage from './pages/WikiEditPage' | |||
import WikiShowPage from './pages/WikiShowPage' | |||
import { API_BASE_URL } from './config' | |||
import axios from 'axios' | |||
import { Toaster } from '@/components/ui/toaster' | |||
@@ -81,7 +81,7 @@ const App = () => { | |||
<Route path="/posts/new" element={<PostNewPage />} /> | |||
<Route path="/posts/:id" element={<PostDetailPage posts={posts} setPosts={setPosts} />} /> | |||
<Route path="/tags/:tag" element={<TagPage />} /> | |||
<Route path="/wiki/:name" element={<WikiEditPage />} /> | |||
<Route path="/wiki/:name" element={<WikiShowPage />} /> | |||
</Routes> | |||
</main> | |||
</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 |