This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import type { Components } from 'react-markdown';
|
||||
|
||||
const components: Components = {
|
||||
// ── Headings ────────────────────────────────────────────────────────────────
|
||||
h1: ({ children }) => (
|
||||
<h1 className="text-2xl font-bold text-white mt-8 mb-4 pb-3 border-b border-border first:mt-0">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2 className="text-lg font-semibold text-white mt-7 mb-3 pb-2 border-b border-border/50 flex items-center gap-2">
|
||||
<span className="inline-block w-1 h-4 rounded-sm shrink-0" style={{ background: 'var(--accent)' }} />
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="text-base font-semibold text-white mt-5 mb-2">{children}</h3>
|
||||
),
|
||||
h4: ({ children }) => (
|
||||
<h4 className="text-sm font-semibold text-zinc-200 mt-4 mb-1 uppercase tracking-wide">{children}</h4>
|
||||
),
|
||||
|
||||
// ── Paragraph ───────────────────────────────────────────────────────────────
|
||||
p: ({ children }) => (
|
||||
<p className="text-zinc-300 leading-7 mb-4 last:mb-0">{children}</p>
|
||||
),
|
||||
|
||||
// ── Links ───────────────────────────────────────────────────────────────────
|
||||
a: ({ href, children }) => (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline underline-offset-2 decoration-[var(--accent)]/50 hover:decoration-[var(--accent)] transition-colors"
|
||||
style={{ color: 'var(--accent)' }}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
|
||||
// ── Strong / Em ─────────────────────────────────────────────────────────────
|
||||
strong: ({ children }) => (
|
||||
<strong className="font-semibold text-white">{children}</strong>
|
||||
),
|
||||
em: ({ children }) => (
|
||||
<em className="italic text-zinc-400">{children}</em>
|
||||
),
|
||||
|
||||
// ── Horizontal rule ─────────────────────────────────────────────────────────
|
||||
hr: () => <hr className="my-6 border-border" />,
|
||||
|
||||
// ── Blockquote ──────────────────────────────────────────────────────────────
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="my-4 pl-4 border-l-2 text-zinc-400 italic bg-white/[0.02] rounded-r-lg py-3 pr-3" style={{ borderLeftColor: 'var(--accent)' }}>
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
|
||||
// ── Lists ───────────────────────────────────────────────────────────────────
|
||||
ul: ({ children }) => (
|
||||
<ul className="my-3 space-y-1.5 list-none pl-4">{children}</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="my-3 space-y-1.5 list-decimal list-inside pl-2 text-zinc-300">{children}</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li className="text-zinc-300 leading-relaxed flex gap-2 items-start">
|
||||
<span className="mt-2.5 w-1.5 h-1.5 rounded-full shrink-0" style={{ background: 'var(--accent)', opacity: 0.7 }} />
|
||||
<span className="flex-1">{children}</span>
|
||||
</li>
|
||||
),
|
||||
|
||||
// ── Inline code ─────────────────────────────────────────────────────────────
|
||||
code: ({ className, children, ...props }) => {
|
||||
const isBlock = Boolean(className);
|
||||
if (isBlock) return <code className={className} {...props}>{children}</code>;
|
||||
return (
|
||||
<code className="font-mono text-[0.8em] bg-white/10 text-[#e2a4ff] px-1.5 py-0.5 rounded-md border border-white/10">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
|
||||
// ── Code block ──────────────────────────────────────────────────────────────
|
||||
pre: ({ children }) => (
|
||||
<pre className="my-4 bg-[#0d0d14] border border-border rounded-xl overflow-x-auto">
|
||||
<div className="flex items-center justify-between px-4 py-2 border-b border-border/50">
|
||||
<div className="flex gap-1.5">
|
||||
<span className="w-3 h-3 rounded-full bg-red-500/40" />
|
||||
<span className="w-3 h-3 rounded-full bg-yellow-500/40" />
|
||||
<span className="w-3 h-3 rounded-full bg-green-500/40" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 text-sm font-mono text-zinc-300 leading-relaxed">
|
||||
{children}
|
||||
</div>
|
||||
</pre>
|
||||
),
|
||||
|
||||
// ── Tables ──────────────────────────────────────────────────────────────────
|
||||
table: ({ children }) => (
|
||||
<div className="my-5 overflow-x-auto rounded-xl border border-border">
|
||||
<table className="w-full text-sm border-collapse">{children}</table>
|
||||
</div>
|
||||
),
|
||||
thead: ({ children }) => (
|
||||
<thead className="bg-white/[0.04] border-b border-border">{children}</thead>
|
||||
),
|
||||
tbody: ({ children }) => (
|
||||
<tbody className="divide-y divide-border">{children}</tbody>
|
||||
),
|
||||
tr: ({ children }) => (
|
||||
<tr className="transition-colors hover:bg-white/[0.03]">{children}</tr>
|
||||
),
|
||||
th: ({ children }) => (
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-muted whitespace-nowrap">
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<td className="px-4 py-3 text-zinc-300 align-top">{children}</td>
|
||||
),
|
||||
};
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export default function MarkdownViewer({ content }: Props) {
|
||||
return (
|
||||
<div className="min-w-0">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import MarkdownViewer from '../components/ui/MarkdownViewer';
|
||||
import {
|
||||
ArrowLeft, ExternalLink, Pencil, Trash2, Upload, FileText,
|
||||
X, Save, FolderOpen, Eye, Code2
|
||||
@@ -244,18 +243,7 @@ export default function ProjectDetail() {
|
||||
</div>
|
||||
<div className="p-6 overflow-auto max-h-[70vh]">
|
||||
{docViewMode === 'preview' ? (
|
||||
<div className="prose prose-invert prose-sm max-w-none
|
||||
prose-headings:text-white prose-headings:font-semibold
|
||||
prose-p:text-zinc-300 prose-p:leading-relaxed
|
||||
prose-a:text-[var(--accent)] prose-a:no-underline hover:prose-a:underline
|
||||
prose-code:bg-white/10 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono
|
||||
prose-pre:bg-surface prose-pre:border prose-pre:border-border
|
||||
prose-blockquote:border-l-[var(--accent)] prose-blockquote:text-muted
|
||||
prose-li:text-zinc-300 prose-strong:text-white
|
||||
prose-table:text-sm prose-th:text-muted prose-td:text-zinc-300
|
||||
prose-hr:border-border">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{docContent}</ReactMarkdown>
|
||||
</div>
|
||||
<MarkdownViewer content={docContent} />
|
||||
) : (
|
||||
<pre className="text-xs text-zinc-300 font-mono whitespace-pre-wrap">{docContent}</pre>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user