Add a new transformer to the document processing pipeline that rebases all relative markdown links to the /pages/{path}/ format. This ensures consistent internal linking across the wiki.
- Transform ALL relative links (not absolute paths starting with
/, not external links) - Convert to
/pages/{link}/format - No URL encoding - keep links as-is
- Handle edge cases: anchors, query params, relative path markers
File: src/document-pipeline/transformers/wikilink.ts
Current behavior:
return `[${displayText}](${encodeURIComponent(link)})`;New behavior:
return `[${displayText}](${link})`;Rationale: Remove encodeURIComponent() so all links remain unencoded throughout the pipeline, consistent with the requirement for no URL encoding.
File: src/document-pipeline/transformers/rebase-links.ts
Function signature:
export function rebaseLinks<
TMetadata,
TContent extends { markdown: string },
>(
doc: Document<TMetadata, TContent>,
): Document<TMetadata, TContent>Core logic:
- Match all markdown links:
/\[([^\]]*)\]\(([^)]+)\)/g - For each link, check if it's a relative link (not absolute, not external)
- If relative, transform to
/pages/{path}/format while preserving anchors and query params - Replace in markdown content
Link detection (relative links):
- NOT external:
http://,https://,mailto:,tel:,ftp:// - NOT absolute: starting with
/ - IS relative: everything else
Transformation algorithm:
function transformLink(href: string): string {
// Skip external links
if (/^(https?|mailto|tel|ftp):/.test(href)) return href;
// Skip absolute paths
if (href.startsWith('/')) return href;
// Parse href components
const [pathPart, ...rest] = href.split(/([?#])/);
let path = pathPart;
// Normalize relative paths
path = path.replace(/^\.\//, '').replace(/^\.\.\//, '');
// Construct new path
const newPath = `/pages/${path}/`;
// Reattach query params and anchors
return newPath + rest.join('');
}Examples:
[Text](PageName)→[Text](/pages/PageName/)[Text](Some Page)→[Text](/pages/Some Page/)(no encoding!)[Text](page?q=test)→[Text](/pages/page/?q=test)[Text](page#section)→[Text](/pages/page/#section)[Text](./relative)→[Text](/pages/relative/)[Text](../parent)→[Text](/pages/parent/)[Text](/absolute/path)→ unchanged[Text](https://example.com)→ unchanged
File: src/document.ts
Current pipeline (lines 132-144):
const doc = await pipe(
parseMarkdown(file),
extractFrontmatter,
sanitizeFrontmatter(validateFrontmatter),
removeFrontmatter,
extractAliases,
extractHeadingTitle,
extractTitle,
removeFirstH1,
transformWikilink,
extractLinks,
render(options),
);New pipeline:
const doc = await pipe(
parseMarkdown(file),
extractFrontmatter,
sanitizeFrontmatter(validateFrontmatter),
removeFrontmatter,
extractAliases,
extractHeadingTitle,
extractTitle,
removeFirstH1,
transformWikilink,
rebaseLinks, // NEW: Add here
extractLinks,
render(options),
);Rationale:
- After
transformWikilink: Wikilinks are converted to markdown links first - Before
extractLinks: Links are rebased before extraction, sometadata.linkscontains rebased hrefs - Before
render(): Rebased links are used in HTML rendering
File: src/document-pipeline/index.ts
Add export:
export { rebaseLinks } from "./transformers/rebase-links.ts";File: src/document.ts
Add to imports (line 9-25):
import {
// ... existing imports
rebaseLinks, // NEW
// ... rest of imports
} from "./document-pipeline/index.ts";Add to exports (line 161-179):
export {
// ... existing exports
rebaseLinks, // NEW
// ... rest of exports
} from "./document-pipeline/index.ts";src/document-pipeline/transformers/rebase-links.ts- Main transformer implementation
src/document-pipeline/transformers/wikilink.ts- RemoveencodeURIComponent()src/document-pipeline/index.ts- ExportrebaseLinkssrc/document.ts- Import, add to pipeline, re-exportsrc/transformers/wikilink.ts- RemoveencodeURIComponent()(utility version)
-
Modify wikilink transformer (both versions)
- Remove
encodeURIComponent()from line 22 insrc/document-pipeline/transformers/wikilink.ts - Remove
encodeURIComponent()from line 22 insrc/transformers/wikilink.ts
- Remove
-
Create rebaseLinks transformer
- Implement in
src/document-pipeline/transformers/rebase-links.ts - Follow the structure of
transformWikilinkas a pattern - Use the transformation algorithm specified above
- Implement in
-
Update exports
- Add export in
src/document-pipeline/index.ts
- Add export in
-
Integrate into pipeline
- Import in
src/document.ts - Add to pipe after
transformWikilink - Re-export from
src/document.ts
- Import in
-
Test thoroughly
- Manual testing with various link formats
- Verify wikilinks are converted and rebased correctly
- Verify external/absolute links remain unchanged
- Anchors:
page#section→/pages/page/#section✓ - Query params:
page?key=val→/pages/page/?key=val✓ - Combined:
page?key=val#section→/pages/page/?key=val#section✓ - Relative markers:
./page,../page→/pages/page/✓ - Spaces:
Some Page→/pages/Some Page/(no encoding) ✓ - Already absolute:
/pages/Home/→ unchanged ✓ - External:
https://example.com→ unchanged ✓ - Empty href:
[text]()→ unchanged (edge case, should be rare)
- Create test document with various link types
- Run through pipeline
- Verify
content.markdownhas rebased links - Verify
content.htmlrenders correctly - Verify
metadata.linkscontains rebased hrefs
# Test Document
- [[Wikilink]] - should become /pages/Wikilink/
- [[Wikilink|Alias]] - should become /pages/Wikilink/
- [Internal](PageName) - should become /pages/PageName/
- [With Space](Page Name) - should become /pages/Page Name/
- [Relative](./page) - should become /pages/page/
- [Parent](../page) - should become /pages/page/
- [Anchor](page#section) - should become /pages/page/#section
- [Query](page?q=test) - should become /pages/page/?q=test
- [Absolute](/absolute/path) - should stay /absolute/path
- [External](https://example.com) - should stay https://example.comThis plan implements link rebasing through:
- Removing encoding from wikilink transformer (consistency)
- Adding rebaseLinks transformer (core functionality)
- Integrating after wikilink conversion, before link extraction
- Preserving anchors, query params, and special link types
- No URL encoding throughout the pipeline
The approach is minimal, focused, and follows existing patterns in the codebase.