import { parse as parseYAML } from 'yaml' import type { ComponentType } from 'react' type ArticleLanguage = { currentEdition: string } type ArticleMeta = { identifier: string languages: Record } type MDXModule = { default: ComponentType } export type HelpEditionText = { title: string body: string } export type HelpSearchEntry = HelpEditionText & { identifier: string edition: string } const articleFiles = import.meta.glob ('../../help/*/article.yml', { query: '?raw', import: 'default', eager: true }) const editionFiles = import.meta.glob ('../../help/*/editions/*/*.mdx') const rawEditionFiles = import.meta.glob ('../../help/*/editions/*/*.mdx', { query: '?raw', import: 'default' }) const cameliseArticle = ( value: { identifier: string languages: Record }, ): ArticleMeta => ( { identifier: value.identifier, languages: Object.fromEntries ( Object.entries (value.languages).map (([language, data]) => [ language, { currentEdition: data['current_edition'] }])) }) const compareEditions = (a: string, b: string): number => { const [dateA, revisionA = '1'] = a.split ('.') const [dateB, revisionB = '1'] = b.split ('.') return dateA.localeCompare (dateB) || Number (revisionA) - Number (revisionB) } export const getHelpArticle = (identifier: string): ArticleMeta | null => { const path = `../../help/${ identifier }/article.yml` const source = articleFiles[path] if (!(source)) return null return cameliseArticle (parseYAML (source)) } export const getHelpEditions = ( identifier: string, language: string, ): string[] => { const prefix = `../../help/${ identifier }/editions/${ language }/` return (Object.keys (editionFiles) .filter (path => path.startsWith (prefix)) .map (path => path.slice (prefix.length, -'.mdx'.length)) .sort (compareEditions)) } export const getHelpEdition = async ( identifier: string, language: string, edition: string, ): Promise => { const path = `../../help/${ identifier }/editions/${ language }/${ edition }.mdx` const loader = editionFiles[path] if (!(loader)) return null return (await loader ()).default } export const getHelpEditionText = async ( identifier: string, language: string, edition: string, ): Promise => { const path = `../../help/${ identifier }/editions/${ language }/${ edition }.mdx` const loader = rawEditionFiles[path] if (!(loader)) return null const mdx = (await loader ()).replace (/^\uFEFF/, '') const heading = mdx.match (/^# +([^\r\n]+)(?:\r?\n|$)/) if (!(heading)) throw new Error (`Help MDX の先頭に H1 がありません: ${ path }`) return { title: heading[1].trim (), body: mdx.slice (heading[0].length) } } export const getHelpSearchEntries = async ( language: string, ): Promise => { const entries = await Promise.all ( Object.values (articleFiles).map (async source => { const article = cameliseArticle (parseYAML (source)) const edition = article.languages[language]?.currentEdition if (!(edition)) return null const content = await getHelpEditionText (article.identifier, language, edition) if (!(content)) throw new Error (`Help edition が見つかりません: ${ article.identifier }/${ edition }`) return { identifier: article.identifier, edition, ...content } })) return entries.filter ((entry): entry is HelpSearchEntry => entry != null) }