/** * T078: Note viewer with rendered markdown, metadata, and backlinks * T081-T082: Wikilink click handling and broken link styling */ import { useMemo } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { Edit, Trash2, Calendar, Tag as TagIcon, ArrowLeft } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Separator } from '@/components/ui/separator'; import type { Note } from '@/types/note'; import type { BacklinkResult } from '@/services/api'; import { createWikilinkComponent } from '@/lib/markdown.tsx'; interface NoteViewerProps { note: Note; backlinks: BacklinkResult[]; onEdit?: () => void; onDelete?: () => void; onWikilinkClick: (linkText: string) => void; } export function NoteViewer({ note, backlinks, onEdit, onDelete, onWikilinkClick, }: NoteViewerProps) { // Create custom markdown components with wikilink handler const markdownComponents = useMemo( () => createWikilinkComponent(onWikilinkClick), [onWikilinkClick] ); // Pre-process markdown to convert wikilinks to standard links // [[Link]] -> [Link](wikilink:Link) // [[Link|Alias]] -> [Alias](wikilink:Link) const processedBody = useMemo(() => { if (!note.body) return ''; const processed = note.body.replace(/\[\[([^\]]+)\]\]/g, (_match, content) => { const [link, alias] = content.split('|'); const displayText = alias || link; const href = `wikilink:${encodeURIComponent(link)}`; return `[${displayText}](${href})`; }); // console.log('Processed Body:', processed); return processed; }, [note.body]); const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }); }; return (
{/* Header */}

{note.title}

{note.note_path}

{onEdit && ( )} {onDelete && ( )}
{/* Content */}
url} // Allow all protocols including wikilink: > {processedBody}
{/* Metadata Footer */}
{/* Tags */} {note.metadata.tags && note.metadata.tags.length > 0 && (
{note.metadata.tags.map((tag) => ( {tag} ))}
)} {/* Timestamps */}
Created: {formatDate(note.created)}
Updated: {formatDate(note.updated)}
{/* Backlinks */} {backlinks.length > 0 && ( <>

Backlinks ({backlinks.length})

{backlinks.map((backlink) => ( ))}
)} {/* Additional metadata */} {note.metadata.project && (
Project: {note.metadata.project}
)}
Version: {note.version} | Size: {(note.size_bytes / 1024).toFixed(1)} KB
); }