11---
22import BaseLayout from ' ../layouts/BaseLayout.astro'
33
4- const projects = [
4+ async function fetchMetaTags(url : string ) {
5+ try {
6+ const response = await fetch (url )
7+ const html = await response .text ()
8+
9+ const titleMatch = html .match (/ <meta property="og:title" content="([^ "] * )"/ ) ||
10+ html .match (/ <title[^ >] * >([^ <] + )<\/ title>/ )
11+ const descriptionMatch = html .match (/ <meta property="og:description" content="([^ "] * )"/ ) ||
12+ html .match (/ <meta name="description" content="([^ "] * )"/ )
13+ const imageMatch = html .match (/ <meta property="og:image" content="([^ "] * )"/ )
14+
15+ return {
16+ title: titleMatch ? titleMatch [1 ] : ' ' ,
17+ description: descriptionMatch ? descriptionMatch [1 ] : ' ' ,
18+ image: imageMatch ? imageMatch [1 ] : ' '
19+ }
20+ } catch (error ) {
21+ return { title: ' ' , description: ' ' , image: ' ' }
22+ }
23+ }
24+
25+ const projectsData = [
526 {
627 title: " shadecn" ,
728 description: " SVG color customizer that transforms unDraw illustrations to match your shadcn themes" ,
829 tech: [" React" , " TypeScript" , " shadcn/ui" , " Tailwind CSS" ],
930 github: " https://github.com/johanwulf/shadecn" ,
10- live: " https://wulf.gg/shadecn"
31+ live: " https://wulf.gg/shadecn" ,
32+ richMediaUrl: " https://wulf.gg/shadecn"
1133 },
1234 {
1335 title: " wulf.gg" ,
1436 description: " This website - built with Astro, React, TypeScript, and MDX" ,
1537 tech: [" Astro" , " TypeScript" , " Tailwind CSS" , " MDX" ],
16- github: " https://github.com/johanwulf/johanwulf.github.io"
38+ github: " https://github.com/johanwulf/johanwulf.github.io" ,
39+ richMediaUrl: " https://wulf.gg"
40+ },
41+ {
42+ title: " replace-comment" ,
43+ description: " GitHub Action to replace comments in PRs and issues with updated content" ,
44+ tech: [" TypeScript" , " GitHub Actions" ],
45+ github: " https://github.com/johanwulf/replace-comment" ,
46+ live: " https://github.com/marketplace/actions/replace-comment" ,
47+ richMediaUrl: " https://github.com/marketplace/actions/replace-comment"
1748 },
1849 {
1950 title: " .dotfiles" ,
@@ -22,25 +53,63 @@ const projects = [
2253 github: " https://github.com/johanwulf/.dotfiles"
2354 },
2455]
56+
57+ const projects = await Promise .all (
58+ projectsData .map (async (project ) => {
59+ if (project .richMediaUrl ) {
60+ const metaTags = await fetchMetaTags (project .richMediaUrl )
61+ return {
62+ ... project ,
63+ richMedia: {
64+ url: project .richMediaUrl ,
65+ ... metaTags
66+ }
67+ }
68+ }
69+ return project
70+ })
71+ )
2572---
2673
2774<BaseLayout title =" Projects - Johan Wulf" >
2875 <h1 class =" text-2xl font-normal mb-8" >Projects</h1 >
2976 <div class =" space-y-8" >
3077 { projects .map ((project ) => (
3178 <article class = " pb-8 border-b border-surface-0 last:border-0" >
32- <h2 class = " text-lg text-text mb-2" >
33- <span class = " text-green" >◆</span > { project .title }
34- </h2 >
35- <p class = " text-overlay-0 text-sm mb-3" >
36- { project .description }
37- </p >
38- <div class = " flex flex-wrap gap-2 mb-3" >
39- { project .tech .map (tech => (
40- <span class = " text-xs px-2 py-1 bg-surface-0 text-overlay-1 rounded" >
41- { tech }
42- </span >
43- ))}
79+ <div class = " flex items-start gap-4" >
80+ <div class = " flex-1" >
81+ <h2 class = " text-lg text-text mb-2" >
82+ <span class = " text-green" >◆</span > { project .title }
83+ </h2 >
84+ <p class = " text-overlay-0 text-sm mb-3" >
85+ { project .description }
86+ </p >
87+ <div class = " flex flex-wrap gap-2 mb-3" >
88+ { project .tech .map (tech => (
89+ <span class = " text-xs px-2 py-1 bg-surface-0 text-overlay-1 rounded" >
90+ { tech }
91+ </span >
92+ ))}
93+ </div >
94+ </div >
95+ { project .richMedia && (
96+ <div class = " w-20 h-20 bg-surface-0 flex-shrink-0 flex items-center justify-center rounded-lg overflow-hidden" >
97+ { project .richMedia .image ? (
98+ <img
99+ src = { project .richMedia .image }
100+ alt = " "
101+ class = " w-full h-full object-cover"
102+ loading = " lazy"
103+ />
104+ ) : (
105+ <svg class = " w-6 h-6 text-overlay-1" fill = " none" stroke = " currentColor" viewBox = " 0 0 24 24" stroke-width = " 2" >
106+ <rect width = " 18" height = " 18" x = " 3" y = " 3" rx = " 2" ry = " 2" />
107+ <circle cx = " 9" cy = " 9" r = " 2" />
108+ <path d = " m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
109+ </svg >
110+ )}
111+ </div >
112+ )}
44113 </div >
45114 <div class = " text-sm text-overlay-0 space-x-4" >
46115 { project .live && (
0 commit comments