"use client"; import { useContext, useEffect, useMemo, type ReactNode } from "react"; import type { ReportPageView } from "yt-dlp-transcript-common/lib/report/views"; import { buildReportSlides, reportPageAnchorIds, slideIndexForPlace, type ReaderPlace, type SlideView, } from "yt-dlp-transcript-common/lib/report/slides"; import { ReportOverview } from "yt-dlp-transcript-common/components/report/slides/ReportOverview"; import { ReportSlides } from "yt-dlp-transcript-common/components/report/slides/ReportSlides"; import { SlidesHostProvider, type SlidesHost } from "yt-dlp-transcript-common/components/report/slides/SlidesHost"; import { readerUrl, type PlaceLike } from "yt-dlp-transcript-common/components/report/slides/useReaderPlace"; import type { Note } from "@/lib/annotations/types"; import { ArticleActionsContext, ArticleCitations, Md } from "./ArticleBody"; // THE ARTICLE AS SLIDES, AND THE OVERVIEW — in umtool's reading column, so a // draft can be read in every shape the published page offers before it is // published (common/components/report/slides/; the export site lays the same // views over its page). The notes rail stays beside them: // // a citation opens its evidence in the rail, as in the article // a slide's notes the open notes on its part of the article, counted on it // (a section's notes on the section's slide, the title's on // the title slide, a citation's on each slide showing it) // Read this in the article, Esc back to the article at the slide's place // Which slides a note is on. function slideKeysOf(note: Note, slides: readonly SlideView[]): string[] { const a = note.anchor; if (a.kind === "text" || a.kind === "section") { if (a.section === "title" || a.section === "subtitle") return ["title"]; if (a.section === "summary") return ["summary"]; return slides.some((s) => s.key === `section:${a.section}`) ? [`section:${a.section}`] : []; } if (a.kind === "cite") { return slides.filter((s) => (s.kind === "claim" || s.kind === "section") && s.cite === a.cite).map((s) => s.key); } return []; } export function openNotesBySlide(notes: readonly Note[], slides: readonly SlideView[]): Map { const out = new Map(); for (const n of notes) { if (n.status !== "open") continue; for (const k of slideKeysOf(n, slides)) out.set(k, (out.get(k) ?? 0) + 1); } return out; } export default function ArticleShapes({ view, notes, place, go, onArticle, }: { view: ReportPageView; notes: readonly Note[]; place: ReaderPlace; go: (place: PlaceLike, push: boolean) => void; // Back to the article at an anchor. onArticle: (anchorId: string | null) => void; }) { const actions = useContext(ArticleActionsContext); const slides = useMemo(() => buildReportSlides(view), [view]); const order = useMemo(() => reportPageAnchorIds(view), [view]); const counts = useMemo(() => openNotesBySlide(notes, slides), [notes, slides]); const index = slideIndexForPlace(slides, place, order); // A slides URL that names a place, not a slide (a switch from the text, a // link to a section): the slide for that place, in the URL — as the export // site's reader does (ReportReader). useEffect(() => { if (place.view === "slides" && place.slide === null && slides.length > 0) go({ view: "slides", slide: index + 1 }, false); }, [place, index, slides.length, go]); const host = useMemo( () => ({ md: (text) => (
), cite: (id: string, children: ReactNode) => ( ), readHref: (s) => readerUrl({ view: "article", anchor: s.anchorId }), onRead: (s) => onArticle(s.anchorId), badge: (s) => { const n = counts.get(s.key) ?? 0; return n > 0 ? ( {n} open note{n === 1 ? "" : "s"} ) : null; }, }), [actions, view, counts, onArticle], ); if (slides.length === 0) return null; return ( {place.view === "slides" ? (
go({ view: "slides", slide: i + 1 }, false)} onExit={(s) => onArticle(s.anchorId)} />
) : (
go({ view: "iso", anchor: a }, false)} onOpenArticle={(s) => onArticle(s.anchorId)} onOpenSlide={(s) => go({ view: "slides", slide: s.n }, true)} onExit={(s) => onArticle(s?.anchorId ?? place.anchor)} />
)}
); }