コミットを比較
2
コミット
1f198db16d
...
cc6d50cf17
| 作成者 | SHA1 | 日付 | |
|---|---|---|---|
|
|
cc6d50cf17 | ||
|
|
419f46dfda |
@@ -4,34 +4,39 @@ import { useEffect, useState } from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
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 (() => {
|
||||
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
|
||||
}
|
||||
if (!(body))
|
||||
return
|
||||
|
||||
void (async () => {
|
||||
try
|
||||
@@ -45,25 +50,50 @@ export default ({ title, body }: Props) => {
|
||||
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) }`)
|
||||
})
|
||||
}, [])
|
||||
|
||||
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))
|
||||
}, [pageNames])
|
||||
|
||||
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>)) }}>
|
||||
<ReactMarkdown components={mdComponents}>
|
||||
{realBody || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`}
|
||||
</ReactMarkdown>)
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export default () => {
|
||||
</Tab>
|
||||
{tags.length === 1 && (
|
||||
<Tab name="Wiki">
|
||||
<WikiBody body={wikiPage?.body} title={tags[0]} />
|
||||
<WikiBody title={tags[0]} body={wikiPage?.body} />
|
||||
<div className="my-2">
|
||||
<Link to={`/wiki/${ encodeURIComponent (tags[0]) }`}>
|
||||
Wiki を見る
|
||||
|
||||
@@ -60,6 +60,7 @@ export default () => {
|
||||
}
|
||||
}) ()
|
||||
|
||||
setPosts ([])
|
||||
void (async () => {
|
||||
try
|
||||
{
|
||||
@@ -73,7 +74,7 @@ export default () => {
|
||||
}
|
||||
catch
|
||||
{
|
||||
setPosts ([])
|
||||
;
|
||||
}
|
||||
}) ()
|
||||
|
||||
@@ -123,7 +124,7 @@ export default () => {
|
||||
<div className="prose mx-auto p-4">
|
||||
{wikiPage === undefined
|
||||
? 'Loading...'
|
||||
: <WikiBody body={wikiPage?.body} title={title} />}
|
||||
: <WikiBody title={title} body={wikiPage?.body} />}
|
||||
</div>
|
||||
|
||||
{(!(version) && posts.length > 0) && (
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする