import { Fragment, useMemo, useState, type ReactNode } from 'react';
import {
highlightCodeLine,
parseMarkdown,
type MarkdownInline,
} from './markdown.js';
export interface MarkdownContentProps {
readonly text: string;
readonly className?: string;
}
function InlineContent({ content }: { readonly content: readonly MarkdownInline[] }) {
const nodes: ReactNode[] = [];
content.forEach((token, index) => {
const key = `${token.type}-${index}`;
const value = token.value.split('\n').map((line, lineIndex, lines) => (
{line}
{lineIndex < lines.length - 1 ?
: null}
));
if (token.type === 'strong') nodes.push({value});
else if (token.type === 'emphasis') nodes.push({value});
else if (token.type === 'strike') nodes.push({value});
else if (token.type === 'code') nodes.push({token.value});
else if (token.type === 'link') nodes.push(
{value},
);
else nodes.push({value});
});
return <>{nodes}>;
}
export function CodeBlock({ code, language = '', closed = true }: { readonly code: string; readonly language?: string; readonly closed?: boolean }) {
const [copied, setCopied] = useState(false);
const [wrapped, setWrapped] = useState(false);
const [expanded, setExpanded] = useState(false);
const lines = useMemo(() => code.split('\n'), [code]);
const isLong = lines.length > 18 || code.length > 1800;
const visibleLines = isLong && !expanded ? lines.slice(0, 18) : lines;
const copy = async () => {
try {
await navigator.clipboard.writeText(code);
setCopied(true);
window.setTimeout(() => setCopied(false), 1600);
} catch {
setCopied(false);
}
};
return (
{language || 'plain text'}
{!closed ? 生成中 : null}
{visibleLines.map((line, lineIndex) => (
{lineIndex + 1}
{highlightCodeLine(line, language).map((token, tokenIndex) => (
{token.value}
))}
{'\n'}
))}
{isLong ? (
) : null}
);
}
export function MarkdownContent({ text, className }: MarkdownContentProps) {
const blocks = useMemo(() => parseMarkdown(text), [text]);
return (
{blocks.map((block, index) => {
if (block.type === 'heading') {
const Heading = `h${block.level}` as keyof React.JSX.IntrinsicElements;
return
;
}
if (block.type === 'paragraph') return
;
if (block.type === 'quote') return
;
if (block.type === 'rule') return
;
if (block.type === 'code') return
;
if (block.type === 'list') {
const List = block.ordered ? 'ol' : 'ul';
return (
{block.items.map((item, itemIndex) => (
{item.checked === undefined ? null : }
))}
);
}
if (block.type === 'table') {
return (
{block.header.map((cell, cellIndex) => | )}
{block.rows.map((row, rowIndex) => {row.map((cell, cellIndex) => | )}
)}
);
}
return null;
})}
);
}