120 行
3.4 KiB
TypeScript
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') }
|