import type { CSSProperties, ReactNode } from "react"; import { citationHeading } from "../../citations/CitationCard"; import { VerdictChip, VerdictTally } from "../VerdictChip"; import { reportFullTitle, CITATION_KIND_LABELS, type CitationView, type ReportPageView } from "../../../lib/report/views"; import { VERDICT_DEFAULTS, type Verdict, type VerdictStyle } from "../../../lib/report/verdicts"; import type { ClaimSlide, EntrySlide, FoundSlide, SectionSlide, SlideView, SourcesSlide, SummarySlide, TitleSlide, } from "../../../lib/report/slides"; import { useSlidesHost } from "./SlidesHost"; // ONE SLIDE (lib/report/slides.ts SlideView), as the reader sees it in the // Slides view, a card in the Overview, and a page of slides.html/slides.pdf. // Presentational: no state, no effects — rendered on a server (slides.html) // or in the browser alike. Styles: common/styles/slides.css (`.rs-*`). // // Every slide is three rows: where it is (its kind and place, its number), // what it says (by layout), and the way back — "Read this in the article", // to its anchor. Its left edge is its MARK: a claim's verdict colour, else // the accent. export type SlideFit = "canvas" | "reflow"; export function verdictStyleOf(view: Pick, v: Verdict): VerdictStyle { return view.verdicts?.[v] ?? VERDICT_DEFAULTS[v]; } function markOf(slide: SlideView, view: ReportPageView): string | undefined { return slide.kind === "claim" && slide.verdict ? verdictStyleOf(view, slide.verdict).color : undefined; } function Eyebrow({ slide, total }: { slide: SlideView; total: number }) { let kind: string; let place: string | undefined; switch (slide.kind) { case "title": kind = "Article"; break; case "summary": kind = "In brief"; break; case "entry": kind = "Timeline"; place = `${slide.index} of ${slide.count}`; break; case "found": kind = "Findings"; place = `${slide.claimCount} claim${slide.claimCount === 1 ? "" : "s"} ruled`; break; case "section": kind = `Section ${slide.index} of ${slide.count}`; break; case "claim": kind = "Claim"; place = slide.section.title; break; case "sources": kind = "Sources"; break; } return (

{kind} {place ? ` · ${place}` : null}

); } function Points({ points }: { points: readonly string[] }) { const host = useSlidesHost(); return ( ); } // The picture a citation brings: a clip's poster, a post's capture, a // document's still of the sentence. function pictureOf(c: CitationView): { src: string; alt: string } | undefined { if ((c.kind === "video" || c.kind === "audio") && c.poster) return { src: c.poster, alt: "" }; if (c.kind === "post" && c.shot) return { src: c.shot, alt: `Screenshot of the post${c.author ? ` by ${c.author}` : ""}` }; if (c.kind === "source" && c.image) return { src: c.image, alt: `The sentence as ${c.sourceTitle} shows it: ${c.quote}` }; return undefined; } // The evidence a slide shows: the picture (when there is one), the quote on // a rail, what it is and its marker. export function SlideEvidence({ c, quote = true }: { c: CitationView; quote?: boolean }) { const host = useSlidesHost(); const pic = pictureOf(c); const { title, meta } = citationHeading(c); const rail = c.kind === "source" && c.sourceAccent ? ({ "--rs-ev-rail": c.sourceAccent } as CSSProperties) : undefined; return (
{pic && ( // eslint-disable-next-line @next/next/no-img-element -- a published still of unknown size {pic.alt} )} {quote && !(c.kind === "source" && pic) && (
{c.quote}
)}
{CITATION_KIND_LABELS[c.kind]} {meta.length > 0 ? ` · ${meta.join(" · ")}` : ""} {host.cite(c.id, title)}
); } function TitleBody({ slide }: { slide: TitleSlide }) { return ( <> {slide.series &&

{slide.series}

}

{slide.title}

{slide.line &&

{slide.line}

} {slide.dates.length > 0 &&

{slide.dates.join(" · ")}

} {slide.subject && (
{slide.subject.title} {slide.subject.meta.length > 0 && {slide.subject.meta.join(" · ")}}
)} ); } function SummaryBody({ slide, view }: { slide: SummarySlide; view: ReportPageView }) { const host = useSlidesHost(); return ( <>

{slide.title}

{slide.points ? : slide.text ?
{host.md(slide.text)}
: null} {slide.tally && (
)} ); } // A timeline entry: its date first (what a timeline is read by), its title, // the first sentences of its body. function EntryBody({ slide }: { slide: EntrySlide }) { const host = useSlidesHost(); return ( <>

{slide.title}

{slide.text &&
{host.md(slide.text)}
} ); } function FoundBody({ slide, view }: { slide: FoundSlide; view: ReportPageView }) { return ( <>

{slide.title}

{slide.groups.map((g) => { const s = verdictStyleOf(view, g.verdict); return (
{g.claims.length} {s.label}
    {g.claims.map((c) => (
  • {c.title}
  • ))}
); })}
); } function SectionBody({ slide, view }: { slide: SectionSlide; view: ReportPageView }) { const host = useSlidesHost(); const cited = slide.cite ? view.citations[slide.cite] : undefined; const words = slide.points ? ( ) : slide.text ? (
{host.md(slide.text)}
) : slide.claims.length > 0 ? (
    {slide.claims.map((c) => (