improve markdown rendering (#213)

* feat: add support for GFM and highlight in markdown rendering

* feat: enhance markdown rendering by splitting YAML frontmatter and body

* feat: improve markdown styling for light and dark themes

* feat: enhance frontmatter parsing and display in SkillDetail component
This commit is contained in:
SHUBHAM PATEL
2026-03-07 14:38:21 +05:30
committed by GitHub
parent 61ec71c5c7
commit 1b167e4ca1
4 changed files with 634 additions and 7 deletions

View File

@@ -1,9 +1,108 @@
@import "tailwindcss";
@import 'github-markdown-css/github-markdown-light.css';
@import 'github-markdown-css/github-markdown.css';
@import 'highlight.js/styles/github.css' (prefers-color-scheme: light);
@import 'highlight.js/styles/github-dark.css' (prefers-color-scheme: dark);
.markdown-body {
color: var(--color-slate-800);
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
color: var(--color-slate-900);
}
.markdown-body hr,
.markdown-body table tr,
.markdown-body table th,
.markdown-body table td,
.markdown-body blockquote {
border-color: var(--color-slate-200);
}
.markdown-body code {
background-color: var(--color-slate-100);
color: var(--color-slate-900);
}
.markdown-body pre {
background-color: var(--color-slate-100);
border: 1px solid var(--color-slate-200);
}
.markdown-body pre code {
background-color: transparent;
}
@media (prefers-color-scheme: dark) {
@import 'github-markdown-css/github-markdown-dark.css';
.markdown-body {
color-scheme: dark;
color: var(--color-slate-200);
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
color: var(--color-slate-100);
}
.markdown-body hr,
.markdown-body table tr,
.markdown-body table th,
.markdown-body table td,
.markdown-body blockquote {
border-color: var(--color-slate-700);
}
.markdown-body code {
background-color: var(--color-slate-800);
color: var(--color-slate-100);
}
.markdown-body pre {
background-color: var(--color-slate-900);
border-color: var(--color-slate-700);
}
.markdown-body pre code,
.markdown-body pre .hljs,
.markdown-body pre .hljs-code,
.markdown-body pre .hljs-subst,
.markdown-body pre .hljs-punctuation,
.markdown-body pre .hljs-operator {
color: var(--color-slate-100) !important;
}
.markdown-body pre .hljs-comment,
.markdown-body pre .hljs-quote {
color: var(--color-slate-200) !important;
}
.markdown-body pre .hljs-keyword,
.markdown-body pre .hljs-selector-tag,
.markdown-body pre .hljs-built_in,
.markdown-body pre .hljs-section,
.markdown-body pre .hljs-title,
.markdown-body pre .hljs-name {
color: var(--color-sky-300) !important;
}
.markdown-body pre .hljs-string,
.markdown-body pre .hljs-literal,
.markdown-body pre .hljs-number,
.markdown-body pre .hljs-attr,
.markdown-body pre .hljs-template-tag,
.markdown-body pre .hljs-template-variable {
color: var(--color-emerald-300) !important;
}
}
:root {
@@ -11,5 +110,14 @@
}
body {
@apply bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-50 overflow-x-hidden;
background-color: var(--color-slate-50);
color: var(--color-slate-900);
overflow-x: hidden;
}
@media (prefers-color-scheme: dark) {
body {
background-color: var(--color-slate-950);
color: var(--color-slate-50);
}
}

View File

@@ -3,10 +3,48 @@ import { useParams, Link } from 'react-router-dom';
import { ArrowLeft, Copy, Check, FileCode, AlertTriangle, Loader2 } from 'lucide-react';
import { SkillStarButton } from '../components/SkillStarButton';
import { useSkills } from '../context/SkillContext';
import remarkGfm from 'remark-gfm';
import rehypeHighlight from 'rehype-highlight';
import rehypeRaw from 'rehype-raw';
// Lazy load heavy markdown component
const Markdown = lazy(() => import('react-markdown'));
/** Split YAML frontmatter (--- ... ---) and markdown body */
function splitFrontmatter(md: string): { frontmatter: string; body: string } {
const match = md.match(/^(---[\s\S]*?---)\s*\n?/);
if (!match) {
return { frontmatter: '', body: md };
}
return {
frontmatter: match[1],
body: md.slice(match[0].length),
};
}
function parseFrontmatterRows(frontmatter: string): Array<{ key: string; value: string }> {
return frontmatter
.split('\n')
.map(line => line.trim())
.filter(line => line && line !== '---')
.map(line => {
const separatorIndex = line.indexOf(':');
if (separatorIndex === -1) {
return null;
}
const key = line.slice(0, separatorIndex).trim();
const rawValue = line.slice(separatorIndex + 1).trim();
const value = rawValue.replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1');
return key ? { key, value } : null;
})
.filter((row): row is { key: string; value: string } => row !== null);
}
interface RouteParams {
id: string;
[key: string]: string | undefined;
@@ -24,6 +62,8 @@ export function SkillDetail(): React.ReactElement {
const skill = useMemo(() => skills.find(s => s.id === id), [skills, id]);
const starCount = useMemo(() => (id ? stars[id] || 0 : 0), [stars, id]);
const { frontmatter, body: markdownBody } = useMemo(() => splitFrontmatter(content), [content]);
const frontmatterRows = useMemo(() => parseFrontmatterRows(frontmatter), [frontmatter]);
useEffect(() => {
if (contextLoading || !skill) return;
@@ -180,9 +220,50 @@ export function SkillDetail(): React.ReactElement {
<div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm overflow-hidden">
<div className="p-6 sm:p-8">
<div className="prose prose-slate dark:prose-invert max-w-none">
{frontmatterRows.length > 0 && (
<div className="mb-6">
<p className="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400 mb-2">
Skill Metadata
</p>
<div className="overflow-x-auto rounded-lg border border-slate-200 dark:border-slate-700">
<table className="min-w-full text-sm text-left border-collapse">
<thead>
<tr className="bg-slate-50 dark:bg-slate-950">
{frontmatterRows.map(({ key }) => (
<th
key={key}
className="px-4 py-2 border-b border-slate-200 dark:border-slate-700 font-semibold text-slate-800 dark:text-slate-100"
>
{key}
</th>
))}
</tr>
</thead>
<tbody>
<tr className="bg-white dark:bg-slate-900">
{frontmatterRows.map(({ key, value }) => (
<td
key={key}
className="px-4 py-2 border-t border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-200 align-top"
>
{value}
</td>
))}
</tr>
</tbody>
</table>
</div>
</div>
)}
<div className="markdown-body" style={{ backgroundColor: 'transparent' }}>
<Suspense fallback={<div className="h-24 animate-pulse bg-slate-100 dark:bg-slate-800 rounded-lg"></div>}>
<Markdown>{content}</Markdown>
<Markdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeHighlight, rehypeRaw]}
>
{markdownBody}
</Markdown>
</Suspense>
</div>
</div>