このコミットが含まれているのは:
生成ファイル
+2
-1
@@ -25,7 +25,8 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-markdown-editor-lite": "^1.3.4",
|
||||
"react-router-dom": "^6.30.0",
|
||||
"tailwind-merge": "^3.3.0"
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"unist-util-visit-parents": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-markdown-editor-lite": "^1.3.4",
|
||||
"react-router-dom": "^6.30.0",
|
||||
"tailwind-merge": "^3.3.0"
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"unist-util-visit-parents": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
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 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 { Components } from 'react-markdown'
|
||||
|
||||
@@ -34,6 +35,8 @@ export default ({ title, body }: Props) => {
|
||||
const [pageNames, setPageNames] = useState<string[]> ([])
|
||||
const [realBody, setRealBody] = useState<string> ('')
|
||||
|
||||
const remarkPlugins = useMemo (() => [remarkWikiAutoLink (pageNames)], [pageNames])
|
||||
|
||||
useEffect (() => {
|
||||
if (!(body))
|
||||
return
|
||||
@@ -42,7 +45,7 @@ export default ({ title, body }: Props) => {
|
||||
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
|
||||
@@ -50,9 +53,7 @@ export default ({ title, body }: Props) => {
|
||||
setPageNames ([])
|
||||
}
|
||||
}) ()
|
||||
}, [])
|
||||
|
||||
useEffect (() => {
|
||||
setRealBody ('')
|
||||
}, [body])
|
||||
|
||||
@@ -97,7 +98,7 @@ export default ({ title, body }: Props) => {
|
||||
}, [body, pageNames])
|
||||
|
||||
return (
|
||||
<ReactMarkdown components={mdComponents}>
|
||||
<ReactMarkdown components={mdComponents} remarkPlugins={remarkPlugins}>
|
||||
{realBody || `このページは存在しません。[新規作成してください](/wiki/new?title=${ encodeURIComponent (title) })。`}
|
||||
</ReactMarkdown>)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { visitParents } from 'unist-util-visit-parents'
|
||||
|
||||
import type { Parent, Root, RootContent, Text } from 'mdast'
|
||||
|
||||
const escapeForRegExp = (s: string) => s.replace (/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
|
||||
export default (pageNames: string[], basePath = '/wiki'): ((tree: Root) => void) => {
|
||||
const names = ([...(new Set(pageNames))]
|
||||
.filter (Boolean)
|
||||
.sort ((a, b) => b.length - a.length))
|
||||
|
||||
if (names.length === 0)
|
||||
{
|
||||
return () => {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
const re = new RegExp (`(${ names.map (escapeForRegExp).join ('|') })`, 'g')
|
||||
|
||||
return (tree: Root) => {
|
||||
visitParents (tree, 'text', (node: Text, ancestors: Parent[]) => {
|
||||
if (ancestors.some (ancestor => ['link',
|
||||
'linkReference',
|
||||
'image',
|
||||
'imageReference',
|
||||
'code',
|
||||
'inlineCode'].includes (ancestor.type)))
|
||||
return
|
||||
|
||||
const value = node.value
|
||||
re.lastIndex = 0
|
||||
let match: RegExpExecArray | null
|
||||
let last = 0
|
||||
const parts: RootContent[] = []
|
||||
|
||||
while (match = re.exec (value))
|
||||
{
|
||||
const start = match.index
|
||||
const end = start + match[0].length
|
||||
if (start > last)
|
||||
parts.push ({ type: 'text', value: value.slice (last, start) })
|
||||
|
||||
const name = match[0]
|
||||
parts.push ({
|
||||
type: 'link',
|
||||
url: `${ basePath }/${ encodeURIComponent (name) }`,
|
||||
title: null,
|
||||
children: [{ type: 'text', value: name }],
|
||||
data: { hProperties: { 'data-wiki': '1' } } })
|
||||
last = end
|
||||
}
|
||||
|
||||
if (parts.length === 0)
|
||||
return
|
||||
|
||||
if (last < value.length)
|
||||
parts.push ({ type: 'text', value: value.slice (last) })
|
||||
|
||||
const parent: Parent = ancestors[ancestors.length - 1]
|
||||
const idx = parent.children.indexOf (node)
|
||||
parent.children.splice (idx, 1, ...parts)
|
||||
})
|
||||
}
|
||||
}
|
||||
新しい課題から参照
ユーザをブロックする