IDK why I didn't do this... using server islands :')
This commit is contained in:
@@ -13,6 +13,7 @@ export default defineConfig({
|
|||||||
"/feed": "/rss.xml",
|
"/feed": "/rss.xml",
|
||||||
},
|
},
|
||||||
output: "server",
|
output: "server",
|
||||||
|
prefetch: true,
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ export default function NavigationBar({ currentPath }: NavigationBarProps) {
|
|||||||
class={`tooltip tooltip-top min-h-[44px] min-w-[44px] inline-flex items-center justify-center ${isActive ? "menu-active" : ""}`}
|
class={`tooltip tooltip-top min-h-[44px] min-w-[44px] inline-flex items-center justify-center ${isActive ? "menu-active" : ""}`}
|
||||||
aria-label={item.tooltip}
|
aria-label={item.tooltip}
|
||||||
data-tip={item.tooltip}
|
data-tip={item.tooltip}
|
||||||
|
data-astro-prefetch="hover"
|
||||||
>
|
>
|
||||||
<Icon size={18} class="sm:w-5 sm:h-5" />
|
<Icon size={18} class="sm:w-5 sm:h-5" />
|
||||||
<span class="sr-only">{item.name}</span>
|
<span class="sr-only">{item.name}</span>
|
||||||
|
|||||||
59
src/components/ProjectsIsland.astro
Normal file
59
src/components/ProjectsIsland.astro
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
import ProjectCard from "./ProjectCard.astro";
|
||||||
|
import { config } from "../config";
|
||||||
|
import { fetchGiteaInfoFromUrl } 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.link && !project.giteaInfo && isGiteaDomain(project.link)) {
|
||||||
|
const giteaInfo = await fetchGiteaInfoFromUrl(project.link);
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
---
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
|
||||||
|
>
|
||||||
|
{sortedProjects.map((project) => <ProjectCard project={project} />)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
sortedProjects.length === 0 && (
|
||||||
|
<p class="text-center text-gray-500 mt-12">
|
||||||
|
No projects available yet. Check back soon!
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
46
src/components/ProjectsLoader.astro
Normal file
46
src/components/ProjectsLoader.astro
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
const skeletonCount = 6;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
Array.from({ length: skeletonCount }).map((_, i) => (
|
||||||
|
<div class="card bg-accent text-accent-content w-full max-w-sm shrink shadow-lg">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="skeleton h-8 w-3/4 mx-auto mb-4" />
|
||||||
|
|
||||||
|
<div class="space-y-2 mb-4">
|
||||||
|
<div class="skeleton h-4 w-full" />
|
||||||
|
<div class="skeleton h-4 w-5/6 mx-auto" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider my-2 before:bg-accent-content/30 after:bg-accent-content/30" />
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-3 justify-center">
|
||||||
|
<div class="skeleton h-5 w-24" />
|
||||||
|
<div class="skeleton h-5 w-24" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2 justify-center mt-2">
|
||||||
|
<div class="skeleton h-5 w-16 rounded-full" />
|
||||||
|
<div class="skeleton h-5 w-20 rounded-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider my-2 before:bg-accent-content/30 after:bg-accent-content/30" />
|
||||||
|
|
||||||
|
<div class="flex gap-2 flex-wrap justify-center">
|
||||||
|
<div class="skeleton h-5 w-16 rounded-full" />
|
||||||
|
<div class="skeleton h-5 w-20 rounded-full" />
|
||||||
|
<div class="skeleton h-5 w-14 rounded-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-actions justify-center gap-2 mt-4">
|
||||||
|
<div class="skeleton h-8 w-20" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -1,43 +1,9 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
import ProjectCard from "../components/ProjectCard.astro";
|
import ProjectsIsland from "../components/ProjectsIsland.astro";
|
||||||
import { config } from "../config";
|
import ProjectsLoader from "../components/ProjectsLoader.astro";
|
||||||
import { fetchGiteaInfoFromUrl } from "../utils/gitea";
|
|
||||||
import type { Project } from "../types";
|
|
||||||
|
|
||||||
function isGiteaDomain(url: string): boolean {
|
export const prerender = false;
|
||||||
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.link && !project.giteaInfo && isGiteaDomain(project.link)) {
|
|
||||||
const giteaInfo = await fetchGiteaInfoFromUrl(project.link);
|
|
||||||
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>
|
||||||
@@ -47,18 +13,9 @@ const sortedProjects = projectsWithGiteaInfo.sort((a, b) => {
|
|||||||
>
|
>
|
||||||
Projects
|
Projects
|
||||||
</h1>
|
</h1>
|
||||||
<div
|
|
||||||
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
|
|
||||||
>
|
|
||||||
{sortedProjects.map((project) => <ProjectCard project={project} />)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
<ProjectsIsland server:defer>
|
||||||
sortedProjects.length === 0 && (
|
<ProjectsLoader slot="fallback" />
|
||||||
<p class="text-center text-gray-500 mt-12">
|
</ProjectsIsland>
|
||||||
No projects available yet. Check back soon!
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
Reference in New Issue
Block a user