50 lines
2.2 KiB
JavaScript
50 lines
2.2 KiB
JavaScript
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
export const contentsRoot = fileURLToPath(new URL('../../contents/', import.meta.url));
|
|
|
|
export function normalizeBase(value = '/') {
|
|
if (!/^\/(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]*\/?$/.test(value)) {
|
|
throw new Error('SITE_BASE must be / or an absolute path of URL-safe segments');
|
|
}
|
|
return value === '/' ? '/' : `/${value.split('/').filter(Boolean).join('/')}/`;
|
|
}
|
|
|
|
/** 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);
|
|
if (!match) return href;
|
|
const filename = path.resolve(path.dirname(sourceFile), decodeURIComponent(match[1]));
|
|
const relative = path.relative(contentsRoot, filename);
|
|
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) => {
|
|
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);
|
|
}
|
|
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);
|
|
};
|
|
}
|