Upgraded the projects view. Looks and acts MUCH nicer
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m21s
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m21s
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atridotdad",
|
||||
"type": "module",
|
||||
"version": "4.0.0",
|
||||
"version": "4.1.0",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { config } from "../config";
|
||||
import { fetchGiteaInfoFromUrl, formatRelativeTime } from "../utils/gitea";
|
||||
import type { Project } from "../types";
|
||||
|
||||
Astro.response.headers.set(
|
||||
"Cache-Control",
|
||||
"public, max-age=300, s-maxage=300, stale-while-revalidate=60",
|
||||
);
|
||||
|
||||
function isGiteaDomain(url: string): boolean {
|
||||
if (!config.siteConfig.giteaDomains) return true;
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return config.siteConfig.giteaDomains.some(
|
||||
(domain) => urlObj.origin === new URL(domain).origin,
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const projectsWithGiteaInfo = await Promise.all(
|
||||
config.projects.map(async (project) => {
|
||||
if (
|
||||
project.gitLink &&
|
||||
!project.giteaInfo &&
|
||||
isGiteaDomain(project.gitLink)
|
||||
) {
|
||||
const giteaInfo = await fetchGiteaInfoFromUrl(project.gitLink);
|
||||
if (giteaInfo) {
|
||||
return { ...project, giteaInfo } as Project;
|
||||
}
|
||||
}
|
||||
return project;
|
||||
}),
|
||||
);
|
||||
|
||||
const sortedProjects = projectsWithGiteaInfo.sort((a, b) => {
|
||||
const aTime = a.giteaInfo?.updatedAt
|
||||
? new Date(a.giteaInfo.updatedAt).getTime()
|
||||
: 0;
|
||||
const bTime = b.giteaInfo?.updatedAt
|
||||
? new Date(b.giteaInfo.updatedAt).getTime()
|
||||
: 0;
|
||||
return bTime - aTime;
|
||||
});
|
||||
---
|
||||
|
||||
<ul class="list bg-base-200 rounded-box max-w-4xl mx-auto">
|
||||
{
|
||||
sortedProjects.map((project) => (
|
||||
<li class="list-row hover:bg-base-300 transition-colors">
|
||||
{/* Project icon/avatar */}
|
||||
<div class="list-col-grow-0">
|
||||
<div class="w-12 h-12 rounded-lg bg-accent flex items-center justify-center">
|
||||
<Icon name="mdi:code-braces" class="w-6 h-6 text-accent-content" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div class="list-col-grow">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1">
|
||||
<h3 class="font-bold text-lg">{project.name}</h3>
|
||||
{project.giteaInfo?.updatedAt && (
|
||||
<span class="text-xs opacity-60">
|
||||
{formatRelativeTime(project.giteaInfo.updatedAt)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p class="text-sm opacity-80 mt-1">{project.description}</p>
|
||||
|
||||
{/* Languages & Topics */}
|
||||
<div class="flex flex-wrap gap-1 mt-2">
|
||||
{project.giteaInfo?.languages?.slice(0, 3).map((lang: string) => (
|
||||
<span class="badge badge-sm badge-primary">{lang}</span>
|
||||
))}
|
||||
{project.giteaInfo?.topics?.slice(0, 4).map((topic: string) => (
|
||||
<span class="badge badge-sm badge-outline">{topic}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action buttons */}
|
||||
<div class="list-col-grow-0 flex flex-wrap gap-1 justify-end">
|
||||
{project.webLink && (
|
||||
<a
|
||||
href={project.webLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-square btn-ghost text-primary hover:bg-primary hover:text-primary-content transition-all"
|
||||
aria-label={`Visit ${project.name} website`}
|
||||
>
|
||||
<Icon name="mdi:web" class="w-5 h-5" />
|
||||
</a>
|
||||
)}
|
||||
{project.gitLink && (
|
||||
<a
|
||||
href={project.gitLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-square btn-ghost text-secondary hover:bg-secondary hover:text-secondary-content transition-all"
|
||||
aria-label={`View ${project.name} source`}
|
||||
>
|
||||
<Icon name="simple-icons:gitea" class="w-5 h-5" />
|
||||
</a>
|
||||
)}
|
||||
{project.iosLink && (
|
||||
<a
|
||||
href={project.iosLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-square btn-ghost text-accent hover:bg-accent hover:text-accent-content transition-all"
|
||||
aria-label={`${project.name} on iOS`}
|
||||
>
|
||||
<Icon name="mdi:apple" class="w-5 h-5" />
|
||||
</a>
|
||||
)}
|
||||
{project.androidLink && (
|
||||
<a
|
||||
href={project.androidLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-square btn-ghost text-success hover:bg-success hover:text-success-content transition-all"
|
||||
aria-label={`${project.name} on Android`}
|
||||
>
|
||||
<Icon name="mdi:google-play" class="w-5 h-5" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
{
|
||||
sortedProjects.length === 0 && (
|
||||
<p class="text-center text-gray-500 mt-12">
|
||||
No projects available yet. Check back soon!
|
||||
</p>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="flex justify-center items-center py-12">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
@@ -206,6 +206,7 @@ export const config: Config = {
|
||||
id: "atrodotdad",
|
||||
name: "Personal Site",
|
||||
description: "My personal website built with Astro.",
|
||||
webLink: "https://atri.dad",
|
||||
gitLink: "https://git.atri.dad/atridad/atridotdad",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,21 +1,189 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import ProjectsIsland from "../components/ProjectsIsland.astro";
|
||||
import ProjectsLoader from "../components/ProjectsLoader.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { config } from "../config";
|
||||
import { fetchGiteaInfoFromUrl, formatRelativeTime } from "../utils/gitea";
|
||||
import type { Project } from "../types";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
Astro.response.headers.set(
|
||||
"Cache-Control",
|
||||
"public, max-age=300, s-maxage=300, stale-while-revalidate=60",
|
||||
);
|
||||
|
||||
function isGiteaDomain(url: string): boolean {
|
||||
if (!config.siteConfig.giteaDomains) return true;
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return config.siteConfig.giteaDomains.some(
|
||||
(domain) => urlObj.origin === new URL(domain).origin,
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const projectsWithGiteaInfo = await Promise.all(
|
||||
config.projects.map(async (project) => {
|
||||
if (
|
||||
project.gitLink &&
|
||||
!project.giteaInfo &&
|
||||
isGiteaDomain(project.gitLink)
|
||||
) {
|
||||
const giteaInfo = await fetchGiteaInfoFromUrl(project.gitLink);
|
||||
if (giteaInfo) {
|
||||
return { ...project, giteaInfo } as Project;
|
||||
}
|
||||
}
|
||||
return project;
|
||||
}),
|
||||
);
|
||||
|
||||
const sortedProjects = projectsWithGiteaInfo.sort((a, b) => {
|
||||
const aTime = a.giteaInfo?.updatedAt
|
||||
? new Date(a.giteaInfo.updatedAt).getTime()
|
||||
: 0;
|
||||
const bTime = b.giteaInfo?.updatedAt
|
||||
? new Date(b.giteaInfo.updatedAt).getTime()
|
||||
: 0;
|
||||
return bTime - aTime;
|
||||
});
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div class="w-full p-4 sm:p-8">
|
||||
<div class="w-full max-w-3xl mx-auto p-4 sm:p-8">
|
||||
<h1
|
||||
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
|
||||
>
|
||||
Projects
|
||||
</h1>
|
||||
|
||||
<ProjectsIsland server:defer>
|
||||
<ProjectsLoader slot="fallback" />
|
||||
</ProjectsIsland>
|
||||
<ul
|
||||
class="flex flex-col bg-base-100 rounded-box shadow-md border border-base-content/20 divide-y divide-base-content/20"
|
||||
>
|
||||
{
|
||||
sortedProjects.map((project) => (
|
||||
<li class="flex items-center hover:bg-base-200/50 transition-colors p-4 group relative rounded-none first:rounded-t-box last:rounded-b-box">
|
||||
{/* Project icon/avatar */}
|
||||
<div class="flex-none z-10 mr-4">
|
||||
{project.giteaInfo?.avatarUrl ? (
|
||||
<img
|
||||
src={project.giteaInfo.avatarUrl}
|
||||
alt={`${project.name} avatar`}
|
||||
class="w-12 h-12 rounded-lg object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div class="w-12 h-12 rounded-lg bg-accent flex items-center justify-center">
|
||||
<Icon
|
||||
name="mdi:code-braces"
|
||||
class="w-6 h-6 text-accent-content"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div class="flex-grow flex flex-col justify-center gap-1 z-10 pointer-events-none">
|
||||
<div class="flex flex-col sm:flex-row sm:items-baseline gap-1 sm:gap-2">
|
||||
<h3 class="font-bold text-lg sm:text-xl text-primary group-hover:text-accent transition-colors">
|
||||
{project.name}
|
||||
</h3>
|
||||
{project.giteaInfo?.updatedAt && (
|
||||
<span class="text-xs opacity-60">
|
||||
{formatRelativeTime(
|
||||
project.giteaInfo.updatedAt,
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p class="text-sm opacity-80 leading-relaxed">
|
||||
{project.description}
|
||||
</p>
|
||||
|
||||
{/* Languages & Topics */}
|
||||
<div class="flex flex-wrap gap-1 mt-1">
|
||||
{project.giteaInfo?.languages
|
||||
?.slice(0, 3)
|
||||
.map((lang: string) => (
|
||||
<span class="badge badge-xs badge-primary">
|
||||
{lang}
|
||||
</span>
|
||||
))}
|
||||
{project.giteaInfo?.topics
|
||||
?.slice(0, 4)
|
||||
.map((topic: string) => (
|
||||
<span class="badge badge-xs badge-outline opacity-70">
|
||||
{topic}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action buttons */}
|
||||
<div class="flex-none flex flex-wrap gap-1 justify-end ml-4 z-20">
|
||||
{project.webLink && (
|
||||
<a
|
||||
href={project.webLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-square btn-ghost text-primary hover:bg-primary hover:text-primary-content transition-all"
|
||||
aria-label={`Visit ${project.name} website`}
|
||||
>
|
||||
<Icon name="mdi:web" class="w-5 h-5" />
|
||||
</a>
|
||||
)}
|
||||
{project.gitLink && (
|
||||
<a
|
||||
href={project.gitLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-square btn-ghost text-success hover:bg-success hover:text-success-content transition-all"
|
||||
aria-label={`View ${project.name} source`}
|
||||
>
|
||||
<Icon
|
||||
name="simple-icons:gitea"
|
||||
class="w-5 h-5"
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
{project.iosLink && (
|
||||
<a
|
||||
href={project.iosLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-square btn-ghost text-accent hover:bg-accent hover:text-accent-content transition-all"
|
||||
aria-label={`${project.name} on iOS`}
|
||||
>
|
||||
<Icon name="mdi:apple" class="w-5 h-5" />
|
||||
</a>
|
||||
)}
|
||||
{project.androidLink && (
|
||||
<a
|
||||
href={project.androidLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-sm btn-square btn-ghost text-success hover:bg-success hover:text-success-content transition-all"
|
||||
aria-label={`${project.name} on Android`}
|
||||
>
|
||||
<Icon
|
||||
name="mdi:google-play"
|
||||
class="w-5 h-5"
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
{
|
||||
sortedProjects.length === 0 && (
|
||||
<p class="text-center text-gray-500 mt-12">
|
||||
No projects available yet. Check back soon!
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface GiteaRepoInfo {
|
||||
size: number;
|
||||
defaultBranch: string;
|
||||
topics: string[];
|
||||
avatarUrl?: string;
|
||||
}
|
||||
|
||||
export interface GiteaConfig {
|
||||
@@ -74,6 +75,7 @@ export async function fetchGiteaRepoInfo(
|
||||
size: data.size || 0,
|
||||
defaultBranch: data.default_branch || "main",
|
||||
topics: Array.isArray(data.topics) ? data.topics : [],
|
||||
avatarUrl: data.avatar_url,
|
||||
};
|
||||
} catch (error) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user