This commit is contained in:
@@ -126,7 +126,7 @@ class WikiPagesController < ApplicationController
|
|||||||
message:,
|
message:,
|
||||||
base_revision_id:)
|
base_revision_id:)
|
||||||
|
|
||||||
render json: WikiPageRepr.base(page).merge(body:), status: :created
|
render json: WikiPageRepr.base(page).merge(body:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def search
|
def search
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown'
|
|||||||
import remarkGFM from 'remark-gfm'
|
import remarkGFM from 'remark-gfm'
|
||||||
|
|
||||||
import PrefetchLink from '@/components/PrefetchLink'
|
import PrefetchLink from '@/components/PrefetchLink'
|
||||||
|
import WikiMarkdown from '@/components/WikiMarkdown'
|
||||||
import SectionTitle from '@/components/common/SectionTitle'
|
import SectionTitle from '@/components/common/SectionTitle'
|
||||||
import SubsectionTitle from '@/components/common/SubsectionTitle'
|
import SubsectionTitle from '@/components/common/SubsectionTitle'
|
||||||
import { wikiKeys } from '@/lib/queryKeys'
|
import { wikiKeys } from '@/lib/queryKeys'
|
||||||
@@ -16,33 +17,6 @@ import type { Components } from 'react-markdown'
|
|||||||
type Props = { title: string
|
type Props = { title: string
|
||||||
body?: string }
|
body?: string }
|
||||||
|
|
||||||
const mdComponents = { h1: ({ children }) => <SectionTitle>{children}</SectionTitle>,
|
|
||||||
h2: ({ children }) => <SubsectionTitle>{children}</SubsectionTitle>,
|
|
||||||
ol: ({ children }) => <ol className="list-decimal pl-6">{children}</ol>,
|
|
||||||
ul: ({ children }) => <ul className="list-disc pl-6">{children}</ul>,
|
|
||||||
a: (({ href, children }) => (
|
|
||||||
['/', '.'].some (e => href?.startsWith (e))
|
|
||||||
? <PrefetchLink to={href!}>{children}</PrefetchLink>
|
|
||||||
: (
|
|
||||||
<a href={href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer">
|
|
||||||
{children}
|
|
||||||
</a>))) } as const satisfies Components
|
|
||||||
|
|
||||||
|
export default (({ title, body }: Props) =>
|
||||||
export default (({ title, body }: Props) => {
|
<WikiMarkdown title={title} body={body ?? ''}/>) satisfies FC<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 (
|
|
||||||
<ReactMarkdown components={mdComponents} remarkPlugins={remarkPlugins}>
|
|
||||||
{body || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`}
|
|
||||||
</ReactMarkdown>)
|
|
||||||
}) satisfies FC<Props>
|
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ type Props = {
|
|||||||
title: string
|
title: string
|
||||||
body: string
|
body: string
|
||||||
onSubmit: (title: string, body: string) => void
|
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<string> (initTitle)
|
const [title, setTitle] = useState<string> (initTitle)
|
||||||
const [body, setBody] = useState<string> (initBody)
|
const [body, setBody] = useState<string> (initBody)
|
||||||
|
|
||||||
@@ -24,6 +26,21 @@ export default (({ title: initTitle, body: initBody, onSubmit, forEdit }: Props)
|
|||||||
setBody (initBody)
|
setBody (initBody)
|
||||||
}, [initTitle, 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<WikiAsset> (
|
||||||
|
`/wiki/${ id }/assets`,
|
||||||
|
formData,
|
||||||
|
{ headers: { 'Content-Type': 'multipart/form-data' } })
|
||||||
|
|
||||||
|
return `{{img:${ asset.no }}}`
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* タイトル */}
|
{/* タイトル */}
|
||||||
@@ -44,7 +61,8 @@ export default (({ title: initTitle, body: initBody, onSubmit, forEdit }: Props)
|
|||||||
value={body}
|
value={body}
|
||||||
style={{ height: '500px' }}
|
style={{ height: '500px' }}
|
||||||
renderHTML={text => mdParser.render (text)}
|
renderHTML={text => mdParser.render (text)}
|
||||||
onChange={({ text }) => setBody (text)}/>
|
onChange={({ text }) => setBody (text)}
|
||||||
|
onImageUpload={handleImageUpload}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 送信 */}
|
{/* 送信 */}
|
||||||
|
|||||||
@@ -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 }) => <SectionTitle>{children}</SectionTitle>,
|
||||||
|
h2: ({ children }) => <SubsectionTitle>{children}</SubsectionTitle>,
|
||||||
|
ol: ({ children }) => <ol className="list-decimal pl-6">{children}</ol>,
|
||||||
|
ul: ({ children }) => <ul className="list-disc pl-6">{children}</ul>,
|
||||||
|
a: ({ href, children }) => {
|
||||||
|
if (!(href))
|
||||||
|
return <>{children}</>
|
||||||
|
|
||||||
|
if (!(preview) && ['/', '.'].some (e => href.startsWith (e)))
|
||||||
|
return <PrefetchLink to={href}>{children}</PrefetchLink>
|
||||||
|
|
||||||
|
const ext = /^(?:https?:)?\/\//.test (href)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a href={href}
|
||||||
|
target={ext ? '_blank' : undefined}
|
||||||
|
rel={ext ? 'noopener noreferrer' : undefined}>
|
||||||
|
{children}
|
||||||
|
</a>)
|
||||||
|
},
|
||||||
|
img: (({ src, alt }) => (
|
||||||
|
<img src={src ?? ''}
|
||||||
|
alt={alt ?? ''}
|
||||||
|
className="max-w-full h-auto rounded"/>)),
|
||||||
|
} 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 (
|
||||||
|
<ReactMarkdown
|
||||||
|
components={components}
|
||||||
|
remarkPlugins={remarkPlugins}>
|
||||||
|
{body
|
||||||
|
|| (title
|
||||||
|
? ('このページは存在しません。'
|
||||||
|
+`[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`)
|
||||||
|
: '')}
|
||||||
|
</ReactMarkdown>)
|
||||||
|
}) satisfies FC<Props>
|
||||||
@@ -43,8 +43,6 @@ export default (({ user }: Props) => {
|
|||||||
{
|
{
|
||||||
await apiPut (`/wiki/${ id }`, formData,
|
await apiPut (`/wiki/${ id }`, formData,
|
||||||
{ headers: { 'Content-Type': 'multipart/form-data' } })
|
{ headers: { 'Content-Type': 'multipart/form-data' } })
|
||||||
qc.setQueryData (wikiKeys.show (title, { }),
|
|
||||||
(prev: WikiPage) => ({ ...prev, title, body }))
|
|
||||||
qc.invalidateQueries ({ queryKey: wikiKeys.root })
|
qc.invalidateQueries ({ queryKey: wikiKeys.root })
|
||||||
toast ({ title: '投稿成功!' })
|
toast ({ title: '投稿成功!' })
|
||||||
navigate (`/wiki/${ title }`)
|
navigate (`/wiki/${ title }`)
|
||||||
@@ -78,7 +76,7 @@ export default (({ user }: Props) => {
|
|||||||
title={title}
|
title={title}
|
||||||
body={body}
|
body={body}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
forEdit/>)}
|
id={Number (id)}/>)}
|
||||||
</div>
|
</div>
|
||||||
</MainArea>)
|
</MainArea>)
|
||||||
}) satisfies FC<Props>
|
}) satisfies FC<Props>
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ export default ({ user }: Props) => {
|
|||||||
{
|
{
|
||||||
const data = await apiPost<WikiPage> ('/wiki', formData,
|
const data = await apiPost<WikiPage> ('/wiki', formData,
|
||||||
{ headers: { 'Content-Type': 'multipart/form-data' } })
|
{ 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 })
|
qc.invalidateQueries ({ queryKey: wikiKeys.root })
|
||||||
toast ({ title: '投稿成功!' })
|
toast ({ title: '投稿成功!' })
|
||||||
navigate (`/wiki/${ data.title }`)
|
navigate (`/wiki/${ data.title }`)
|
||||||
|
|||||||
Reference in New Issue
Block a user