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 { useState, useEffect, useRef } from 'react';
|
||||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import MarkdownViewer from '../components/ui/MarkdownViewer';
|
||||||
import remarkGfm from 'remark-gfm';
|
|
||||||
import {
|
import {
|
||||||
ArrowLeft, ExternalLink, Pencil, Trash2, Upload, FileText,
|
ArrowLeft, ExternalLink, Pencil, Trash2, Upload, FileText,
|
||||||
X, Save, FolderOpen, Eye, Code2
|
X, Save, FolderOpen, Eye, Code2
|
||||||
@@ -244,18 +243,7 @@ export default function ProjectDetail() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-6 overflow-auto max-h-[70vh]">
|
<div className="p-6 overflow-auto max-h-[70vh]">
|
||||||
{docViewMode === 'preview' ? (
|
{docViewMode === 'preview' ? (
|
||||||
<div className="prose prose-invert prose-sm max-w-none
|
<MarkdownViewer content={docContent} />
|
||||||
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>
|
|
||||||
) : (
|
) : (
|
||||||
<pre className="text-xs text-zinc-300 font-mono whitespace-pre-wrap">{docContent}</pre>
|
<pre className="text-xs text-zinc-300 font-mono whitespace-pre-wrap">{docContent}</pre>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user