Spaces:
Sleeping
Sleeping
| /** | |
| * T074: Markdown rendering configuration and wikilink handling | |
| */ | |
| import React from 'react'; | |
| import type { Components } from 'react-markdown'; | |
| export interface WikilinkComponentProps { | |
| linkText: string; | |
| resolved: boolean; | |
| onClick?: (linkText: string) => void; | |
| } | |
| /** | |
| * Custom renderer for wikilinks in markdown | |
| */ | |
| export function createWikilinkComponent( | |
| onWikilinkClick?: (linkText: string) => void | |
| ): Components { | |
| return { | |
| // Override the text renderer to handle wikilinks | |
| text: ({ children }: any) => { | |
| const value = String(children || ''); | |
| const parts: React.ReactNode[] = []; | |
| const pattern = /\[\[([^\]]+)\]\]/g; | |
| let lastIndex = 0; | |
| let match; | |
| let key = 0; | |
| while ((match = pattern.exec(value)) !== null) { | |
| // Add text before the wikilink | |
| if (match.index > lastIndex) { | |
| parts.push(value.slice(lastIndex, match.index)); | |
| } | |
| // Add the wikilink as a clickable element | |
| const linkText = match[1]; | |
| parts.push( | |
| <span | |
| key={key++} | |
| className="wikilink cursor-pointer text-primary hover:underline" | |
| onClick={(e) => { | |
| e.preventDefault(); | |
| onWikilinkClick?.(linkText); | |
| }} | |
| role="link" | |
| tabIndex={0} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| onWikilinkClick?.(linkText); | |
| } | |
| }} | |
| > | |
| [[{linkText}]] | |
| </span> | |
| ); | |
| lastIndex = pattern.lastIndex; | |
| } | |
| // Add remaining text | |
| if (lastIndex < value.length) { | |
| parts.push(value.slice(lastIndex)); | |
| } | |
| return parts.length > 0 ? <>{parts}</> : value; | |
| }, | |
| // Style code blocks | |
| code: ({ className, children, ...props }) => { | |
| const match = /language-(\w+)/.exec(className || ''); | |
| const isInline = !match; | |
| if (isInline) { | |
| return ( | |
| <code className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono" {...props}> | |
| {children} | |
| </code> | |
| ); | |
| } | |
| return ( | |
| <code | |
| className={`${className} block bg-muted p-4 rounded-lg overflow-x-auto text-sm font-mono`} | |
| {...props} | |
| > | |
| {children} | |
| </code> | |
| ); | |
| }, | |
| // Style links | |
| a: ({ href, children, ...props }) => { | |
| const isExternal = href?.startsWith('http'); | |
| return ( | |
| <a | |
| href={href} | |
| className="text-primary hover:underline" | |
| target={isExternal ? '_blank' : undefined} | |
| rel={isExternal ? 'noopener noreferrer' : undefined} | |
| {...props} | |
| > | |
| {children} | |
| </a> | |
| ); | |
| }, | |
| // Style headings | |
| h1: ({ children, ...props }) => ( | |
| <h1 className="text-3xl font-bold mt-6 mb-4" {...props}> | |
| {children} | |
| </h1> | |
| ), | |
| h2: ({ children, ...props }) => ( | |
| <h2 className="text-2xl font-semibold mt-5 mb-3" {...props}> | |
| {children} | |
| </h2> | |
| ), | |
| h3: ({ children, ...props }) => ( | |
| <h3 className="text-xl font-semibold mt-4 mb-2" {...props}> | |
| {children} | |
| </h3> | |
| ), | |
| // Style lists | |
| ul: ({ children, ...props }) => ( | |
| <ul className="list-disc list-inside my-2 space-y-1" {...props}> | |
| {children} | |
| </ul> | |
| ), | |
| ol: ({ children, ...props }) => ( | |
| <ol className="list-decimal list-inside my-2 space-y-1" {...props}> | |
| {children} | |
| </ol> | |
| ), | |
| // Style blockquotes | |
| blockquote: ({ children, ...props }) => ( | |
| <blockquote className="border-l-4 border-muted-foreground pl-4 italic my-4" {...props}> | |
| {children} | |
| </blockquote> | |
| ), | |
| // Style tables | |
| table: ({ children, ...props }) => ( | |
| <div className="overflow-x-auto my-4"> | |
| <table className="min-w-full border-collapse border border-border" {...props}> | |
| {children} | |
| </table> | |
| </div> | |
| ), | |
| th: ({ children, ...props }) => ( | |
| <th className="border border-border px-4 py-2 bg-muted font-semibold text-left" {...props}> | |
| {children} | |
| </th> | |
| ), | |
| td: ({ children, ...props }) => ( | |
| <td className="border border-border px-4 py-2" {...props}> | |
| {children} | |
| </td> | |
| ), | |
| }; | |
| } | |
| /** | |
| * Render broken wikilinks with distinct styling | |
| */ | |
| export function renderBrokenWikilink( | |
| linkText: string, | |
| onCreate?: () => void | |
| ): React.ReactElement { | |
| return ( | |
| <span | |
| className="wikilink-broken text-destructive border-b border-dashed border-destructive cursor-pointer hover:bg-destructive/10" | |
| onClick={onCreate} | |
| role="link" | |
| tabIndex={0} | |
| title={`Note "${linkText}" not found. Click to create.`} | |
| > | |
| [[{linkText}]] | |
| </span> | |
| ); | |
| } | |