|
- import axios from 'axios'
- import toCamel from 'camelcase-keys'
- import { useEffect, useState } from 'react'
- import ReactMarkdown from 'react-markdown'
- import { Link } from 'react-router-dom'
- import remarkGFM from 'remark-gfm'
-
- import SectionTitle from '@/components/common/SectionTitle'
- import SubsectionTitle from '@/components/common/SubsectionTitle'
- import { API_BASE_URL } from '@/config'
-
- import type { Components } from 'react-markdown'
-
- import type { WikiPage } from '@/types'
-
- 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))
- ? <Link to={href!}>{children}</Link>
- : (
- <a href={href}
- target="_blank"
- rel="noopener noreferrer">
- {children}
- </a>))) } as const satisfies Components
-
-
- export default ({ title, body }: Props) => {
- const [pageNames, setPageNames] = useState<string[]> ([])
- const [realBody, setRealBody] = useState<string> ('')
-
- useEffect (() => {
- if (!(body))
- return
-
- void (async () => {
- try
- {
- const res = await axios.get (`${ API_BASE_URL }/wiki`)
- const data = toCamel (res.data as any, { deep: true }) as WikiPage[]
- setPageNames (data.map (page => page.title).sort ((a, b) => b.length - a.length))
- }
- catch
- {
- setPageNames ([])
- }
- }) ()
- }, [])
-
- useEffect (() => {
- setRealBody ('')
- }, [body])
-
- useEffect (() => {
- if (!(body))
- return
-
- const matchIndices = (target: string, keyword: string) => {
- const indices: number[] = []
- let pos = 0
- let idx
- while ((idx = target.indexOf (keyword, pos)) >= 0)
- {
- indices.push (idx)
- pos = idx + keyword.length
- }
-
- return indices
- }
-
- const linkIndices = (text: string, names: string[]): [string, [number, number]][] => {
- const result: [string, [number, number]][] = []
-
- names.forEach (name => {
- matchIndices (text, name).forEach (idx => {
- const start = idx
- const end = idx + name.length
- const overlaps = result.some (([, [st, ed]]) => start < ed && end > st)
- if (!(overlaps))
- result.push ([name, [start, end]])
- })
- })
-
- return result.sort (([, [a]], [, [b]]) => b - a)
- }
-
- setRealBody (
- linkIndices (body, pageNames).reduce ((acc, [name, [start, end]]) => (
- acc.slice (0, start)
- + `[${ name }](/wiki/${ encodeURIComponent (name) })`
- + acc.slice (end)), body))
- }, [body, pageNames])
-
- return (
- <ReactMarkdown components={mdComponents} remarkPlugins={[remarkGFM]}>
- {realBody || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`}
- </ReactMarkdown>)
- }
|