This commit is contained in:
@@ -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 }) => <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) => {
|
||||
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>
|
||||
export default (({ title, body }: Props) =>
|
||||
<WikiMarkdown title={title} body={body ?? ''}/>) satisfies FC<Props>
|
||||
|
||||
@@ -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<string> (initTitle)
|
||||
const [body, setBody] = useState<string> (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<WikiAsset> (
|
||||
`/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}/>
|
||||
</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,
|
||||
{ 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)}/>)}
|
||||
</div>
|
||||
</MainArea>)
|
||||
}) satisfies FC<Props>
|
||||
|
||||
@@ -38,8 +38,6 @@ export default ({ user }: Props) => {
|
||||
{
|
||||
const data = await apiPost<WikiPage> ('/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 }`)
|
||||
|
||||
Reference in New Issue
Block a user