Add SEO and agent-readable documentation

This commit is contained in:
2026-08-14 21:10:52 +09:00
parent cda32cc260
commit e5c73c647d
26 changed files with 576 additions and 13 deletions
+51 -3
View File
@@ -173,6 +173,16 @@ export function allDocSlugs(locale = 'en') {
return Object.keys(docsByLocale[locale] ?? {}).filter((slug) => slug !== 'index');
}
function flattenNav(items) {
return items.flatMap((item) => [item.slug, ...(item.children ? flattenNav(item.children) : [])]);
}
export function orderedDocSlugs(locale = 'en', { includeIndex = false } = {}) {
const localizedDocs = docsByLocale[locale] ?? docsByLocale.en;
const slugs = flattenNav(getNav(locale)).filter((slug) => localizedDocs[slug]);
return includeIndex ? ['index', ...slugs] : slugs;
}
function findDocTitle(slug, items) {
for (const item of items) {
if (item.slug === slug) return item.title;
@@ -185,13 +195,51 @@ function findDocTitle(slug, items) {
}
export function docTitle(slug, locale = 'en') {
if (slug === 'index') return locale === 'ja' ? 'Decodalマニュアル' : 'Decodal Manual';
return findDocTitle(slug, getNav(locale)) ?? slug;
}
export function renderMarkdown(slug, locale = 'en') {
function getSource(slug, locale) {
const localizedDocs = docsByLocale[locale] ?? docsByLocale.en;
const source = localizedDocs[slug] ?? localizedDocs.index;
const html = marked.parse(source ?? '# Not found\n');
return localizedDocs[slug] ?? localizedDocs.index ?? '# Not found\n';
}
function stripInlineMarkdown(value) {
return value
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
.replace(/[`*_~]/g, '')
.replace(/<[^>]+>/g, '')
.replace(/\s+/g, ' ')
.trim();
}
export function docDescription(slug, locale = 'en') {
const blocks = getSource(slug, locale)
.replace(/```[\s\S]*?```/g, '')
.split(/\n\s*\n/)
.map((block) => block.trim())
.filter((block) => block && !block.startsWith('#') && !/^[-*]\s/.test(block));
const description = stripInlineMarkdown(blocks[0] ?? '');
if (description.length <= 180) return description;
const shortened = description.slice(0, 177).replace(/\s+\S*$/, '').trimEnd();
return `${shortened || description.slice(0, 177)}`;
}
export function sourceMarkdown(slug, locale = 'en') {
return getSource(slug, locale).replace(/\]\(([^)\s]+)\)/g, (all, destination) => {
const [href, hash = ''] = destination.split('#', 2);
if (!href.endsWith('.md')) return all;
const target = normalizeDocLink(locale, slug, href.slice(0, -3));
const pathname = target === 'index'
? `/docs/${locale}.md`
: `/docs/${locale}/${target}.md`;
return `](${pathname}${hash ? `#${hash}` : ''})`;
});
}
export function renderMarkdown(slug, locale = 'en') {
const html = marked.parse(getSource(slug, locale));
return html.replace(/href="([^"#][^"]*)\.md(#[^"]*)?"/g, (_all, href, hash = '') => {
const target = normalizeDocLink(locale, slug, href);
const pathname = target === 'index' ? `/docs/${locale}/` : `/docs/${locale}/${target}/`;