SPB Git

Repository Public

markdown.tsx 2.7 KB 68 lines tsx

1import { memo } from 'react';2import { parseMarkdown, type Inline } from '@/lib/markdown';3import { cn } from '@/lib/cn';45function Inl({ parts }: { parts: Inline[] }) {6  return (7    <>8      {parts.map((p, i) =>9        p.t === 'code' ? (10          <code key={i} className="rounded-[5px] bg-surface-2 px-1 py-px font-mono text-[0.9em] text-text">{p.v}</code>11        ) : p.t === 'bold' ? (12          <strong key={i} className="font-semibold text-text">{p.v}</strong>13        ) : p.t === 'italic' ? (14          <em key={i}>{p.v}</em>15        ) : (16          <span key={i}>{p.v}</span>17        ),18      )}19    </>20  );21}2223/**24 * Model text. Everything is a React text node: URLs stay plain text (never links), image syntax25 * is shown as written (never fetched), code blocks are escaped.26 */27export const Markdown = memo(function Markdown({ text, className }: { text: string; className?: string }) {28  const blocks = parseMarkdown(text);29  return (30    <div className={cn('flex min-w-0 flex-col gap-2.5 break-words text-[14px] leading-[1.65] text-text [overflow-wrap:anywhere]', className)} data-testid="md">31      {blocks.map((b, i) => {32        switch (b.t) {33          case 'p':34            return <p key={i} className="whitespace-pre-wrap"><Inl parts={b.inl} /></p>;35          case 'h':36            return (37              <p key={i} role="heading" aria-level={b.level + 2} className={cn('font-semibold tracking-tight text-text', b.level === 1 ? 'text-[16px]' : b.level === 2 ? 'text-[15px]' : 'text-[14px]')}>38                <Inl parts={b.inl} />39              </p>40            );41          case 'ul':42            return (43              <ul key={i} className="flex list-disc flex-col gap-1 pl-5 marker:text-faint">44                {b.items.map((it, j) => <li key={j} className="whitespace-pre-wrap pl-0.5"><Inl parts={it} /></li>)}45              </ul>46            );47          case 'ol':48            return (49              <ol key={i} start={b.start} className="tnum flex list-decimal flex-col gap-1 pl-6 marker:text-muted">50                {b.items.map((it, j) => <li key={j} className="whitespace-pre-wrap pl-0.5"><Inl parts={it} /></li>)}51              </ol>52            );53          case 'quote':54            return <blockquote key={i} className="whitespace-pre-wrap border-l-2 border-line-3 pl-3 text-text-2"><Inl parts={b.inl} /></blockquote>;55          case 'code':56            return (57              <pre key={i} data-lang={b.lang ?? undefined} className="overflow-x-auto rounded-sm border border-line bg-surface-2 px-3 py-2 font-mono text-[12.5px] leading-relaxed text-text">58                <code>{b.text}</code>59              </pre>60            );61          case 'hr':62            return <hr key={i} className="border-line-2" />;63        }64      })}65    </div>66  );67});68
Files main
Menu

Theme