このコミットが含まれているのは:
2026-10-12 14:46:19 +09:00
コミット 4813a3a09e
11個のファイルの変更、566行の追加、110行の削除
+138 -4
ファイルの表示
@@ -18,6 +18,18 @@ export type HelpSearchEntry = HelpEditionText & {
identifier: string
edition: string }
export type HelpEditionContent = HelpEditionText & { content: ComponentType }
export type HelpEditionSource = HelpEditionText & { source: string }
export type HelpChange = HelpSearchEntry & { pred: string | null }
export type HelpDiffLine = {
type: 'added' | 'removed' | 'unchanged'
content: string }
export type HelpDiff = {
from: HelpEditionSource
to: HelpEditionSource
lines: HelpDiffLine[] }
const articleFiles = import.meta.glob<string> ('../../help/*/article.yml',
{ query: '?raw',
import: 'default',
@@ -72,6 +84,18 @@ export const getHelpEditions = (
}
export const getHelpPublishedEditions = (
identifier: string,
language: string,
): string[] => {
const currentEdition = getHelpArticle (identifier)?.languages[language]?.currentEdition
return currentEdition == null
? []
: getHelpEditions (identifier, language)
.filter (edition => compareEditions (edition, currentEdition) <= 0)
}
export const getHelpEdition = async (
identifier: string,
language: string,
@@ -87,28 +111,40 @@ export const getHelpEdition = async (
}
export const getHelpEditionText = async (
export const getHelpEditionSource = async (
identifier: string,
language: string,
edition: string,
): Promise<HelpEditionText | null> => {
): Promise<HelpEditionSource | null> => {
const path = `../../help/${ identifier }/editions/${ language }/${ edition }.mdx`
const loader = rawEditionFiles[path]
if (!(loader))
return null
const mdx = (await loader ()).replace (/^\uFEFF/, '')
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 { title: heading[1].trim (),
return { source,
title: heading[1].trim (),
body: mdx.slice (heading[0].length) }
}
export const getHelpEditionText = async (
identifier: string,
language: string,
edition: string,
): Promise<HelpEditionText | null> => {
const content = await getHelpEditionSource (identifier, language, edition)
return content == null ? null : { title: content.title, body: content.body }
}
export const getHelpSearchEntries = async (
language: string,
): Promise<HelpSearchEntry[]> => {
@@ -130,3 +166,101 @@ export const getHelpSearchEntries = async (
return entries.filter ((entry): entry is HelpSearchEntry => entry != null)
}
export const getHelpEditionContent = async (
identifier: string,
language: string,
edition: string,
): Promise<HelpEditionContent | null> => {
const [content, text] = await Promise.all ([
getHelpEdition (identifier, language, edition),
getHelpEditionText (identifier, language, edition)])
return content != null && text != null ? { content, ...text } : null
}
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 }`)
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))
}
const diffHelpLines = (from: string, to: string): HelpDiffLine[] => {
const before = from.match (/[^\n]*\n|[^\n]+$/g) ?? []
const after = to.match (/[^\n]*\n|[^\n]+$/g) ?? []
const lengths = Array.from ({ length: before.length + 1 },
() => new Uint32Array (after.length + 1))
for (let i = before.length - 1; i >= 0; --i)
{
for (let j = after.length - 1; j >= 0; --j)
{
lengths[i][j] =
before[i] === after[j]
? lengths[i + 1][j + 1] + 1
: Math.max (lengths[i + 1][j], lengths[i][j + 1])
}
}
const lines: HelpDiffLine[] = []
let i = 0
let j = 0
while (i < before.length || j < after.length)
{
if (i < before.length && j < after.length && before[i] === after[j])
{
lines.push ({ type: 'unchanged', content: before[i] })
++i
++j
}
else if (i < before.length
&& (j === after.length || lengths[i + 1][j] >= lengths[i][j + 1]))
{
lines.push ({ type: 'removed', content: before[i] })
++i
}
else
{
lines.push ({ type: 'added', content: after[j] })
++j
}
}
return lines
}
export const getHelpDiff = async (
identifier: string,
language: string,
from: string,
to: string,
): Promise<HelpDiff | null> => {
const editions = getHelpPublishedEditions (identifier, language)
if (!(editions.includes (from)) || !(editions.includes (to)))
return null
const [before, after] = await Promise.all ([
getHelpEditionSource (identifier, language, from),
getHelpEditionSource (identifier, language, to)])
if (before == null || after == null)
return null
return { from: before, to: after, lines: diffHelpLines (before.source, after.source) }
}