feat: Wiki 自動リンクをテキスト領域のみに制限(#93) (#218)

Merge branch 'main' into #93

#93

Merge remote-tracking branch 'origin/main' into #93

#93

#93

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #218
This commit was merged in pull request #218.
This commit is contained in:
2026-01-13 19:51:21 +09:00
parent f2b774ab1d
commit 74141f2a84
6 changed files with 148 additions and 79 deletions
+8 -52
View File
@@ -1,6 +1,6 @@
import axios from 'axios'
import toCamel from 'camelcase-keys'
import { useEffect, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import { Link } from 'react-router-dom'
import remarkGFM from 'remark-gfm'
@@ -8,6 +8,7 @@ import remarkGFM from 'remark-gfm'
import SectionTitle from '@/components/common/SectionTitle'
import SubsectionTitle from '@/components/common/SubsectionTitle'
import { API_BASE_URL } from '@/config'
import remarkWikiAutoLink from '@/lib/remark-wiki-autolink'
import type { FC } from 'react'
import type { Components } from 'react-markdown'
@@ -34,17 +35,16 @@ const mdComponents = { h1: ({ children }) => <SectionTitle>{children}</SectionT
export default (({ title, body }: Props) => {
const [pageNames, setPageNames] = useState<string[]> ([])
const [realBody, setRealBody] = useState<string> ('')
const remarkPlugins = useMemo (
() => [() => remarkWikiAutoLink (pageNames), remarkGFM], [pageNames])
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[]
const data: WikiPage[] = toCamel (res.data as any, { deep: true })
setPageNames (data.map (page => page.title).sort ((a, b) => b.length - a.length))
}
catch
@@ -54,52 +54,8 @@ export default (({ title, body }: Props) => {
}) ()
}, [])
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 components={mdComponents} remarkPlugins={remarkPlugins}>
{body || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`}
</ReactMarkdown>)
}) satisfies FC<Props>