Skip to content

Commit 4a565fc

Browse files
committed
fix trailing slash issues for blog posts
1 parent 3e15332 commit 4a565fc

6 files changed

Lines changed: 191 additions & 86 deletions

File tree

src/layout/BaseLayout.astro

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,42 @@ const defaultSchema: WithContext<Thing> = {
133133
].forEach((link) => link.remove())
134134
);
135135
</script>
136+
137+
<!-- Preline UI JavaScript -->
138+
<script>
139+
import "preline/dist/preline.js";
140+
document.addEventListener("astro:page-load", () => {
141+
// @ts-ignore
142+
window.HSStaticMethods.autoInit();
143+
144+
// Custom hover functionality for mega menu dropdowns
145+
document
146+
.querySelectorAll(".hs-dropdown")
147+
.forEach((dropdown) => {
148+
if (window.innerWidth >= 1280) {
149+
// xl breakpoint
150+
const button = dropdown.querySelector(
151+
".hs-dropdown-toggle"
152+
);
153+
const menu =
154+
dropdown.querySelector(".hs-dropdown-menu");
155+
156+
// Only add hover functionality to desktop view
157+
dropdown.addEventListener("mouseenter", () => {
158+
button?.setAttribute("aria-expanded", "true");
159+
menu?.classList.remove("hidden");
160+
menu?.classList.add("opacity-100");
161+
});
162+
163+
dropdown.addEventListener("mouseleave", () => {
164+
button?.setAttribute("aria-expanded", "false");
165+
menu?.classList.add("hidden");
166+
menu?.classList.remove("opacity-100");
167+
});
168+
}
169+
});
170+
});
171+
</script>
136172
</head>
137173
<body
138174
class="bg-off-white flex min-h-screen flex-col selection:bg-teal-400 selection:text-slate-700"

src/pages/about/contact.astro

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import BaseLayout from "../../layout/BaseLayout.astro";
33
import Icon from "../../components/ui/icons/Icon.astro";
4+
import { getAssetPath } from "../../utils/utils";
45
// import { Image } from "astro:assets"; // Only needed if images are in src/assets and need optimization
56
67
const pageTitle = "Contact Us | CVD-Net";
@@ -65,7 +66,13 @@ const keyContacts = [
6566
{" "}
6667
{/* Increased bottom margin */}
6768
<img
68-
src={contact.photoUrl}
69+
src={
70+
contact.photoUrl
71+
? getAssetPath(contact.photoUrl)
72+
: getAssetPath(
73+
"/images/team/team-placeholder.jpeg"
74+
)
75+
}
6976
alt={`Photo of ${contact.name}`}
7077
class="size-20 flex-shrink-0 rounded-full border-2 border-slate-100 object-cover"
7178
width="80"

src/pages/about/project-team.astro

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import BaseLayout from "../../layout/BaseLayout.astro";
33
import teamData from "../../data/team.json";
4+
import { getAssetPath } from "../../utils/utils";
45
56
// Group team members by section
67
const groupedTeam = teamData.reduce(
@@ -29,18 +30,18 @@ const pageDescription =
2930
---
3031

3132
<style>
32-
/* Team member card styles */
33-
.team-role {
34-
color: var(--color-teal-600);
35-
margin-bottom: 0.5rem;
36-
font-size: 0.875rem;
37-
font-weight: 500;
38-
}
33+
/* Team member card styles */
34+
.team-role {
35+
color: var(--color-teal-600);
36+
margin-bottom: 0.5rem;
37+
font-size: 0.875rem;
38+
font-weight: 500;
39+
}
3940

40-
.team-info {
41-
color: #4B5563; /* Equivalent to slate-600 */
42-
font-size: 0.875rem;
43-
}
41+
.team-info {
42+
color: #4b5563; /* Equivalent to slate-600 */
43+
font-size: 0.875rem;
44+
}
4445
</style>
4546

4647
<BaseLayout seo={{ title: pageTitle, description: pageDescription }}>
@@ -78,8 +79,13 @@ const pageDescription =
7879
<div class="mx-auto h-40 w-40 overflow-hidden rounded-full">
7980
<img
8081
src={
81-
member.photoUrl ||
82-
"/images/team/team-placeholder.jpeg"
82+
member.photoUrl
83+
? getAssetPath(
84+
member.photoUrl
85+
)
86+
: getAssetPath(
87+
"/images/team/team-placeholder.jpeg"
88+
)
8389
}
8490
alt={`Photo of ${member.name}`}
8591
class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
@@ -153,4 +159,4 @@ const pageDescription =
153159
)
154160
}
155161
</div>
156-
</BaseLayout>
162+
</BaseLayout>

