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",
|
"name": "atridotdad",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro build",
|
"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",
|
id: "atrodotdad",
|
||||||
name: "Personal Site",
|
name: "Personal Site",
|
||||||
description: "My personal website built with Astro.",
|
description: "My personal website built with Astro.",
|
||||||
|
webLink: "https://atri.dad",
|
||||||
gitLink: "https://git.atri.dad/atridad/atridotdad",
|
gitLink: "https://git.atri.dad/atridad/atridotdad",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,21 +1,189 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
import ProjectsIsland from "../components/ProjectsIsland.astro";
|
import { Icon } from "astro-icon/components";
|
||||||
import ProjectsLoader from "../components/ProjectsLoader.astro";
|
import { config } from "../config";
|
||||||
|
import { fetchGiteaInfoFromUrl, formatRelativeTime } from "../utils/gitea";
|
||||||
|
import type { Project } from "../types";
|
||||||
|
|
||||||
export const prerender = false;
|
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>
|
<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
|
<h1
|
||||||
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
|
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
|
||||||
>
|
>
|
||||||
Projects
|
Projects
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<ProjectsIsland server:defer>
|
<ul
|
||||||
<ProjectsLoader slot="fallback" />
|
class="flex flex-col bg-base-100 rounded-box shadow-md border border-base-content/20 divide-y divide-base-content/20"
|
||||||
</ProjectsIsland>
|
>
|
||||||
|
{
|
||||||
|
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>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export interface GiteaRepoInfo {
|
|||||||
size: number;
|
size: number;
|
||||||
defaultBranch: string;
|
defaultBranch: string;
|
||||||
topics: string[];
|
topics: string[];
|
||||||
|
avatarUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GiteaConfig {
|
export interface GiteaConfig {
|
||||||
@@ -74,6 +75,7 @@ export async function fetchGiteaRepoInfo(
|
|||||||
size: data.size || 0,
|
size: data.size || 0,
|
||||||
defaultBranch: data.default_branch || "main",
|
defaultBranch: data.default_branch || "main",
|
||||||
topics: Array.isArray(data.topics) ? data.topics : [],
|
topics: Array.isArray(data.topics) ? data.topics : [],
|
||||||
|
avatarUrl: data.avatar_url,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user