このコミットが含まれているのは:
2026-10-12 15:16:58 +09:00
コミット 1a4cbe36fb
13個のファイルの変更、451行の追加、225行の削除
+36 -109
ファイルの表示
@@ -1,15 +1,12 @@
import { parse as parseYAML } from 'yaml'
import type { ComponentType } from 'react'
type ArticleLanguage = { currentEdition: string }
type ArticleLanguage = { currentEdition: string; publishedEditions: string[] }
type ArticleMeta = {
export type ArticleMeta = {
identifier: string
canonicalLanguage: string
languages: Record<string, ArticleLanguage> }
type MDXModule = { default: ComponentType }
export type HelpEditionText = {
title: string
body: string }
@@ -21,7 +18,11 @@ export type HelpSearchEntry = HelpEditionText & {
export type HelpEditionContent = HelpEditionText & { content: ComponentType }
export type HelpEditionSource = HelpEditionText & { source: string }
export type HelpChange = HelpSearchEntry & { pred: string | null }
export type HelpChange = Pick<HelpSearchEntry, 'identifier' | 'edition' | 'title'> & {
pred: string | null
change: 'new' | 'update'
date: string }
export type HelpTitleIndex = Record<string, string[]>
export type HelpDiffLine = {
type: 'added' | 'removed' | 'unchanged'
content: string }
@@ -30,70 +31,26 @@ export type HelpDiff = {
to: HelpEditionSource
lines: HelpDiffLine[] }
const articleFiles = import.meta.glob<string> ('../../help/*/article.yml',
{ query: '?raw',
import: 'default',
eager: true })
const editionFiles = import.meta.glob<MDXModule> ('../../help/*/editions/*/*.mdx')
const rawEditionFiles = import.meta.glob<string> ('../../help/*/editions/*/*.mdx',
{ query: '?raw',
import: 'default' })
const cameliseArticle = (
value: { identifier: string
languages: Record<string, { ['current_edition']: string }> },
): 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 = async (identifier: string): Promise<ArticleMeta | null> => {
const { default: articles } = await import ('virtual:help/articles')
return Object.hasOwn (articles, identifier) ? articles[identifier] : null
}
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 = (
export const getHelpEditions = async (
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))
): Promise<string[]> => {
const article = await getHelpArticle (identifier)
return article?.languages[language]?.publishedEditions ?? []
}
export const getHelpPublishedEditions = (
export const getHelpPublishedEditions = async (
identifier: string,
language: string,
): string[] => {
const currentEdition = getHelpArticle (identifier)?.languages[language]?.currentEdition
return currentEdition == null
? []
: getHelpEditions (identifier, language)
.filter (edition => compareEditions (edition, currentEdition) <= 0)
}
): Promise<string[]> => await getHelpEditions (identifier, language)
export const getHelpEdition = async (
@@ -101,8 +58,8 @@ export const getHelpEdition = async (
language: string,
edition: string,
): Promise<ComponentType | null> => {
const path = `../../help/${ identifier }/editions/${ language }/${ edition }.mdx`
const loader = editionFiles[path]
const { components } = await import ('virtual:help/loaders')
const loader = components[`${ identifier }/${ language }/${ edition }`]
if (!(loader))
return null
@@ -116,22 +73,15 @@ export const getHelpEditionSource = async (
language: string,
edition: string,
): Promise<HelpEditionSource | null> => {
const path = `../../help/${ identifier }/editions/${ language }/${ edition }.mdx`
const loader = rawEditionFiles[path]
const { sources } = await import ('virtual:help/loaders')
const loader = sources[`${ identifier }/${ language }/${ edition }`]
if (!(loader))
return null
const source = await loader ()
const mdx = source.replace (/^\uFEFF/, '')
const heading = mdx.match (/^# +([^\r\n]+)(?:\r?\n|$)/)
if (!(heading))
throw new Error (`Help MDX の先頭に H1 がありません: ${ path }`)
return { source,
title: heading[1].trim (),
body: mdx.slice (heading[0].length) }
const [source, text] = await Promise.all ([
loader (), getHelpEditionText (identifier, language, edition)])
return text == null ? null : { source: source.default, ...text }
}
@@ -140,31 +90,17 @@ export const getHelpEditionText = async (
language: string,
edition: string,
): Promise<HelpEditionText | null> => {
const content = await getHelpEditionSource (identifier, language, edition)
return content == null ? null : { title: content.title, body: content.body }
const { texts } = await import ('virtual:help/loaders')
const loader = texts[`${ identifier }/${ language }/${ edition }`]
return loader == null ? null : (await loader ()).default
}
export const getHelpSearchEntries = async (
language: string,
): Promise<HelpSearchEntry[]> => {
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)
const { default: search } = await import ('virtual:help/search')
return search[language] ?? []
}
@@ -182,23 +118,14 @@ export const getHelpEditionContent = async (
export const getHelpChanges = async (language: string): Promise<HelpChange[]> => {
const articles = Object.values (articleFiles).map (source =>
cameliseArticle (parseYAML (source)))
const changes = await Promise.all (articles.flatMap (article => {
const editions = getHelpPublishedEditions (article.identifier, language)
return editions.map (async (edition, index) => {
const text = await getHelpEditionText (article.identifier, language, edition)
if (text == null)
throw new Error (`Help edition が見つかりません: ${ article.identifier }/${ edition }`)
const { default: changes } = await import ('virtual:help/changes')
return changes[language] ?? []
}
return { identifier: article.identifier,
edition, pred: editions[index - 1] ?? null, ...text }
})
}))
return changes.sort ((a, b) =>
compareEditions (b.edition, a.edition)
|| a.identifier.localeCompare (b.identifier))
export const getHelpTitleIndex = async (language: string): Promise<HelpTitleIndex> => {
const { default: titles } = await import ('virtual:help/titles')
return titles[language] ?? { }
}
@@ -252,7 +179,7 @@ export const getHelpDiff = async (
from: string,
to: string,
): Promise<HelpDiff | null> => {
const editions = getHelpPublishedEditions (identifier, language)
const editions = await getHelpPublishedEditions (identifier, language)
if (!(editions.includes (from)) || !(editions.includes (to)))
return null
+1
ファイルの表示
@@ -65,6 +65,7 @@ export const wikiKeys = {
export const helpKeys = {
root: ['help'] as const,
index: (language: string) => ['help', 'index', language] as const,
titles: (language: string) => ['help', 'titles', language] as const,
article: (identifier: string) => ['help', 'article', identifier] as const,
edition: (identifier: string, language: string, edition: string) =>
['help', 'edition', identifier, language, edition] as const,