The analysis-and-research.md content has markdown tables (ציר דיוני) and inline formatting like **label:** strong runs, which were rendering as raw pipes and dashes because the compose page used whitespace-pre-line on plain text. Add a reusable <Markdown> component backed by react-markdown + remark-gfm with a custom `components` map that styles paragraphs, lists, blockquotes, strong runs, and especially GFM tables for RTL + aligned columns: - table: table-auto + border-collapse, wrapped in overflow-x-auto so very wide tables don't push the parent card out - th: whitespace-nowrap so the header row sets column widths and every row border lines up row-to-row - everything text-right + the whole block gets dir="rtl" at the root Use it in three places on the compose screen: - ProseSection (represented_party, procedural_background, agreed_facts, disputed_facts) - Conclusions card - SubsectionCard field content — threshold_claims and issues have the same markdown shape in their fields[] react-markdown + remark-gfm added (~30KB gzipped). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
117 lines
4.5 KiB
TypeScript
117 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import ReactMarkdown from "react-markdown";
|
|
import remarkGfm from "remark-gfm";
|
|
|
|
/*
|
|
* Tiny markdown renderer for Hebrew prose blocks — paragraphs, lists,
|
|
* emphasis, and GFM tables (the main reason this exists). The parsed
|
|
* research_md fields and the conclusions field both contain tables
|
|
* like "ציר דיוני" that we want to render as real <table>s, RTL, with
|
|
* auto-sized columns that line up row-to-row.
|
|
*
|
|
* Table styling uses `table-auto` + `whitespace-nowrap` on header cells
|
|
* so the column widths are dictated by the longest cell in that column,
|
|
* and every row's borders align exactly underneath each other. The
|
|
* overflow-x-auto wrapper catches extremely wide tables on narrow
|
|
* viewports without letting the parent card grow.
|
|
*/
|
|
|
|
export function Markdown({ content }: { content: string }) {
|
|
return (
|
|
<div className="prose-md text-sm text-ink-soft leading-relaxed" dir="rtl">
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
components={{
|
|
p: ({ node: _n, ...props }) => (
|
|
<p className="mb-2 last:mb-0 text-justify" {...props} />
|
|
),
|
|
strong: ({ node: _n, ...props }) => (
|
|
<strong className="text-navy font-semibold" {...props} />
|
|
),
|
|
em: ({ node: _n, ...props }) => (
|
|
<em className="text-ink" {...props} />
|
|
),
|
|
a: ({ node: _n, ...props }) => (
|
|
<a
|
|
className="text-gold-deep hover:text-gold underline underline-offset-2"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
{...props}
|
|
/>
|
|
),
|
|
ul: ({ node: _n, ...props }) => (
|
|
<ul className="list-disc ps-5 mb-2 space-y-1" {...props} />
|
|
),
|
|
ol: ({ node: _n, ...props }) => (
|
|
<ol className="list-decimal ps-5 mb-2 space-y-1" {...props} />
|
|
),
|
|
li: ({ node: _n, ...props }) => (
|
|
<li className="text-ink" {...props} />
|
|
),
|
|
h1: ({ node: _n, ...props }) => (
|
|
<h3 className="text-navy text-base font-semibold mt-3 mb-1" {...props} />
|
|
),
|
|
h2: ({ node: _n, ...props }) => (
|
|
<h4 className="text-navy text-sm font-semibold mt-3 mb-1" {...props} />
|
|
),
|
|
h3: ({ node: _n, ...props }) => (
|
|
<h5 className="text-navy text-sm font-semibold mt-2 mb-1" {...props} />
|
|
),
|
|
blockquote: ({ node: _n, ...props }) => (
|
|
<blockquote
|
|
className="border-e-2 border-gold-soft pe-3 text-ink italic my-2"
|
|
{...props}
|
|
/>
|
|
),
|
|
code: ({ node: _n, ...props }) => (
|
|
<code
|
|
className="rounded bg-rule-soft px-1 py-0.5 font-mono text-[0.78rem] text-ink"
|
|
{...props}
|
|
/>
|
|
),
|
|
/* ── Tables ─────────────────────────────────────────────────
|
|
Wrapped in an overflow-x-auto so very wide tables don't push
|
|
the parent card out of its track. table-auto lets the browser
|
|
size columns by their longest cell (that's what keeps borders
|
|
aligned row-to-row) and whitespace-nowrap on the headers
|
|
ensures the header row sets column widths instead of
|
|
breaking mid-word. */
|
|
table: ({ node: _n, ...props }) => (
|
|
<div className="my-3 -mx-1 overflow-x-auto">
|
|
<table
|
|
className="w-full table-auto border-collapse border border-rule text-sm text-right"
|
|
{...props}
|
|
/>
|
|
</div>
|
|
),
|
|
thead: ({ node: _n, ...props }) => (
|
|
<thead className="bg-rule-soft/70" {...props} />
|
|
),
|
|
tbody: ({ node: _n, ...props }) => <tbody {...props} />,
|
|
tr: ({ node: _n, ...props }) => (
|
|
<tr className="border-b border-rule last:border-b-0" {...props} />
|
|
),
|
|
th: ({ node: _n, ...props }) => (
|
|
<th
|
|
className="border border-rule px-3 py-2 text-right text-navy font-semibold whitespace-nowrap align-top"
|
|
{...props}
|
|
/>
|
|
),
|
|
td: ({ node: _n, ...props }) => (
|
|
<td
|
|
className="border border-rule px-3 py-2 text-right text-ink align-top"
|
|
{...props}
|
|
/>
|
|
),
|
|
hr: ({ node: _n, ...props }) => (
|
|
<hr className="my-3 border-rule" {...props} />
|
|
),
|
|
}}
|
|
>
|
|
{content}
|
|
</ReactMarkdown>
|
|
</div>
|
|
);
|
|
}
|