From ef6219dcb1cce36cebe6f7d80c7f27f1e9bd7453 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Thu, 26 Mar 2026 00:01:29 +0900 Subject: [PATCH] #47 --- .../app/controllers/wiki_pages_controller.rb | 2 +- frontend/src/components/WikiBody.tsx | 32 +------- frontend/src/components/WikiEditForm.tsx | 24 +++++- frontend/src/components/WikiMarkdown.tsx | 77 +++++++++++++++++++ frontend/src/pages/wiki/WikiEditPage.tsx | 4 +- frontend/src/pages/wiki/WikiNewPage.tsx | 2 - 6 files changed, 103 insertions(+), 38 deletions(-) create mode 100644 frontend/src/components/WikiMarkdown.tsx diff --git a/backend/app/controllers/wiki_pages_controller.rb b/backend/app/controllers/wiki_pages_controller.rb index e19557d..9e264fb 100644 --- a/backend/app/controllers/wiki_pages_controller.rb +++ b/backend/app/controllers/wiki_pages_controller.rb @@ -126,7 +126,7 @@ class WikiPagesController < ApplicationController message:, base_revision_id:) - render json: WikiPageRepr.base(page).merge(body:), status: :created + render json: WikiPageRepr.base(page).merge(body:) end def search diff --git a/frontend/src/components/WikiBody.tsx b/frontend/src/components/WikiBody.tsx index 50316b2..86381e5 100644 --- a/frontend/src/components/WikiBody.tsx +++ b/frontend/src/components/WikiBody.tsx @@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown' import remarkGFM from 'remark-gfm' import PrefetchLink from '@/components/PrefetchLink' +import WikiMarkdown from '@/components/WikiMarkdown' import SectionTitle from '@/components/common/SectionTitle' import SubsectionTitle from '@/components/common/SubsectionTitle' import { wikiKeys } from '@/lib/queryKeys' @@ -16,33 +17,6 @@ import type { Components } from 'react-markdown' type Props = { title: string body?: string } -const mdComponents = { h1: ({ children }) => {children}, - h2: ({ children }) => {children}, - ol: ({ children }) =>
    {children}
