"use client"; import { useState, useCallback, type MouseEvent as ReactMouseEvent } from "react"; import dynamic from "next/dynamic"; import { splitReportBlocks, hasReportBlocks } from "@/lib/report-blocks"; import { isWorkspaceLink } from "@/lib/workspace-links"; import type { TreeNode, MentionSearchFn } from "./slash-command"; // Load markdown renderer client-only to avoid SSR issues with ESM-only packages const MarkdownContent = dynamic( () => import("./markdown-content").then((mod) => mod.MarkdownContent), { ssr: false, loading: () => (
), }, ); // Lazy-load ReportCard (uses Recharts which is heavy) const ReportCard = dynamic( () => import("../charts/report-card").then((m) => ({ default: m.ReportCard })), { ssr: false, loading: () => (
), }, ); // Lazy-load the Tiptap-based editor (heavy -- keep out of initial bundle) const MarkdownEditor = dynamic( () => import("./markdown-editor").then((m) => ({ default: m.MarkdownEditor })), { ssr: false, loading: () => (
), }, ); type DocumentViewProps = { content: string; title?: string; filePath?: string; tree?: TreeNode[]; onSave?: () => void; onNavigate?: (path: string) => void; searchFn?: MentionSearchFn; }; export function DocumentView({ content, title, filePath, tree, onSave, onNavigate, searchFn, }: DocumentViewProps) { const [editMode, setEditMode] = useState(!!filePath); // Strip YAML frontmatter if present const body = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, ""); // Extract title from first H1 if no title provided const h1Match = body.match(/^#\s+(.+)/m); const displayTitle = title ?? h1Match?.[1]; const markdownBody = displayTitle && h1Match ? body.replace(/^#\s+.+\n?/, "") : body; // If we have a filePath and editing is enabled, render the Tiptap editor if (editMode && filePath) { return (
setEditMode(false)} searchFn={searchFn} />
); } // Check if the markdown contains embedded report-json blocks const hasReports = hasReportBlocks(markdownBody); // Intercept workspace-internal links in read mode (delegated click handler) const handleLinkClick = useCallback( (event: ReactMouseEvent) => { if (!onNavigate) {return;} const target = event.target as HTMLElement; const link = target.closest("a"); if (!link) {return;} const href = link.getAttribute("href"); if (!href) {return;} if (isWorkspaceLink(href)) { event.preventDefault(); event.stopPropagation(); onNavigate(href); } }, [onNavigate], ); return ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
{/* Header row with title + edit button */}
{displayTitle && (

{displayTitle}

)} {filePath && ( )}
{hasReports ? ( ) : (
)}
); } /** * Renders markdown content that contains embedded report-json blocks. * Splits the content into alternating markdown and interactive chart sections. */ function EmbeddedReportContent({ content }: { content: string }) { const segments = splitReportBlocks(content); return (
{segments.map((segment, index) => { if (segment.type === "report-artifact") { return (
); } // Text segment -- render as markdown return (
); })}
); }