src/pages/about/work-packages.astro

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import BaseLayout from "../../layout/BaseLayout.astro";
33
import teamData from "../../data/team.json";
44
import Icon from "../../components/ui/icons/Icon.astro";
5+
import { getAssetPath } from "../../utils/utils";
56
67
const pageTitle = "Work Packages | CVD-Net";
78
const pageDescription =
@@ -195,8 +196,13 @@ const workPackages = [
195196
<div class="flex items-center gap-3">
196197
<img
197198
src={
198-
teamMember?.photoUrl ||
199-
"/images/team/team-placeholder.jpeg"
199+
teamMember?.photoUrl
200+
? getAssetPath(
201+
teamMember.photoUrl
202+
)
203+
: getAssetPath(
204+
"/images/team/team-placeholder.jpeg"
205+
)
200206
}
201207
alt={`Photo of ${leadName}`}
202208
class="size-12 rounded-full border-2 border-slate-200 object-cover"

src/pages/blog/index.astro

Lines changed: 93 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,104 @@
11
---
2-
import { getCollection } from 'astro:content';
3-
import BaseLayout from '../../layout/BaseLayout.astro';
4-
import { formatDate } from '../../utils/utils';
5-
import { Image } from 'astro:assets';
2+
import { getCollection } from "astro:content";
3+
import BaseLayout from "../../layout/BaseLayout.astro";
4+
import { formatDate } from "../../utils/utils";
5+
import { Image } from "astro:assets";
66
import Icon from "../../components/ui/icons/Icon.astro";
77
88
const seo = {
9-
title: 'Blog | CVD-Net',
10-
description: 'Read the latest updates and insights from the CVD-Net project team.',
9+
title: "Blog | CVD-Net",
10+
description:
11+
"Read the latest updates and insights from the CVD-Net project team.",
1112
};
1213
1314
// Fetch blog posts, sort by publish date (newest first)
14-
const posts = (await getCollection('blog')).sort(
15-
(a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
15+
const posts = (await getCollection("blog")).sort(
16+
(a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
1617
);
1718
---
19+
1820
<BaseLayout seo={seo}>
19-
<div class="container mx-auto max-w-6xl px-4 py-12">
20-
<div class="mx-auto max-w-4xl text-center mb-12">
21-
<h1 class="mb-4 text-4xl font-bold text-slate-800 lg:text-5xl">Blog</h1>
22-
<p class="mb-8 text-lg text-slate-600 max-w-3xl mx-auto">
23-
Stay up-to-date with the latest news, updates, and insights from the CVD-Net project team.
24-
</p>
25-
</div>
21+
<div class="container mx-auto max-w-6xl px-4 py-12">
22+
<div class="mx-auto mb-12 max-w-4xl text-center">
23+
<h1 class="mb-4 text-4xl font-bold text-slate-800 lg:text-5xl">
24+
Blog
25+
</h1>
26+
<p class="mx-auto mb-8 max-w-3xl text-lg text-slate-600">
27+
Stay up-to-date with the latest news, updates, and insights from
28+
the CVD-Net project team.
29+
</p>
30+
</div>
31+
32+
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
33+
{
34+
posts.map((post) => (
35+
<div class="group flex h-full flex-col overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm transition-all hover:shadow-md">
36+
<div class="relative aspect-video overflow-hidden">
37+
{post.data.coverImage ? (
38+
<Image
39+
src={post.data.coverImage}
40+
alt={post.data.title}
41+
width={400}
42+
height={225}
43+
class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
44+
/>
45+
) : (
46+
<div class="flex h-full w-full items-center justify-center bg-slate-100">
47+
<Icon
48+
name="ph:file-text-duotone"
49+
size="64px"
50+
class="text-slate-400"
51+
/>
52+
</div>
53+
)}
54+
</div>
55+
<div class="flex flex-1 flex-col p-6">
56+
<div class="mb-3 flex items-center gap-2">
57+
<span class="text-sm text-slate-500">
58+
{formatDate(post.data.publishDate)}
59+
</span>
60+
{post.data.author && (
61+
<>
62+
<span class="text-slate-300">•</span>
63+
<span class="text-sm text-slate-500">
64+
{post.data.author}
65+
</span>
66+
</>
67+
)}
68+
</div>
69+
70+
<h2 class="mb-2 text-2xl font-semibold text-slate-800">
71+
{post.data.title}
72+
</h2>
73+
<p class="mb-4 flex-1 text-slate-600">
74+
{post.data.description}
75+
</p>
76+
77+
<a
78+
href={`${import.meta.env.BASE_URL}/blog/${post.slug}`}
79+
class="inline-flex items-center justify-center rounded-md bg-teal-600 px-4 py-2 text-sm font-medium text-white shadow transition-colors hover:bg-teal-700 focus:ring-2 focus:ring-teal-500/40 focus:outline-none"
80+
>
81+
Read more
82+
</a>
83+
</div>
84+
</div>
85+
))
86+
}
87+
</div>
2688

27-
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
28-
{posts.map((post) => (
29-
<div class="group flex flex-col h-full overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm transition-all hover:shadow-md">
30-
<div class="relative aspect-video overflow-hidden">
31-
{post.data.coverImage ? (
32-
<Image
33-
src={post.data.coverImage}
34-
alt={post.data.title}
35-
width={400}
36-
height={225}
37-
class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
38-
/>
39-
) : (
40-
<div class="flex h-full w-full items-center justify-center bg-slate-100">
41-
<Icon name="ph:file-text-duotone" size="64px" class="text-slate-400" />
42-
</div>
43-
)}
44-
</div>
45-
<div class="flex flex-1 flex-col p-6">
46-
<div class="flex items-center gap-2 mb-3">
47-
<span class="text-sm text-slate-500">
48-
{formatDate(post.data.publishDate)}
49-
</span>
50-
{post.data.author && (
51-
<>
52-
<span class="text-slate-300">•</span>
53-
<span class="text-sm text-slate-500">{post.data.author}</span>
54-
</>
55-
)}
56-
</div>
57-
58-
<h2 class="mb-2 text-2xl font-semibold text-slate-800">{post.data.title}</h2>
59-
<p class="mb-4 flex-1 text-slate-600">{post.data.description}</p>
60-
61-
<a
62-
href={`/blog/${post.slug}/`}
63-
class="inline-flex items-center justify-center rounded-md bg-teal-600 px-4 py-2 text-sm font-medium text-white shadow transition-colors hover:bg-teal-700 focus:outline-none focus:ring-2 focus:ring-teal-500/40"
64-
>
65-
Read more
66-
</a>
67-
</div>
68-
</div>
69-
))}
70-
</div>
71-
72-
{posts.length === 0 && (
73-
<div class="text-center py-16">
74-
<Icon name="ph:newspaper-duotone" size="64px" class="text-slate-300 mx-auto mb-4" />
75-
<p class="text-xl text-slate-600">No blog posts found yet. Check back soon!</p>
76-
</div>
77-
)}
78-
</div>
79-
</BaseLayout>
89+
{
90+
posts.length === 0 && (
91+
<div class="py-16 text-center">
92+
<Icon
93+
name="ph:newspaper-duotone"
94+
size="64px"
95+
class="mx-auto mb-4 text-slate-300"
96+
/>
97+
<p class="text-xl text-slate-600">
98+
No blog posts found yet. Check back soon!
99+
</p>
100+
</div>
101+
)
102+
}
103+
</div>
104+
</BaseLayout>

src/utils/utils.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,29 @@ function formatDate(date: Date): string {
99
return new Date(date).toLocaleDateString(undefined, options);
1010
}
1111

12-
export { formatDate };
12+
/**
13+
* Resolves asset paths by prefixing with BASE_URL when needed
14+
* This handles image paths and other static assets that need the base URL in production
15+
*/
16+
function getAssetPath(path?: string): string {
17+
// Handle undefined or empty strings
18+
if (!path) {
19+
return "";
20+
}
21+
22+
// If the path already starts with http/https, return as is
23+
if (path.startsWith("http")) {
24+
return path;
25+
}
26+
27+
// For paths that start with /, prefix with BASE_URL
28+
// Otherwise return the path as is (for relative paths)
29+
if (path.startsWith("/")) {
30+
return `${import.meta.env.BASE_URL}${path}`;
31+
}
32+
33+
// For other cases, return as is
34+
return path;
35+
}
36+
37+
export { formatDate, getAssetPath };

0 commit comments

Comments
 (0)