@@ -3,6 +3,7 @@ import Layout from '../../layouts/Layout.astro';
33import Nav from ' ../../components/Nav.astro' ;
44import Footer from ' ../../components/Footer.astro' ;
55import { projects } from ' ../../data/projects' ;
6+ import { marked } from ' marked' ;
67
78type Release = {
89 tag_name: string ;
@@ -12,37 +13,45 @@ type Release = {
1213};
1314
1415export async function getStaticPaths() {
16+ async function fetchReleases(slug : string ): Promise <Release []> {
17+ try {
18+ const res = await fetch (` https://api.github.com/repos/rustkit-ai/${slug }/releases ` );
19+ const data: Release [] = await res .json ();
20+ return data
21+ .filter ((r ) => r .published_at )
22+ .sort ((a , b ) => new Date (b .published_at ! ).getTime () - new Date (a .published_at ! ).getTime ());
23+ } catch {
24+ return [];
25+ }
26+ }
27+
28+ async function fetchReadme(slug : string ): Promise <string > {
29+ try {
30+ const res = await fetch (` https://raw.githubusercontent.com/rustkit-ai/${slug }/main/README.md ` );
31+ if (! res .ok ) return ' ' ;
32+ const md = await res .text ();
33+ return marked (md ) as string ;
34+ } catch {
35+ return ' ' ;
36+ }
37+ }
38+
1539 const results = await Promise .all (
1640 projects .map (async (p ) => {
17- let releases: Release [] = [];
18- if (p .hasReleases ) {
19- try {
20- const res = await fetch (` https://api.github.com/repos/rustkit-ai/${p .slug }/releases ` );
21- const data: Release [] = await res .json ();
22- releases = data
23- .filter ((r ) => r .published_at )
24- .sort ((a , b ) => new Date (b .published_at ! ).getTime () - new Date (a .published_at ! ).getTime ());
25- } catch {}
26- }
27- return { params: { slug: p .slug }, props: { project: p , releases } };
41+ const [releases, readme] = await Promise .all ([
42+ p .hasReleases ? fetchReleases (p .slug ) : Promise .resolve ([]),
43+ fetchReadme (p .slug ),
44+ ]);
45+ return { params: { slug: p .slug }, props: { project: p , releases , readme } };
2846 })
2947 );
3048 return results ;
3149}
3250
3351const { slug } = Astro .params ;
3452const project = Astro .props .project ?? projects .find ((p ) => p .slug === slug )! ;
35-
36- let releases: Release [] = Astro .props .releases ?? [];
37- if (! Astro .props .releases && project .hasReleases ) {
38- try {
39- const res = await fetch (` https://api.github.com/repos/rustkit-ai/${project .slug }/releases ` );
40- const data: Release [] = await res .json ();
41- releases = data
42- .filter ((r ) => r .published_at )
43- .sort ((a , b ) => new Date (b .published_at ! ).getTime () - new Date (a .published_at ! ).getTime ());
44- } catch {}
45- }
53+ const releases: Release [] = Astro .props .releases ?? [];
54+ const readme: string = Astro .props .readme ?? ' ' ;
4655
4756function formatDate(iso : string ) {
4857 return new Date (iso ).toLocaleDateString (' en-US' , {
@@ -107,14 +116,17 @@ function formatDate(iso: string) {
107116 </div >
108117 </div >
109118
110- <!-- Description -->
119+ <!-- README / Description -->
111120 <div class =" section" >
112- <div class =" section-label" >About</div >
113- <div class =" long-desc" >
114- { project .longDescription .split (' \n\n ' ).map ((para ) => (
115- <p >{ para } </p >
116- ))}
117- </div >
121+ { readme ? (
122+ <div class = " readme-content" set :html = { readme } />
123+ ) : (
124+ <div class = " long-desc" >
125+ { project .longDescription .split (' \n\n ' ).map ((para ) => (
126+ <p >{ para } </p >
127+ ))}
128+ </div >
129+ )}
118130 </div >
119131
120132 <!-- Releases -->
@@ -357,6 +369,122 @@ function formatDate(iso: string) {
357369
358370 .release-item:hover .release-arrow { transform: translateX(3px); color: var(--rust); }
359371
372+ /* README rendered markdown */
373+ .readme-content :global(h1) { display: none; }
374+
375+ .readme-content :global(h2) {
376+ font-family: 'Space Mono', monospace;
377+ font-size: 1.1rem;
378+ font-weight: 700;
379+ letter-spacing: -0.02em;
380+ color: var(--text);
381+ margin: 2.5rem 0 1rem;
382+ padding-bottom: 0.5rem;
383+ border-bottom: 1px solid var(--border);
384+ }
385+
386+ .readme-content :global(h3) {
387+ font-family: 'IBM Plex Mono', monospace;
388+ font-size: 0.9rem;
389+ font-weight: 600;
390+ color: var(--text);
391+ margin: 1.75rem 0 0.75rem;
392+ }
393+
394+ .readme-content :global(p) {
395+ font-size: 0.95rem;
396+ color: var(--text-muted);
397+ line-height: 1.8;
398+ margin-bottom: 1rem;
399+ }
400+
401+ .readme-content :global(p:has(img)) { display: none; }
402+
403+ .readme-content :global(a) {
404+ color: var(--rust);
405+ text-decoration: none;
406+ }
407+
408+ .readme-content :global(a:hover) { text-decoration: underline; }
409+
410+ .readme-content :global(code) {
411+ font-family: 'IBM Plex Mono', monospace;
412+ font-size: 0.82rem;
413+ background: var(--bg-2);
414+ border: 1px solid var(--border);
415+ border-radius: 3px;
416+ padding: 0.1em 0.4em;
417+ color: var(--text);
418+ }
419+
420+ .readme-content :global(pre) {
421+ background: var(--bg-2);
422+ border: 1px solid var(--border);
423+ border-radius: 6px;
424+ padding: 1.25rem 1.5rem;
425+ overflow-x: auto;
426+ margin: 1.25rem 0;
427+ }
428+
429+ .readme-content :global(pre code) {
430+ background: none;
431+ border: none;
432+ padding: 0;
433+ font-size: 0.82rem;
434+ color: var(--text-muted);
435+ }
436+
437+ .readme-content :global(ul),
438+ .readme-content :global(ol) {
439+ padding-left: 1.5rem;
440+ margin-bottom: 1rem;
441+ }
442+
443+ .readme-content :global(li) {
444+ font-size: 0.95rem;
445+ color: var(--text-muted);
446+ line-height: 1.8;
447+ margin-bottom: 0.25rem;
448+ }
449+
450+ .readme-content :global(table) {
451+ width: 100%;
452+ border-collapse: collapse;
453+ font-family: 'IBM Plex Mono', monospace;
454+ font-size: 0.82rem;
455+ margin: 1.25rem 0;
456+ }
457+
458+ .readme-content :global(th) {
459+ text-align: left;
460+ color: var(--text-dim);
461+ font-size: 0.72rem;
462+ text-transform: uppercase;
463+ letter-spacing: 0.08em;
464+ border-bottom: 1px solid var(--border);
465+ padding: 0.5rem 1rem;
466+ }
467+
468+ .readme-content :global(td) {
469+ color: var(--text-muted);
470+ border-bottom: 1px solid var(--border);
471+ padding: 0.6rem 1rem;
472+ }
473+
474+ .readme-content :global(blockquote) {
475+ border-left: 3px solid var(--rust-dim);
476+ padding-left: 1rem;
477+ margin: 1rem 0;
478+ color: var(--text-dim);
479+ font-size: 0.9rem;
480+ }
481+
482+ .readme-content :global(hr) {
483+ border: none;
484+ border-top: 1px solid var(--border);
485+ margin: 2rem 0;
486+ }
487+
360488 @media (max-width: 640px) {
361489 .project-page { padding: 6rem 1.25rem 3rem; }
362490 .release-item { grid-template-columns: auto 1fr auto; }
0 commit comments