118 lines
4.5 KiB
JavaScript
118 lines
4.5 KiB
JavaScript
/**
|
|
* Rehype plugin to transform markdown file links (.md) to page routes
|
|
*
|
|
* Transforms:
|
|
* ./path/to/file.md → ./path/to/file/
|
|
* ./path/index.md → ./path/ (index.md becomes directory root)
|
|
* ../path/file.md#anchor → ../path/file/#anchor
|
|
* ./file.md?query=param → ./file/?query=param
|
|
* /docs/absolute/path/file.md → {base}/absolute/path/file/
|
|
*
|
|
* For absolute paths starting with /docs/, the /docs prefix is stripped
|
|
* since the Astro site serves content from the docs directory as the root.
|
|
* The base path is prepended to absolute paths for subdirectory deployments.
|
|
*
|
|
* Affects relative links (./, ../) and absolute paths (/) - external links are unchanged
|
|
*/
|
|
|
|
import { visit } from 'unist-util-visit';
|
|
|
|
/**
|
|
* Convert Markdown file links (.md) into equivalent page route-style links.
|
|
*
|
|
* The returned transformer walks the HTML tree and rewrites anchor `href` values that are relative paths (./, ../) or absolute paths (/) pointing to `.md` files. It preserves query strings and hash anchors, rewrites `.../index.md` to the directory root path (`.../`), and rewrites other `.md` file paths by removing the `.md` extension and ensuring a trailing slash. External links (http://, https://) and non-.md links are left unchanged.
|
|
*
|
|
* @param {Object} options - Plugin options
|
|
* @param {string} options.base - The base path to prepend to absolute URLs (e.g., '/BMAD-METHOD/')
|
|
* @returns {function} A HAST tree transformer that mutates `a` element `href` properties as described.
|
|
*/
|
|
export default function rehypeMarkdownLinks(options = {}) {
|
|
const base = options.base || '/';
|
|
// Normalize base: ensure it ends with / and doesn't have double slashes
|
|
const normalizedBase = base === '/' ? '' : base.endsWith('/') ? base.slice(0, -1) : base;
|
|
|
|
return (tree) => {
|
|
visit(tree, 'element', (node) => {
|
|
// Only process anchor tags with href
|
|
if (node.tagName !== 'a' || !node.properties?.href) {
|
|
return;
|
|
}
|
|
|
|
const href = node.properties.href;
|
|
|
|
// Skip if not a string (shouldn't happen, but be safe)
|
|
if (typeof href !== 'string') {
|
|
return;
|
|
}
|
|
|
|
// Skip external links (http://, https://, mailto:, etc.)
|
|
if (href.includes('://') || href.startsWith('mailto:') || href.startsWith('tel:')) {
|
|
return;
|
|
}
|
|
|
|
// Only transform paths starting with ./, ../, or / (absolute)
|
|
if (!href.startsWith('./') && !href.startsWith('../') && !href.startsWith('/')) {
|
|
return;
|
|
}
|
|
|
|
// Extract path portion (before ? and #) to check if it's a .md file
|
|
const firstDelimiter = Math.min(
|
|
href.indexOf('?') === -1 ? Infinity : href.indexOf('?'),
|
|
href.indexOf('#') === -1 ? Infinity : href.indexOf('#'),
|
|
);
|
|
const pathPortion = firstDelimiter === Infinity ? href : href.substring(0, firstDelimiter);
|
|
|
|
// Don't transform if path doesn't end with .md
|
|
if (!pathPortion.endsWith('.md')) {
|
|
return;
|
|
}
|
|
|
|
// Split the URL into parts: path, anchor, and query
|
|
let urlPath = pathPortion;
|
|
let anchor = '';
|
|
let query = '';
|
|
|
|
// Extract query string and anchor from original href
|
|
if (firstDelimiter !== Infinity) {
|
|
const suffix = href.substring(firstDelimiter);
|
|
const anchorInSuffix = suffix.indexOf('#');
|
|
if (suffix.startsWith('?')) {
|
|
if (anchorInSuffix !== -1) {
|
|
query = suffix.substring(0, anchorInSuffix);
|
|
anchor = suffix.substring(anchorInSuffix);
|
|
} else {
|
|
query = suffix;
|
|
}
|
|
} else {
|
|
// starts with #
|
|
anchor = suffix;
|
|
}
|
|
}
|
|
|
|
// Track if this was an absolute path (for base path prepending)
|
|
const isAbsolute = urlPath.startsWith('/');
|
|
|
|
// Strip /docs/ prefix from absolute paths (repo-relative → site-relative)
|
|
if (urlPath.startsWith('/docs/')) {
|
|
urlPath = urlPath.slice(5); // Remove '/docs' prefix, keeping the leading /
|
|
}
|
|
|
|
// Transform .md to /
|
|
// Special case: index.md → directory root (e.g., ./tutorials/index.md → ./tutorials/)
|
|
if (urlPath.endsWith('/index.md')) {
|
|
urlPath = urlPath.replace(/\/index\.md$/, '/');
|
|
} else {
|
|
urlPath = urlPath.replace(/\.md$/, '/');
|
|
}
|
|
|
|
// Prepend base path to absolute URLs for subdirectory deployments
|
|
if (isAbsolute && normalizedBase) {
|
|
urlPath = normalizedBase + urlPath;
|
|
}
|
|
|
|
// Reconstruct the href
|
|
node.properties.href = urlPath + query + anchor;
|
|
});
|
|
};
|
|
}
|