change theme and cleanup added favicon
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getRepos } from '@/lib/gitea';
|
||||
import type { GiteaRepo } from '@/lib/types';
|
||||
import { GITEA_URL, GITEA_USERNAME } from '@/lib/config';
|
||||
import { SITE, GITEA_URL, GITEA_USERNAME } from '@/lib/config';
|
||||
|
||||
const LANG_COLOR: Record<string, string> = {
|
||||
python: '#3572A5',
|
||||
@@ -12,7 +12,7 @@ const LANG_COLOR: Record<string, string> = {
|
||||
go: '#00add8',
|
||||
rust: '#dea584',
|
||||
'c++': '#f34b7d',
|
||||
c: '#555',
|
||||
c: '#555555',
|
||||
html: '#e34c26',
|
||||
css: '#563d7c',
|
||||
shell: '#89e051',
|
||||
@@ -21,7 +21,7 @@ const LANG_COLOR: Record<string, string> = {
|
||||
};
|
||||
|
||||
function langColor(l: string | null) {
|
||||
return LANG_COLOR[(l || '').toLowerCase()] || '#555';
|
||||
return LANG_COLOR[(l || '').toLowerCase()] || '#555555';
|
||||
}
|
||||
|
||||
function timeAgo(d: string | undefined | null) {
|
||||
@@ -41,8 +41,6 @@ function timeAgo(d: string | undefined | null) {
|
||||
return `${months}mo ago`;
|
||||
}
|
||||
|
||||
type SortKey = 'recent' | 'stars';
|
||||
|
||||
export default function Projects() {
|
||||
const [repos, setRepos] = useState<GiteaRepo[] | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -52,17 +50,12 @@ export default function Projects() {
|
||||
const [readmeLoading, setReadmeLoading] = useState(false);
|
||||
const [readmeError, setReadmeError] = useState<string | null>(null);
|
||||
|
||||
const [sortBy, setSortBy] = useState<SortKey>('recent');
|
||||
const [languageFilter, setLanguageFilter] = useState<string>('all');
|
||||
|
||||
useEffect(() => {
|
||||
// Get all repos (or a high limit) instead of SITE.repoLimit
|
||||
getRepos(100)
|
||||
getRepos(SITE.repoLimit)
|
||||
.then(setRepos)
|
||||
.catch(e => setError(e instanceof Error ? e.message : String(e)));
|
||||
.catch((e) => setError(e instanceof Error ? e.message : String(e)));
|
||||
}, []);
|
||||
|
||||
// Load README when a repo is opened
|
||||
useEffect(() => {
|
||||
if (!activeRepo) return;
|
||||
setReadmeLoading(true);
|
||||
@@ -70,12 +63,12 @@ export default function Projects() {
|
||||
setReadmeHtml(null);
|
||||
|
||||
fetch(`/api/gitea/readme?repo=${encodeURIComponent(activeRepo.name)}`)
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`README HTTP ${res.status}`);
|
||||
return res.text();
|
||||
})
|
||||
.then(html => setReadmeHtml(html))
|
||||
.catch(e =>
|
||||
.then((html) => setReadmeHtml(html))
|
||||
.catch((e) =>
|
||||
setReadmeError(
|
||||
e instanceof Error ? e.message : 'Could not load README',
|
||||
),
|
||||
@@ -83,259 +76,178 @@ export default function Projects() {
|
||||
.finally(() => setReadmeLoading(false));
|
||||
}, [activeRepo]);
|
||||
|
||||
// Derive list of languages for filter
|
||||
const languages = useMemo(() => {
|
||||
if (!repos) return [];
|
||||
const set = new Set<string>();
|
||||
repos.forEach(r => {
|
||||
if (r.language) set.add(r.language);
|
||||
});
|
||||
return Array.from(set).sort((a, b) => a.localeCompare(b));
|
||||
}, [repos]);
|
||||
|
||||
// Apply sort + language filter
|
||||
const filteredRepos = useMemo(() => {
|
||||
if (!repos) return null;
|
||||
let list = [...repos];
|
||||
|
||||
if (languageFilter !== 'all') {
|
||||
list = list.filter(
|
||||
r => (r.language || '').toLowerCase() === languageFilter.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
list.sort((a, b) => {
|
||||
if (sortBy === 'stars') {
|
||||
return b.stars_count - a.stars_count;
|
||||
}
|
||||
// recent: sort by updated/updated_at desc
|
||||
const ad = new Date((a.updated || (a as any).updated_at) ?? '').getTime();
|
||||
const bd = new Date((b.updated || (b as any).updated_at) ?? '').getTime();
|
||||
return bd - ad;
|
||||
});
|
||||
|
||||
return list;
|
||||
}, [repos, sortBy, languageFilter]);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="projects"
|
||||
className="py-24 px-6"
|
||||
aria-labelledby="projects-heading"
|
||||
>
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<h2
|
||||
id="projects-heading"
|
||||
className="text-[clamp(1.8rem,4vw,2.5rem)] font-black tracking-tight text-white"
|
||||
<div className="max-w-5xl mx-auto min-w-0">
|
||||
<div className="flex items-end justify-between mb-8 gap-4">
|
||||
<h2
|
||||
id="projects-heading"
|
||||
className="text-[clamp(1.8rem,4vw,2.5rem)] font-black tracking-tight text-slate-900"
|
||||
>
|
||||
</h2>
|
||||
|
||||
<a
|
||||
href={`${GITEA_URL}/${GITEA_USERNAME}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hidden sm:inline-flex items-center gap-1.5 text-xs text-slate-500 hover:text-slate-900 transition-colors"
|
||||
>
|
||||
All repos
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
Projects
|
||||
</h2>
|
||||
<p className="text-xs text-white/40 mt-1">
|
||||
Open-source work, sorted by activity or stars.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs">
|
||||
{/* Sort */}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-white/35">Sort:</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSortBy('recent')}
|
||||
className={`px-2.5 py-1 rounded-full border text-xs ${
|
||||
sortBy === 'recent'
|
||||
? 'border-white/40 bg-white/10 text-white'
|
||||
: 'border-white/10 text-white/40 hover:border-white/25 hover:text-white/70'
|
||||
}`}
|
||||
>
|
||||
Most recent
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSortBy('stars')}
|
||||
className={`px-2.5 py-1 rounded-full border text-xs ${
|
||||
sortBy === 'stars'
|
||||
? 'border-white/40 bg-white/10 text-white'
|
||||
: 'border-white/10 text-white/40 hover:border-white/25 hover:text-white/70'
|
||||
}`}
|
||||
>
|
||||
Stars
|
||||
</button>
|
||||
</div>
|
||||
{/* Language filter */}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-white/35">Language:</span>
|
||||
<select
|
||||
value={languageFilter}
|
||||
onChange={e => setLanguageFilter(e.target.value)}
|
||||
className="bg-white/5 border border-white/15 rounded-full px-2.5 py-1 text-xs text-white/80 focus:outline-none focus:ring-1 focus:ring-white/40"
|
||||
>
|
||||
<option value="all">All</option>
|
||||
{languages.map(lang => (
|
||||
<option key={lang} value={lang}>
|
||||
{lang}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* All repos link */}
|
||||
<a
|
||||
href={`${GITEA_URL}/${GITEA_USERNAME}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hidden sm:inline-flex items-center gap-1.5 text-xs text-white/30 hover:text-white/60 transition-colors"
|
||||
>
|
||||
All repos
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M7 17L17 7M7 7h10v10" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<path d="M7 17L17 7M7 7h10v10" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Loading */}
|
||||
{!filteredRepos && !error && (
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div key={i} className="glass rounded-2xl p-5 flex flex-col gap-3">
|
||||
<div className="skel h-4 w-1/2" />
|
||||
<div className="skel h-3 w-5/6" />
|
||||
<div className="skel h-3 w-3/4" />
|
||||
<div className="skel h-3 w-1/3 mt-auto" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="glass rounded-2xl p-10 text-center">
|
||||
<p className="text-white/30 text-sm mb-1">
|
||||
Could not load repositories
|
||||
</p>
|
||||
<p className="text-white/20 text-xs">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Repos list */}
|
||||
{filteredRepos && (
|
||||
<div
|
||||
className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
aria-live="polite"
|
||||
>
|
||||
{filteredRepos.map(repo => {
|
||||
const updated = repo.updated || (repo as any).updated_at;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={repo.id}
|
||||
type="button"
|
||||
onClick={() => setActiveRepo(repo)}
|
||||
className="glass rounded-2xl p-5 flex flex-col gap-4 group transition-all duration-200 text-left
|
||||
hover:border-white/[0.14] hover:shadow-[0_0_0_1px_rgba(0,0,0,0.6),0_16px_48px_rgba(0,0,0,0.6)]
|
||||
hover:-translate-y-[2px] cursor-pointer"
|
||||
aria-label={`${repo.name}: ${repo.description || 'No description'}`}
|
||||
<div className="glass rounded-[28px] p-5 sm:p-7 md:p-8">
|
||||
{!repos && !error && (
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="rounded-2xl border border-slate-200/80 bg-white/70 p-5 flex flex-col gap-3"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div
|
||||
className="p-1.5 rounded-md bg-white/[0.04] border border-white/[0.07]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="rgba(255,255,255,0.4)"
|
||||
strokeWidth="1.8"
|
||||
>
|
||||
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.2c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
</div>
|
||||
<svg
|
||||
className="text-white/20 group-hover:text-white/40 transition-colors"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M7 17L17 7M7 7h10v10" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="skel h-4 w-1/2" />
|
||||
<div className="skel h-3 w-5/6" />
|
||||
<div className="skel h-3 w-3/4" />
|
||||
<div className="skel h-3 w-1/3 mt-auto" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-white mb-1 truncate">
|
||||
{repo.name}
|
||||
</p>
|
||||
<p className="text-xs text-white/35 leading-relaxed line-clamp-2">
|
||||
{repo.description ? (
|
||||
repo.description
|
||||
) : (
|
||||
<span className="italic text-white/20">
|
||||
No description
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{error && (
|
||||
<div className="rounded-2xl border border-slate-200/80 bg-white/80 p-10 text-center">
|
||||
<p className="text-slate-600 text-sm mb-1">
|
||||
Could not load repositories
|
||||
</p>
|
||||
<p className="text-slate-400 text-xs font-mono">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-4 mt-auto">
|
||||
{repo.language && (
|
||||
<span className="flex items-center gap-1.5 text-xs text-white/30">
|
||||
<span
|
||||
className="w-2 h-2 rounded-full flex-shrink-0"
|
||||
style={{ background: langColor(repo.language) }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{repo.language}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="flex items-center gap-1 text-xs text-white/25"
|
||||
aria-label={`${repo.stars_count} stars`}
|
||||
>
|
||||
<svg
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
{repos && (
|
||||
<div
|
||||
className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
aria-live="polite"
|
||||
>
|
||||
{repos.map((repo) => {
|
||||
const updated = repo.updated || (repo as any).updated_at;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={repo.id}
|
||||
type="button"
|
||||
onClick={() => setActiveRepo(repo)}
|
||||
className="rounded-2xl border border-slate-200/80 bg-white/75 p-5 flex flex-col gap-4 text-left transition-all duration-200 hover:border-slate-300 hover:shadow-md hover:-translate-y-[1px] cursor-pointer"
|
||||
aria-label={`${repo.name}: ${repo.description || 'No description'}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div
|
||||
className="p-1.5 rounded-md bg-slate-50 border border-slate-200"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
||||
</svg>
|
||||
{repo.stars_count}
|
||||
</span>
|
||||
<span className="text-xs text-white/20 ml-auto tabular-nums">
|
||||
{timeAgo(updated)}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="rgba(15,23,42,0.5)"
|
||||
strokeWidth="1.8"
|
||||
>
|
||||
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.2c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<svg
|
||||
className="text-slate-400 hover:text-slate-700 transition-colors"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M7 17L17 7M7 7h10v10" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-slate-900 mb-1 truncate">
|
||||
{repo.name}
|
||||
</p>
|
||||
<p className="text-xs text-slate-600 leading-relaxed line-clamp-2">
|
||||
{repo.description ? (
|
||||
repo.description
|
||||
) : (
|
||||
<span className="italic text-slate-400">
|
||||
No description
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 mt-auto">
|
||||
{repo.language && (
|
||||
<span className="flex items-center gap-1.5 text-xs text-slate-600">
|
||||
<span
|
||||
className="w-2 h-2 rounded-full flex-shrink-0"
|
||||
style={{ background: langColor(repo.language) }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{repo.language}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<span
|
||||
className="flex items-center gap-1 text-xs text-slate-500"
|
||||
aria-label={`${repo.stars_count} stars`}
|
||||
>
|
||||
<svg
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
className="text-amber-400"
|
||||
>
|
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
||||
</svg>
|
||||
{repo.stars_count}
|
||||
</span>
|
||||
|
||||
<span className="text-xs text-slate-500 ml-auto tabular-nums">
|
||||
{timeAgo(updated)}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Popout with README + Live + Wiki (unchanged) */}
|
||||
{activeRepo && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 px-4"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/40 px-4"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`${activeRepo.name} details`}
|
||||
>
|
||||
<div className="glass max-w-2xl w-full rounded-2xl p-5 sm:p-6 relative max-h-[80vh] overflow-hidden flex flex-col">
|
||||
<div className="glass max-w-2xl w-full rounded-2xl p-5 sm:p-6 relative max-h-[80vh] overflow-hidden flex flex-col bg-white/95 border border-slate-200 text-slate-900">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -343,7 +255,7 @@ export default function Projects() {
|
||||
setReadmeHtml(null);
|
||||
setReadmeError(null);
|
||||
}}
|
||||
className="absolute top-3 right-3 text-white/40 hover:text-white/80"
|
||||
className="absolute top-3 right-3 text-slate-500 hover:text-slate-900"
|
||||
aria-label="Close"
|
||||
>
|
||||
<svg
|
||||
@@ -359,62 +271,67 @@ export default function Projects() {
|
||||
</button>
|
||||
|
||||
<header className="mb-3 pr-8">
|
||||
<h3 className="text-sm font-semibold text-white mb-1">
|
||||
<h3 className="text-sm font-semibold text-slate-900 mb-1">
|
||||
{activeRepo.name}
|
||||
</h3>
|
||||
<p className="text-xs text-white/40">
|
||||
<p className="text-xs text-slate-600">
|
||||
{activeRepo.description ||
|
||||
'This project does not have a description yet.'}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div className="flex items-center gap-2 mb-4 text-xs">
|
||||
<div className="flex items-center gap-2 mb-4 text-xs flex-wrap">
|
||||
<a
|
||||
href={activeRepo.html_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-white/5 border border-white/15 text-white/80 hover:bg-white/8"
|
||||
className="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-slate-900 text-slate-50 hover:bg-black transition-colors"
|
||||
>
|
||||
View code
|
||||
</a>
|
||||
|
||||
{activeRepo.website &&
|
||||
activeRepo.website.trim().length > 0 && (
|
||||
<a
|
||||
href={activeRepo.website}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-white/5 border border-white/15 text-white/80 hover:bg-white/8"
|
||||
className="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-white border border-slate-200 text-slate-900 hover:bg-slate-50 transition-colors"
|
||||
>
|
||||
View live
|
||||
</a>
|
||||
)}
|
||||
|
||||
<a
|
||||
href={`https://git.williammarch.xyz/${GITEA_USERNAME}/${activeRepo.name}/wiki`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-white/5 border border-white/15 text-white/80 hover:bg-white/8"
|
||||
className="inline-flex items-center gap-1 px-3 py-1 rounded-full bg-white border border-slate-200 text-slate-900 hover:bg-slate-50 transition-colors"
|
||||
>
|
||||
Wiki
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto pr-1 text-xs leading-relaxed text-white/70">
|
||||
<div className="flex-1 overflow-y-auto pr-1 text-xs leading-relaxed text-slate-800">
|
||||
{readmeLoading && (
|
||||
<p className="text-white/40">Loading README…</p>
|
||||
<p className="text-slate-500">Loading README…</p>
|
||||
)}
|
||||
|
||||
{readmeError && (
|
||||
<p className="text-white/40">
|
||||
<p className="text-slate-500">
|
||||
Could not load README: {readmeError}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{readmeHtml && (
|
||||
<div
|
||||
className="prose prose-invert prose-sm max-w-none"
|
||||
className="prose prose-sm max-w-none text-slate-900"
|
||||
dangerouslySetInnerHTML={{ __html: readmeHtml }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!readmeLoading && !readmeError && !readmeHtml && (
|
||||
<p className="text-white/40">
|
||||
<p className="text-slate-500">
|
||||
No README content available for this repository.
|
||||
</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user