, - ul: ({ children }) => , - a: (({ href, children }) => ( - ['/', '.'].some (e => href?.startsWith (e)) - ? {children} - : ( - - {children} - ))) } as const satisfies Components - -export default (({ title, body }: Props) => { - const { data } = useQuery ({ - enabled: Boolean (body), - queryKey: wikiKeys.index ({ }), - queryFn: () => fetchWikiPages ({ }) }) - const pageNames = (data ?? []).map (page => page.title).sort ((a, b) => b.length - a.length) - - const remarkPlugins = useMemo ( - () => [() => remarkWikiAutoLink (pageNames), remarkGFM], [pageNames]) - - return ( - - {body || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`} - ) -}) satisfies FC +export default (({ title, body }: Props) => + ) satisfies FC diff --git a/frontend/src/components/WikiEditForm.tsx b/frontend/src/components/WikiEditForm.tsx index 14cd4da..be64469 100644 --- a/frontend/src/components/WikiEditForm.tsx +++ b/frontend/src/components/WikiEditForm.tsx @@ -12,10 +12,12 @@ type Props = { title: string body: string onSubmit: (title: string, body: string) => void - forEdit?: boolean } + id?: number | null } -export default (({ title: initTitle, body: initBody, onSubmit, forEdit }: Props) => { +export default (({ title: initTitle, body: initBody, onSubmit, id }: Props) => { + const forEdit = id != null + const [title, setTitle] = useState (initTitle) const [body, setBody] = useState (initBody) @@ -24,6 +26,21 @@ export default (({ title: initTitle, body: initBody, onSubmit, forEdit }: Props) setBody (initBody) }, [initTitle, initBody]) + const handleImageUpload = async (file: File) => { + if (!(forEdit)) + throw new Error ('画像は Wiki 作成前に追加することができません.') + + const formData = new FormData + formData.append ('file', file) + + const asset = await apiPost ( + `/wiki/${ id }/assets`, + formData, + { headers: { 'Content-Type': 'multipart/form-data' } }) + + return `{{img:${ asset.no }}}` + } + return ( <> {/* タイトル */} @@ -44,7 +61,8 @@ export default (({ title: initTitle, body: initBody, onSubmit, forEdit }: Props) value={body} style={{ height: '500px' }} renderHTML={text => mdParser.render (text)} - onChange={({ text }) => setBody (text)}/> + onChange={({ text }) => setBody (text)} + onImageUpload={handleImageUpload}/> {/* 送信 */} diff --git a/frontend/src/components/WikiMarkdown.tsx b/frontend/src/components/WikiMarkdown.tsx new file mode 100644 index 0000000..edb23e2 --- /dev/null +++ b/frontend/src/components/WikiMarkdown.tsx @@ -0,0 +1,77 @@ +import { useQuery } from '@tanstack/react-query' +import { useMemo } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGFM from 'remark-gfm' + +import PrefetchLink from '@/components/PrefetchLink' +import SectionTitle from '@/components/common/SectionTitle' +import SubsectionTitle from '@/components/common/SubsectionTitle' +import { wikiKeys } from '@/lib/queryKeys' +import remarkWikiAutoLink from '@/lib/remark-wiki-autolink' +import { fetchWikiPages } from '@/lib/wiki' + +import type { FC } from 'react' +import type { Components } from 'react-markdown' + +type Props = { + title?: string + body: string + preview?: boolean } + + +const makeComponents = (preview = false) => ( + { h1: ({ children }) => {children}, + h2: ({ children }) => {children}, + ol: ({ children }) =>
    {children}
, + ul: ({ children }) =>
    {children}
, + a: ({ href, children }) => { + if (!(href)) + return <>{children} + + if (!(preview) && ['/', '.'].some (e => href.startsWith (e))) + return {children} + + const ext = /^(?:https?:)?\/\//.test (href) + + return ( + + {children} + ) + }, + img: (({ src, alt }) => ( + {alt)), + } as const satisfies Components) + + +export default (({ title, body, preview = false }: Props) => { + const { data } = useQuery ({ + queryKey: wikiKeys.index ({ }), + queryFn: () => fetchWikiPages ({ }) }) + + const pageNames = useMemo ( + () => (data ?? []).map ((page) => page.title).sort ((a, b) => b.length - a.length), + [data]) + + const remarkPlugins = useMemo ( + () => [() => remarkWikiAutoLink (pageNames), remarkGFM], + [pageNames]) + + const components = useMemo ( + () => makeComponents (preview), + [preview]) + + return ( + + {body + || (title + ? ('このページは存在しません。' + +`[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`) + : '')} + ) +}) satisfies FC diff --git a/frontend/src/pages/wiki/WikiEditPage.tsx b/frontend/src/pages/wiki/WikiEditPage.tsx index cef9c4a..75c9742 100644 --- a/frontend/src/pages/wiki/WikiEditPage.tsx +++ b/frontend/src/pages/wiki/WikiEditPage.tsx @@ -43,8 +43,6 @@ export default (({ user }: Props) => { { await apiPut (`/wiki/${ id }`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }) - qc.setQueryData (wikiKeys.show (title, { }), - (prev: WikiPage) => ({ ...prev, title, body })) qc.invalidateQueries ({ queryKey: wikiKeys.root }) toast ({ title: '投稿成功!' }) navigate (`/wiki/${ title }`) @@ -78,7 +76,7 @@ export default (({ user }: Props) => { title={title} body={body} onSubmit={handleSubmit} - forEdit/>)} + id={Number (id)}/>)} ) }) satisfies FC diff --git a/frontend/src/pages/wiki/WikiNewPage.tsx b/frontend/src/pages/wiki/WikiNewPage.tsx index 307b1a1..38355ca 100644 --- a/frontend/src/pages/wiki/WikiNewPage.tsx +++ b/frontend/src/pages/wiki/WikiNewPage.tsx @@ -38,8 +38,6 @@ export default ({ user }: Props) => { { const data = await apiPost ('/wiki', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) - qc.setQueryData (wikiKeys.show (data.title, { }), - (prev: WikiPage) => ({ ...prev, title: data.title, body: data.body })) qc.invalidateQueries ({ queryKey: wikiKeys.root }) toast ({ title: '投稿成功!' }) navigate (`/wiki/${ data.title }`)