みてるぞ 1 month ago
parent
commit
2550d31e3b
4 changed files with 76 additions and 7 deletions
  1. +2
    -1
      frontend/package-lock.json
  2. +2
    -1
      frontend/package.json
  3. +6
    -5
      frontend/src/components/WikiBody.tsx
  4. +66
    -0
      frontend/src/lib/remark-wiki-autolink.ts

+ 2
- 1
frontend/package-lock.json View File

@@ -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
frontend/package.json View File

@@ -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",


+ 6
- 5
frontend/src/components/WikiBody.tsx View File

@@ -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>)
}

+ 66
- 0
frontend/src/lib/remark-wiki-autolink.ts View File

@@ -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)
})
}
}

Loading…
Cancel
Save