このコミットが含まれているのは:
2026-10-18 13:56:03 +09:00
コミット bbee83e35b
21個のファイルの変更、468行の追加、236行の削除
+41 -5
ファイルの表示
@@ -11,6 +11,29 @@ export const formatUIString = (text: string, language: string): string =>
: text
const splitAinuText = (text: string): string[] => {
const parts: string[] = []
let part = ''
let boundary = false
for (const character of text)
{
// Keep punctuation runs and closing brackets/quotes with the preceding word.
if (boundary && !/[\u3000、,,。.\p{Pe}\p{Pf}]/u.test (character))
{
parts.push (part)
part = ''
boundary = false
}
part += character
if (/[\u3000、,,。.]/u.test (character))
boundary = true
}
if (part !== '')
parts.push (part)
return parts
}
export const localiseUIChildren = (children: ReactNode, language: string): ReactNode => {
if (language !== 'ain' && language !== 'ain-Kana')
return children
@@ -18,15 +41,28 @@ export const localiseUIChildren = (children: ReactNode, language: string): React
return Children.map (children, child => {
if (typeof child === 'string')
{
return formatUIString (child, language).split (/(\u3000+)/).map ((part, index) =>
part.includes ('\u3000')
? <Fragment key={index}>{part}<wbr/></Fragment>
: <span key={index} className="ainu-ui-word">{part}</span>)
const parts = splitAinuText (formatUIString (child, language))
return parts.map ((part, index) => {
const spaces = part.match (/\u3000+$/u)?.[0] ?? ''
const word = part.slice (0, part.length - spaces.length)
return (
<Fragment key={index}>
{word !== '' && <span className="ainu-ui-word">{word}</span>}
{spaces}
{index < parts.length - 1 && <wbr/>}
</Fragment>)
})
}
if (isValidElement<{ children?: ReactNode; lang?: string }> (child))
{
if (child.type === 'code' || child.type === 'pre'
// Only traverse inline formatting owned by the renderer. Components,
// links and code retain their own children and behaviour.
const inline = child.type === Fragment
|| (typeof child.type === 'string'
&& ['span', 'em', 'strong', 'del', 's', 'b', 'i', 'u',
'small', 'mark', 'sup', 'sub'].includes (child.type))
if (!inline
|| (child.props.lang != null && !(child.props.lang.startsWith ('ain'))))
return child
return cloneElement (child, { }, localiseUIChildren (child.props.children, language))
+53 -55
ファイルの表示
@@ -27,66 +27,64 @@ const PostOriginalCreatedTimeField: FC<Props> = (
<FormField label={label ('labels.originalCreatedAt')} messages={errors}>
{({ describedBy, invalid }) => (
<>
<div className="my-1 flex">
<div className="w-80">
<DateTimeField
className="mr-2"
disabled={disabled ?? false}
aria-describedby={describedBy}
aria-invalid={invalid}
invalid={invalid}
value={originalCreatedFrom ?? undefined}
onChange={setOriginalCreatedFrom}
onBlur={ev => {
const v = ev.target.value
if (!(v))
return
<div className="my-1 flex min-w-0 flex-wrap items-center gap-2">
<DateTimeField
className="min-w-0 max-w-full"
disabled={disabled ?? false}
aria-describedby={describedBy}
aria-invalid={invalid}
invalid={invalid}
value={originalCreatedFrom ?? undefined}
onChange={setOriginalCreatedFrom}
onBlur={ev => {
const v = ev.target.value
if (!(v))
return
const d = new Date (v)
if (d.getMinutes () === 0 && d.getHours () === 0)
d.setDate (d.getDate () + 1)
else
d.setMinutes (d.getMinutes () + 1)
setOriginalCreatedBefore (toMinutePrecisionIsoUtc (d.toISOString ()))
}}/>
const d = new Date (v)
if (d.getMinutes () === 0 && d.getHours () === 0)
d.setDate (d.getDate () + 1)
else
d.setMinutes (d.getMinutes () + 1)
setOriginalCreatedBefore (toMinutePrecisionIsoUtc (d.toISOString ()))
}}/>
<span className="ainu-ui-label shrink-0 whitespace-nowrap">
{label ('labels.since')}
</div>
<div>
<Button
type="button"
className="bg-gray-600 text-white rounded"
disabled={disabled}
onClick={() => {
setOriginalCreatedFrom (null)
}}>
{label ('actions.reset')}
</Button>
</div>
</span>
<Button
type="button"
className="ainu-ui-label shrink-0 rounded bg-gray-600 text-white
dark:bg-gray-600 dark:text-white"
disabled={disabled}
onClick={() => {
setOriginalCreatedFrom (null)
}}>
{label ('actions.reset')}
</Button>
</div>
<div className="my-1 flex">
<div className="w-80">
<DateTimeField
className="mr-2"
disabled={disabled}
aria-describedby={describedBy}
aria-invalid={invalid}
invalid={invalid}
value={originalCreatedBefore ?? undefined}
onChange={setOriginalCreatedBefore}/>
<div className="my-1 flex min-w-0 flex-wrap items-center gap-2">
<DateTimeField
className="min-w-0 max-w-full"
disabled={disabled}
aria-describedby={describedBy}
aria-invalid={invalid}
invalid={invalid}
value={originalCreatedBefore ?? undefined}
onChange={setOriginalCreatedBefore}/>
<span className="ainu-ui-label shrink-0 whitespace-nowrap">
{label ('labels.before')}
</div>
<div>
<Button
type="button"
className="bg-gray-600 text-white rounded"
disabled={disabled}
onClick={() => {
setOriginalCreatedBefore (null)
}}>
{label ('actions.reset')}
</Button>
</div>
</span>
<Button
type="button"
className="ainu-ui-label shrink-0 rounded bg-gray-600 text-white
dark:bg-gray-600 dark:text-white"
disabled={disabled}
onClick={() => {
setOriginalCreatedBefore (null)
}}>
{label ('actions.reset')}
</Button>
</div>
</>)}
</FormField>)
+22 -11
ファイルの表示
@@ -8,13 +8,15 @@ import { useUITranslation } from '@/lib/useUITranslation'
import PrefetchLink from '@/components/PrefetchLink'
import { wikiKeys } from '@/lib/queryKeys'
import remarkWikiAutoLink from '@/lib/remark-wiki-autolink'
import { fetchWikiPages } from '@/lib/wiki'
import { fetchWikiAutolinkCandidates } from '@/lib/wiki'
import { ainuMDXComponents, LocalisedDocument } from '@/mdx-components'
import type { FC } from 'react'
import type { Components } from 'react-markdown'
type Props = { title: string
body?: string }
body?: string
language?: string }
const mdComponents = { a: (({ href, children }) => (
['/', '.'].some (e => href?.startsWith (e))
@@ -27,23 +29,32 @@ const mdComponents = { a: (({ href, children }) => (
</a>))) } as const satisfies Components
const WikiBody: FC<Props> = ({ title, body }) => {
const WikiBody: FC<Props> = ({ title, body, language = 'und' }) => {
const { message } = useUITranslation ('wiki')
const locale = useLocale ()
const missingPage = message ('missingPage', {
url: `/wiki/new?title=${ encodeURIComponent (title) }` })
const contentLanguage = body ? language : missingPage.language
const matchLanguage = contentLanguage === 'ain-Kana' ? 'ain' : contentLanguage
const { data } = useQuery ({
enabled: Boolean (body),
queryKey: wikiKeys.index ({ }, locale),
queryFn: () => fetchWikiPages ({ }, locale) })
const pageNames = (data ?? []).map (page => page.title).sort ((a, b) => b.length - a.length)
queryKey: wikiKeys.autolinkCandidates (matchLanguage, locale),
queryFn: () => fetchWikiAutolinkCandidates (matchLanguage, locale) })
const remarkPlugins = useMemo (
() => [() => remarkWikiAutoLink (pageNames), remarkGFM], [pageNames])
() => [() => remarkWikiAutoLink (data ?? [], matchLanguage), remarkGFM],
[data, matchLanguage])
const components = {
...((contentLanguage === 'ain' || contentLanguage === 'ain-Kana')
? ainuMDXComponents : { }),
...mdComponents }
return (
<ReactMarkdown components={mdComponents} remarkPlugins={remarkPlugins}>
{body || message ('missingPage', {
url: `/wiki/new?title=${ encodeURIComponent (title) }` }).text}
</ReactMarkdown>)
<LocalisedDocument language={contentLanguage}>
<ReactMarkdown components={components} remarkPlugins={remarkPlugins}>
{body || missingPage.text}
</ReactMarkdown>
</LocalisedDocument>)
}
export default WikiBody