91 lines
3 KiB
TypeScript
91 lines
3 KiB
TypeScript
/**
|
|
* Wikilink Transformation Plugin
|
|
*
|
|
* Transforms Obsidian wikilink syntax to standard markdown links at build time.
|
|
* Handles:
|
|
* - [[Page Title]] → [Page Title](/articles/page-title)
|
|
* - [[Page|Display Text]] → [Display Text](/articles/page)
|
|
* - ![[image.jpg]] → 
|
|
* - ![[image.jpg|alt text]] → 
|
|
*
|
|
* Runs during content:file:beforeParse hook for early transformation
|
|
*/
|
|
|
|
export default defineNitroPlugin((nitroApp) => {
|
|
nitroApp.hooks.hook('content:file:beforeParse' as any, (file: any) => {
|
|
// Only process markdown files in articles collection
|
|
if (file._id && file._id.endsWith('.md') && file._id.includes('/articles/')) {
|
|
if (file.body) {
|
|
file.body = transformWikilinks(file.body);
|
|
file.body = transformImageEmbeds(file.body);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Transform Obsidian wikilinks to markdown links
|
|
* Patterns:
|
|
* - [[P0 - Page Title]]
|
|
* - [[Page Title|Display Text]]
|
|
* - [[P1 - Long Title|Short]]
|
|
*/
|
|
function transformWikilinks(content: string): string {
|
|
// Pattern: [[P0/P1/P2 - Page Title]] or [[Page Title]] with optional |Display Text
|
|
const wikilinkPattern = /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g;
|
|
|
|
return content.replace(wikilinkPattern, (match, pageName, displayText) => {
|
|
// Clean the page name: strip P0/P1/P2 prefix
|
|
const cleanName = pageName.replace(/^P[012]\s*-\s*/i, '').trim();
|
|
|
|
// Generate slug from clean name
|
|
const slug = titleToSlug(cleanName);
|
|
|
|
// Use display text if provided, otherwise use clean name
|
|
const linkText = displayText ? displayText.trim() : cleanName;
|
|
|
|
// Generate markdown link with /articles/ prefix
|
|
return `[${linkText}](/articles/${slug})`;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Transform Obsidian image embeds to markdown image syntax
|
|
* Patterns:
|
|
* - ![[image.jpg]]
|
|
* - ![[image.jpg|alt text]]
|
|
*/
|
|
function transformImageEmbeds(content: string): string {
|
|
// Pattern: ![[filename]] or ![[filename|alt text]]
|
|
const imagePattern = /!\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g;
|
|
|
|
return content.replace(imagePattern, (match, filename, altText) => {
|
|
// Use alt text if provided, otherwise use filename (without extension)
|
|
const alt = altText?.trim() || filename.replace(/\.(jpg|jpeg|png|gif|svg|webp)$/i, '');
|
|
|
|
// Generate markdown image syntax with /img/ prefix
|
|
return ``;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Convert a page title to a URL slug
|
|
* Rules:
|
|
* 1. Convert to lowercase
|
|
* 2. Remove quotation marks
|
|
* 3. Replace & with "and"
|
|
* 4. Remove other special characters (keep alphanumeric and hyphens)
|
|
* 5. Replace spaces with hyphens
|
|
* 6. Collapse multiple hyphens to single hyphen
|
|
* 7. Trim hyphens from start/end
|
|
*/
|
|
function titleToSlug(title: string): string {
|
|
return title
|
|
.toLowerCase()
|
|
.replace(/['"]/g, '') // Remove quotes
|
|
.replace(/&/g, 'and') // & → and
|
|
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters
|
|
.replace(/\s+/g, '-') // Spaces → hyphens
|
|
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
.replace(/^-|-$/g, ''); // Trim hyphens from edges
|
|
}
|