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