| @@ -1,14 +1,69 @@ | |||||
| import axios from 'axios' | |||||
| import toCamel from 'camelcase-keys' | |||||
| import { useEffect, useState } from 'react' | |||||
| import ReactMarkdown from 'react-markdown' | import ReactMarkdown from 'react-markdown' | ||||
| import { Link } from 'react-router-dom' | import { Link } from 'react-router-dom' | ||||
| import { API_BASE_URL } from '@/config' | |||||
| import type { WikiPage } from '@/types' | |||||
| type Props = { title: string | type Props = { title: string | ||||
| body?: string } | body?: string } | ||||
| export default ({ title, body }: Props) => ( | |||||
| <ReactMarkdown components={{ a: ( | |||||
| ({ href, children }) => (['/', '.'].some (e => href?.startsWith (e)) | |||||
| ? <Link to={href!}>{children}</Link> | |||||
| : <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>)) }}> | |||||
| {body || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`} | |||||
| </ReactMarkdown>) | |||||
| export default ({ title, body }: Props) => { | |||||
| const [pageNames, setPageNames] = useState<string[]> ([]) | |||||
| const [realBody, setRealBody] = useState<string> ('') | |||||
| useEffect (() => { | |||||
| const matchIndices = (target: string, keyword: string) => { | |||||
| const indices: number[] = [] | |||||
| let pos = 0 | |||||
| while (true) | |||||
| { | |||||
| const idx = target.indexOf (keyword, pos) | |||||
| if (idx < 0) | |||||
| break | |||||
| indices.push (idx) | |||||
| pos = idx + keyword.length | |||||
| } | |||||
| return indices | |||||
| } | |||||
| 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 ([]) | |||||
| } | |||||
| }) () | |||||
| const linkIndices: [string, [number, number]][] = [] | |||||
| pageNames.forEach (name => { | |||||
| matchIndices (body ?? '', name).map (idx => [idx, idx + name.length]).forEach (([start, end]) => { | |||||
| if (linkIndices.every (([, [st, ed]]) => (start < st || ed <= start) && (end < st || ed <= end))) | |||||
| linkIndices.push ([name, [start, end]]) | |||||
| }) | |||||
| }) | |||||
| linkIndices.sort (([, [a]], [, [b]]) => b - a) | |||||
| linkIndices.forEach (([name, [start, end]]) => { | |||||
| setRealBody (prev => `${ prev.slice (0, start) }[${ name }](/wiki/${ encodeURIComponent (name) })${ prev.slice (end) }`) | |||||
| }) | |||||
| }, []) | |||||
| return ( | |||||
| <ReactMarkdown components={{ a: ( | |||||
| ({ href, children }) => (['/', '.'].some (e => href?.startsWith (e)) | |||||
| ? <Link to={href!}>{children}</Link> | |||||
| : <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>)) }}> | |||||
| {realBody || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`} | |||||
| </ReactMarkdown>) | |||||
| } | |||||