Files
brachnha-insight/src/components/features/draft/DraftContent.tsx
Max 9b79856827 feat(ui): implement 'Twilight Velvet' dark theme and fix visibility issues
- Add 'Twilight Velvet' color palette to globals.css with OKLCH values
- Update SettingsPage headers, cards, and dialogs to use semantic theme variables
- Update HistoryCard, HistoryFeed, and DraftContent to support dark mode
- Update ProviderSelector and ProviderList to use custom card background (#2A2A3D)
- Add ThemeToggle component with improved visibility
- Ensure consistent use of 'bg-card', 'text-foreground', and 'text-muted-foreground'
2026-01-27 11:03:55 +07:00

134 lines
4.7 KiB
TypeScript

'use client';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeHighlight from 'rehype-highlight';
import rehypeRaw from 'rehype-raw';
import type { Draft } from '@/lib/db/draft-service';
interface DraftContentProps {
draft: Draft;
}
/**
* DraftContent Component - Markdown renderer for draft content
*
* Renders draft content with:
* - Merriweather serif font for "published" feel
* - Markdown parsing with GFM support
* - Syntax highlighting for code blocks
* - Tag display
* - Generous whitespace for comfortable reading
*/
export function DraftContent({ draft }: DraftContentProps) {
// Strip the first heading from content if it matches the title
const processedContent = (() => {
const lines = draft.content.split('\n');
const firstLine = lines[0]?.trim() || '';
// Check if first line is a heading that matches the title
const headingMatch = firstLine.match(/^#+\s*(.+)$/);
if (headingMatch) {
const headingText = headingMatch[1].trim();
if (headingText.toLowerCase() === draft.title.toLowerCase()) {
// Remove the first line (and any immediate blank lines after it)
let startIndex = 1;
while (startIndex < lines.length && lines[startIndex].trim() === '') {
startIndex++;
}
return lines.slice(startIndex).join('\n');
}
}
return draft.content;
})();
return (
<article className="draft-content px-4 sm:px-6 py-6 bg-card">
{/* Title - using Merriweather serif font */}
<h2 className="draft-title text-2xl sm:text-3xl font-bold text-foreground mb-6 font-serif leading-tight">
{draft.title}
</h2>
{/* Body content - Markdown with prose styling */}
<div className="draft-body prose prose-slate dark:prose-invert max-w-none font-serif">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeHighlight, rehypeRaw]}
components={{
// Custom heading styles
h1: ({ node, ...props }) => (
<h1 className="text-2xl font-bold text-foreground mt-8 mb-4 first:mt-0" {...props} />
),
h2: ({ node, ...props }) => (
<h2 className="text-xl font-bold text-foreground mt-6 mb-3" {...props} />
),
h3: ({ node, ...props }) => (
<h3 className="text-lg font-semibold text-foreground mt-5 mb-2" {...props} />
),
// Paragraph styling
p: ({ node, ...props }) => (
<p className="text-base leading-relaxed text-muted-foreground mb-4" {...props} />
),
// Code blocks
code: ({ node, inline, className, children, ...props }: any) => {
if (inline) {
return (
<code
className="px-1.5 py-0.5 bg-muted text-foreground rounded text-sm font-mono"
{...props}
>
{children}
</code>
);
}
return (
<code
className={`block bg-muted text-foreground p-4 rounded-lg text-sm font-mono overflow-x-auto ${className || ''}`}
{...props}
>
{children}
</code>
);
},
// Pre tags
pre: ({ node, ...props }) => (
<pre className="bg-muted p-4 rounded-lg overflow-x-auto mb-4" {...props} />
),
// Links
a: ({ node, ...props }) => (
<a className="text-primary hover:underline" {...props} />
),
// Lists
ul: ({ node, ...props }) => (
<ul className="list-disc list-inside mb-4 text-muted-foreground space-y-1" {...props} />
),
ol: ({ node, ...props }) => (
<ol className="list-decimal list-inside mb-4 text-muted-foreground space-y-1" {...props} />
),
// Blockquotes
blockquote: ({ node, ...props }) => (
<blockquote className="border-l-4 border-muted-foreground/30 pl-4 italic text-muted-foreground my-4" {...props} />
),
}}
>
{processedContent}
</ReactMarkdown>
</div>
{/* Tags section */}
{draft.tags && draft.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mt-6 pt-4 border-t border-border">
{draft.tags.map((tag) => (
<span
key={tag}
className="tag-chip px-3 py-1 bg-secondary text-secondary-foreground rounded-full text-sm font-sans"
>
#{tag}
</span>
))}
</div>
)}
</article>
);
}