fix(report): group chart cards in the existing grid and stop remounting on scroll (BUG-616, BUG-617)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
||||
import { memo, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
||||
import ReactMarkdown, { type Components } from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
parseReportChartBlock,
|
||||
toNorthIndianChart,
|
||||
} from "@/lib/report-chart-block";
|
||||
import { reportChartGrid } from "@/lib/report-chart-grid-rehype";
|
||||
import { NorthIndianChartSvg } from "@/components/personal-report/vedic-chart-svg";
|
||||
import {
|
||||
buildLongformOutline,
|
||||
@@ -90,6 +91,7 @@ function renderMarkdown(markdown: string, headings: readonly LongformHeading[])
|
||||
<ReactMarkdown
|
||||
components={markdownComponents(headings)}
|
||||
disallowedElements={["script", "iframe", "object", "embed", "img"]}
|
||||
rehypePlugins={[reportChartGrid]}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
skipHtml
|
||||
unwrapDisallowed
|
||||
@@ -100,7 +102,7 @@ function renderMarkdown(markdown: string, headings: readonly LongformHeading[])
|
||||
);
|
||||
}
|
||||
|
||||
function LazyMarkdownSection({
|
||||
const LazyMarkdownSection = memo(function LazyMarkdownSection({
|
||||
section,
|
||||
}: {
|
||||
section: LongformSection;
|
||||
@@ -120,21 +122,24 @@ function LazyMarkdownSection({
|
||||
observer.observe(ref.current);
|
||||
return () => observer.disconnect();
|
||||
}, [visible]);
|
||||
const content = useMemo(
|
||||
() => (visible ? renderMarkdown(section.markdown, section.headings) : (
|
||||
<h2 id={section.id}>{section.title}</h2>
|
||||
)),
|
||||
[visible, section],
|
||||
);
|
||||
return (
|
||||
<section ref={ref} className="personal-report-md-section">
|
||||
{visible ? renderMarkdown(section.markdown, section.headings) : (
|
||||
<h2 id={section.id}>{section.title}</h2>
|
||||
)}
|
||||
{content}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export function PersonalReportMarkdownView({ markdown }: { markdown: string }) {
|
||||
const outline = useMemo(() => buildLongformOutline(markdown), [markdown]);
|
||||
const [activeId, setActiveId] = useState(outline.headings[0]?.id ?? "");
|
||||
function ReportToc({ headings }: { headings: readonly LongformHeading[] }) {
|
||||
const [activeId, setActiveId] = useState(headings[0]?.id ?? "");
|
||||
|
||||
useEffect(() => {
|
||||
if (outline.headings.length === 0 || typeof IntersectionObserver === "undefined") return;
|
||||
if (headings.length === 0 || typeof IntersectionObserver === "undefined") return;
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
const visible = entries
|
||||
.filter((entry) => entry.isIntersecting)
|
||||
@@ -142,29 +147,41 @@ export function PersonalReportMarkdownView({ markdown }: { markdown: string }) {
|
||||
const id = visible[0]?.target.id;
|
||||
if (id) setActiveId(id);
|
||||
}, { rootMargin: "-20% 0px -70% 0px", threshold: [0, 1] });
|
||||
for (const item of outline.headings) {
|
||||
for (const item of headings) {
|
||||
const node = document.getElementById(item.id);
|
||||
if (node) observer.observe(node);
|
||||
}
|
||||
return () => observer.disconnect();
|
||||
}, [outline.headings]);
|
||||
}, [headings]);
|
||||
|
||||
return (
|
||||
<nav aria-label="报告目录" className="personal-report-toc">
|
||||
<details className="personal-report-toc-drawer">
|
||||
<summary>目录</summary>
|
||||
<TocList activeId={activeId} items={headings} />
|
||||
</details>
|
||||
<div className="personal-report-toc-desktop">
|
||||
<p>目录</p>
|
||||
<TocList activeId={activeId} items={headings} />
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
export function PersonalReportMarkdownView({ markdown }: { markdown: string }) {
|
||||
const outline = useMemo(() => buildLongformOutline(markdown), [markdown]);
|
||||
const lead = useMemo(
|
||||
() => (outline.leadMarkdown ? renderMarkdown(outline.leadMarkdown, outline.headings) : null),
|
||||
[outline],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="personal-report-md-layout">
|
||||
<nav aria-label="报告目录" className="personal-report-toc">
|
||||
<details className="personal-report-toc-drawer">
|
||||
<summary>目录</summary>
|
||||
<TocList activeId={activeId} items={outline.headings} />
|
||||
</details>
|
||||
<div className="personal-report-toc-desktop">
|
||||
<p>目录</p>
|
||||
<TocList activeId={activeId} items={outline.headings} />
|
||||
</div>
|
||||
</nav>
|
||||
<ReportToc headings={outline.headings} />
|
||||
<article className="personal-report-document personal-report-md-article">
|
||||
{outline.leadMarkdown ? (
|
||||
{lead ? (
|
||||
<section className="personal-report-md-lead">
|
||||
{renderMarkdown(outline.leadMarkdown, outline.headings)}
|
||||
{lead}
|
||||
</section>
|
||||
) : null}
|
||||
{outline.sections.filter((section) => !section.eager).map((section) => (
|
||||
|
||||
Reference in New Issue
Block a user