/* eslint-disable security/detect-unsafe-regex */ "use client"; import React from "react"; interface WikiLinkRendererProps { /** HTML content with wiki-links to parse */ html: string; /** Additional CSS classes */ className?: string; } /** * WikiLinkRenderer - Parses and renders wiki-links in HTML content * * Converts: * - [[slug]] → clickable link to /knowledge/slug * - [[slug|display text]] → clickable link with custom text * * Features: * - Distinct styling for wiki-links (blue color, underline) * - Graceful handling of broken links (gray out) * - Preserves all other HTML formatting */ export function WikiLinkRenderer({ html, className = "", }: WikiLinkRendererProps): React.ReactElement { const processedHtml = React.useMemo(() => { return parseWikiLinks(html); }, [html]); return (
); } /** * Parse wiki-links in HTML and convert to anchor tags * * Supports: * - [[slug]] - basic link * - [[slug|display text]] - link with custom display text */ function parseWikiLinks(html: string): string { // Match [[...]] patterns // Group 1: target slug // Group 2: optional display text after | const wikiLinkRegex = /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g; return html.replace(wikiLinkRegex, (match, slug: string, displayText?: string) => { const trimmedSlug = slug.trim(); const text = displayText?.trim() ?? trimmedSlug; // Validate slug contains only safe characters if (!/^[a-zA-Z0-9\-_./]+$/.test(trimmedSlug)) { // Invalid slug - return original text without creating a link return escapeHtml(match); } // Create a styled link // Using data-wiki-link attribute for styling and click handling return `${escapeHtml(text)}`; }); } /** * Handle wiki-link clicks * Intercepts clicks on wiki-links to use Next.js navigation */ function handleWikiLinkClick(e: React.MouseEvent): void { const target = e.target as HTMLElement; // Check if the clicked element is a wiki-link if (target.tagName === "A" && target.dataset.wikiLink === "true") { const href = target.getAttribute("href"); if (href?.startsWith("/knowledge/")) { // Let Next.js Link handle navigation naturally // No need to preventDefault - the href will work } } } /** * Escape HTML to prevent XSS */ function escapeHtml(text: string): string { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } /** * Custom hook to check if a wiki-link target exists * (For future enhancement - mark broken links differently) */ export function useWikiLinkValidation(_slug: string): { isValid: boolean; isLoading: boolean; } { // Placeholder for future implementation // Could fetch from /api/knowledge/entries/:slug to check existence return { isValid: true, isLoading: false, }; }