feat: add persistent book navigation and public-only pages

This commit is contained in:
2026-10-15 17:53:48 +09:00
parent f6905074b1
commit 839eb2a6a4
22 changed files with 1191 additions and 269 deletions
+19 -7
View File
@@ -10,7 +10,7 @@ export function normalizeBase(value = '/') {
return value === '/' ? '/' : `/${value.split('/').filter(Boolean).join('/')}/`;
}
/** Rewrite Markdown document links, not code, fragments, or external URLs. */
/** Rewrite published Markdown links; null means a source-only draft target. */
export function documentHref(href, sourceFile, base = '/') {
if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(href)) return href;
const match = href.match(/^([^?#]+\.md)([?#].*)?$/i);
@@ -20,18 +20,30 @@ export function documentHref(href, sourceFile, base = '/') {
if (relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error(`Markdown link outside contents/: ${href} in ${sourceFile}`);
}
if (relative.split(path.sep)[0] === 'draft') return null;
const slug = relative.replace(/\.md$/i, '').split(path.sep).map(encodeURIComponent).join('/');
return `${normalizeBase(base)}book/${slug}/${match[2] ?? ''}`;
}
export default function documentLinks({ base = '/' } = {}) {
return (tree, file) => {
function walk(node) {
if ((node.type === 'link' || node.type === 'definition') && typeof node.url === 'string') {
node.url = documentHref(node.url, file.path, base);
}
for (const child of node.children ?? []) walk(child);
const draftReferences = new Set();
function collect(node) {
if (node.type === 'definition' && documentHref(node.url, file.path, base) === null) draftReferences.add(node.identifier);
for (const child of node.children ?? []) collect(child);
}
walk(tree);
collect(tree);
function rewrite(node) {
if (node.children) node.children = node.children.flatMap(rewrite);
if (node.type === 'linkReference' && draftReferences.has(node.identifier)) return node.children;
if ((node.type === 'link' || node.type === 'definition') && typeof node.url === 'string') {
const url = documentHref(node.url, file.path, base);
// Retain authored link text/formatting, but expose no unpublished URL.
if (url === null) return node.type === 'link' ? node.children : [];
node.url = url;
}
return [node];
}
rewrite(tree);
};
}