ファイル
btrc-hub/frontend/src/mdx-components.tsx
T
2026-10-18 13:56:03 +09:00

120 行
3.4 KiB
TypeScript

import { createContext, createElement, useContext, useMemo } from 'react'
import { useLocation } from 'react-router-dom'
import PrefetchLink from '@/components/PrefetchLink'
import { localiseUIChildren } from '@/components/LocalisedText'
import { cn } from '@/lib/utils'
import type { MDXComponents } from 'mdx/types'
import type { ComponentProps, HTMLAttributes, ReactNode } from 'react'
const DocumentLanguageContext = createContext ('ja')
export const LocalisedDocument = (
{ language, children, className }: {
language: string
children: ReactNode
className?: string },
) => (
<DocumentLanguageContext.Provider value={language}>
<div lang={language === 'ain' ? 'ain-Kana' : language}
className={cn ('localised-document', className)}>
{children}
</div>
</DocumentLanguageContext.Provider>)
const MDXLink = (
{ href, children, ...props }: ComponentProps<'a'>,
) => {
const location = useLocation ()
const url = useMemo (() => {
try
{
const base = new URL (location.pathname + location.search, window.location.origin)
return href ? new URL (href, base) : null
}
catch
{
return null
}
}, [href, location.pathname, location.search])
const internal = url != null
&& url.origin === window.location.origin
&& (url.protocol === 'http:' || url.protocol === 'https:')
const anchor = href?.startsWith ('#')
|| (url?.pathname === location.pathname
&& url.search === location.search && Boolean (url.hash))
const external = url != null && !(internal)
&& (url.protocol === 'http:' || url.protocol === 'https:')
if (internal && !(anchor) && props.download == null)
{
return (
<PrefetchLink to={url.pathname + url.search + url.hash} {...props}>
{children}
</PrefetchLink>)
}
return (
<a href={href}
{...props}
target={external ? props.target ?? '_blank' : props.target}
rel={props.rel ?? 'noopener noreferrer'}>
{children}
</a>)
}
export const useMDXComponents = (): MDXComponents => {
const language = useContext (DocumentLanguageContext)
return {
a: MDXLink,
...((language === 'ain' || language === 'ain-Kana') ? ainuMDXComponents : { }) }
}
const ainuElement = (element: string) => {
const AinuElement = (
{ children, className, ...props }: HTMLAttributes<HTMLElement> & {
node?: unknown },
) => {
const documentLanguage = useContext (DocumentLanguageContext)
const language = props.lang ?? documentLanguage
// ReactMarkdown supplies its syntax node; it is not a DOM attribute.
delete props.node
return (
<DocumentLanguageContext.Provider value={language}>
{createElement (element,
{ ...props,
lang: language === 'ain' ? 'ain-Kana' : language,
className: cn (
(language === 'ain' || language === 'ain-Kana') && 'ainu-ui-text',
className) },
localiseUIChildren (children, language))}
</DocumentLanguageContext.Provider>)
}
return AinuElement
}
// Opt in using the document's language, independently of the URL locale.
export const ainuMDXComponents = {
p: ainuElement ('p'),
h1: ainuElement ('h1'),
h2: ainuElement ('h2'),
h3: ainuElement ('h3'),
h4: ainuElement ('h4'),
h5: ainuElement ('h5'),
h6: ainuElement ('h6'),
li: ainuElement ('li'),
dt: ainuElement ('dt'),
dd: ainuElement ('dd'),
th: ainuElement ('th'),
td: ainuElement ('td'),
em: ainuElement ('em'),
strong: ainuElement ('strong'),
del: ainuElement ('del') }