feat: add support for multiple links in projects

Projects now support multiple links (source code, demo, etc) each with a label. Migrated all projects to use the new schema.
This commit is contained in:
2026-05-14 12:49:57 -04:00
parent 962efd0e75
commit 23e5e17be5
8 changed files with 54 additions and 19 deletions
+4 -1
View File
@@ -24,7 +24,10 @@ const projects = defineCollection({
description: z.string(), description: z.string(),
tags: z.array(z.string()), tags: z.array(z.string()),
image: image(), image: image(),
link: z.url().optional(), links: z.array(z.object({
label: z.string(),
url: z.url()
})).optional(),
order: z.number().optional(), order: z.number().optional(),
startDate: z.coerce.date().optional(), startDate: z.coerce.date().optional(),
endDate: z.coerce.date().optional(), endDate: z.coerce.date().optional(),
+3 -1
View File
@@ -3,7 +3,9 @@ name: 'Bullseye'
description: 'Bullseye is a self-hosted offline-first personal utilities app with habit tracking, tasks, and notes.' description: 'Bullseye is a self-hosted offline-first personal utilities app with habit tracking, tasks, and notes.'
tags: ['typescript', 'svelte', 'sqlite'] tags: ['typescript', 'svelte', 'sqlite']
image: '../../../public/static/bullseye.webp' image: '../../../public/static/bullseye.webp'
link: 'https://git.jaroszew.ski/patrick/bullseye-app' links:
- label: 'Source Code'
url: 'https://git.jaroszew.ski/patrick/bullseye-app'
order: 6 order: 6
startDate: '2025-03-30' startDate: '2025-03-30'
--- ---
+3 -1
View File
@@ -3,7 +3,9 @@ name: 'Chitai'
description: 'Chitai is a self-hosted eBook management application' description: 'Chitai is a self-hosted eBook management application'
tags: ['python', 'typescript', 'postgresql', 'svelte', 'open-source'] tags: ['python', 'typescript', 'postgresql', 'svelte', 'open-source']
image: '../../../public/static/chitai.webp' image: '../../../public/static/chitai.webp'
link: 'https://git.jaroszew.ski/patrick/chitai' links:
- label: 'Source Code'
url: 'https://git.jaroszew.ski/patrick/chitai'
order: 2 order: 2
startDate: '2025-03-30' startDate: '2025-03-30'
--- ---
+1 -1
View File
@@ -8,7 +8,7 @@ startDate: '2025-09-23'
--- ---
<div class="flex flex-wrap gap-2 my-0! [&>img]:my-0! [&>img]:mt-2!" > <div class="flex flex-wrap gap-2 my-0! [&>img]:my-0! [&>img]:mt-2!" >
<img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python" /> <img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=FFD43B" alt="Python" />
<img src="https://img.shields.io/badge/PostgreSQL-336791?style=for-the-badge&logo=postgresql&logoColor=white" alt="PostgreSQL" /> <img src="https://img.shields.io/badge/PostgreSQL-336791?style=for-the-badge&logo=postgresql&logoColor=white" alt="PostgreSQL" />
<img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" /> <img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
<img src="https://img.shields.io/badge/Svelte-FF3E00?style=for-the-badge&logo=svelte&logoColor=white" alt="Svelte" /> <img src="https://img.shields.io/badge/Svelte-FF3E00?style=for-the-badge&logo=svelte&logoColor=white" alt="Svelte" />
+3 -1
View File
@@ -3,7 +3,9 @@ name: 'Marketing Site'
description: 'A simple marketing site built for a small business.' description: 'A simple marketing site built for a small business.'
tags: ['astro', 'tailwindcss'] tags: ['astro', 'tailwindcss']
image: '../../../public/static/druksubli.webp' image: '../../../public/static/druksubli.webp'
link: 'https://druksubli.pl' links:
- label: 'Live Site'
url: 'https://druksubli.pl'
order: 5 order: 5
startDate: '2025-04-16' startDate: '2025-04-16'
endDate: '2025-05-29' endDate: '2025-05-29'
+3 -1
View File
@@ -3,7 +3,9 @@ name: 'Homelab'
description: 'My homelab is where I tinker and learn new technologies.' description: 'My homelab is where I tinker and learn new technologies.'
tags: ['proxmox', 'networking', 'hardware'] tags: ['proxmox', 'networking', 'hardware']
image: '../../../public/static/homelab.webp' image: '../../../public/static/homelab.webp'
link: 'https://git.jaroszew.ski/ansible' links:
- label: 'Ansible Configs'
url: 'https://git.jaroszew.ski/ansible'
order: 1 order: 1
startDate: '2023-05-15' startDate: '2023-05-15'
--- ---
+5 -1
View File
@@ -3,7 +3,11 @@ name: 'Personal Portfolio'
description: 'Personal Portfolio is a personal website that showcases my work and projects. It is built with Astro.js and styled with Tailwind CSS, providing a fast and responsive user experience. The website features a clean design, easy navigation, and a focus on showcasing my skills and projects.' description: 'Personal Portfolio is a personal website that showcases my work and projects. It is built with Astro.js and styled with Tailwind CSS, providing a fast and responsive user experience. The website features a clean design, easy navigation, and a focus on showcasing my skills and projects.'
tags: ['astro', 'tailwindcss', 'typescript', 'svelte'] tags: ['astro', 'tailwindcss', 'typescript', 'svelte']
image: '../../../public/static/portfolio.webp' image: '../../../public/static/portfolio.webp'
link: 'https://patrick.jaroszew.ski' links:
- label: 'Live Site'
url: 'https://patrick.jaroszew.ski'
- label: 'Source Code'
url: 'https://git.jaroszew.ski/patrick/portfolio'
order: 4 order: 4
startDate: '2026-03-10' startDate: '2026-03-10'
--- ---
+22 -2
View File
@@ -126,18 +126,28 @@ const currentUrl = Astro.url;
<p>{project.data.description}</p> <p>{project.data.description}</p>
</div> </div>
{project.data.link && ( {(project.data.links || project.data.link) && (
<> <>
<hr class="my-4 border-t" /> <hr class="my-4 border-t" />
<div class="flex flex-col gap-2 text-sm text-muted-foreground"> <div class="flex flex-col gap-2 text-sm text-muted-foreground">
<h3 class="text-base font-semibold">Project Links</h3> <h3 class="text-base font-semibold">Project Links</h3>
<ul class="list-disc pl-4"> <ul class="list-disc pl-4">
{project.data.links ? (
project.data.links.map((link) => (
<li>
<a href={link.url} target="_blank" rel="noopener noreferrer" aria-label={link.label}>
{link.label}
</a>
</li>
))
) : project.data.link ? (
<li> <li>
<a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link"> <a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link">
{project.data.link} {project.data.link}
</a> </a>
</li> </li>
) : null}
</ul> </ul>
</div> </div>
</> </>
@@ -155,18 +165,28 @@ const currentUrl = Astro.url;
<p>{project.data.description}</p> <p>{project.data.description}</p>
</div> </div>
{project.data.link && ( {(project.data.links || project.data.link) && (
<> <>
<hr class="my-4 border-t" /> <hr class="my-4 border-t" />
<div class="flex flex-col gap-2 text-sm text-muted-foreground"> <div class="flex flex-col gap-2 text-sm text-muted-foreground">
<h3 class="text-base font-semibold">Project Links</h3> <h3 class="text-base font-semibold">Project Links</h3>
<ul class="list-disc pl-4"> <ul class="list-disc pl-4">
{project.data.links ? (
project.data.links.map((link) => (
<li>
<a href={link.url} target="_blank" rel="noopener noreferrer" aria-label={link.label}>
{link.label}
</a>
</li>
))
) : project.data.link ? (
<li> <li>
<a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link"> <a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link">
{project.data.link} {project.data.link}
</a> </a>
</li> </li>
) : null}
</ul> </ul>
</div> </div>
</> </>