Skip to content

Commit c42a874

Browse files
author
Michał Gryczka
committed
tags filtering in blog + ekseem in customers navbar
1 parent 0c29660 commit c42a874

File tree

6 files changed

+126
-4
lines changed

6 files changed

+126
-4
lines changed

src/content/blog/eskemm-numerique-multi-tenant-remote-access.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ companySegment: "Research & Education / Hosted Services"
1010
companyWebsite: "https://www.eskemm-numerique.fr/"
1111

1212
image: "/images/blog/eskemm_case_study.png"
13-
tags: ["ISO27001", "HDS", "Compliance"]
13+
tags: ["ISO27001", "HDS", "Compliance", "case study"]
1414
---
1515

1616
![Secure multi-tenant remote access built by French MSP for research institutions with Defguard](/images/blog/eskemm_case_study.png)

src/content/blog/prusa-vpn-scaling-with-defguard.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ companySegment: "Desktop and industrial 3D printing"
1010
companyWebsite: "https://www.prusa3d.com"
1111

1212
image: "/images/blog/prusa.png"
13-
tags: ["prusa", "enterprise vpn", "self-hosted", "wireguard", "mfa", "identity management"]
13+
tags: ["prusa", "enterprise vpn", "self-hosted", "wireguard", "mfa", "identity management", "case study"]
1414
---
1515

1616
![How Prusa Secured Global VPN Access with Defguard](/images/blog/prusa.png)

src/content/blog/self-hosted-vpn-private-cloud-acquinox-defguard.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ companySegment: "Private Equity"
1010
companyWebsite: "https://acquinox.capital"
1111

1212
image: "/images/blog/acquinox.png"
13-
tags: ["private cloud", "self-hosted", "data sovereignty", "sso", "finance", "gdpr"]
13+
tags: ["private cloud", "data sovereignty", "gdpr", "case study"]
1414
---
1515

1616
![Private Equity firm builds secure private cloud with Defguard](/images/blog/acquinox.png)

src/data/blog-filter-tags.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Tags shown in the blog index filter, in this order.
3+
* Add, remove, or reorder here; the blog index reflects this list.
4+
* Tags with no matching posts will show an empty list when selected.
5+
*/
6+
export const blogFilterTags: string[] = [
7+
"case study",
8+
"release",
9+
"mfa",
10+
11+
12+
13+
];

src/data/nav.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
{
5151
"display": "Acquinox: Private Cloud & Data Sovereignty",
5252
"url": "/blog/self-hosted-vpn-private-cloud-acquinox-defguard"
53+
},
54+
{
55+
"display": "Eskemm Numérique: Multi-Tenant Remote Access",
56+
"url": "/blog/eskemm-numerique-multi-tenant-remote-access"
5357
}
5458
]
5559
},

src/pages/blog/index.astro

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Navigation from "../../components/base/Navigation.astro";
44
import ProductLayout from "../../layouts/ProductLayout.astro";
55
import FlexibleSection from "../../components/FlexibleSection.astro";
66
import BlogListJSONLD from "../../scripts/BlogListJSONLD.astro";
7+
import { blogFilterTags } from "../../data/blog-filter-tags";
78
89
// Get all blog posts, sorted by date
910
const allPosts = await getCollection("blog", ({ data }) => {
@@ -16,6 +17,9 @@ const sortedPosts = allPosts.sort(
1617
new Date(b.data.publishDate).valueOf() - new Date(a.data.publishDate).valueOf(),
1718
);
1819
20+
// Filter pills: show all configured tags in config order (so edits to blogFilterTags are visible immediately)
21+
const allTags = [...blogFilterTags];
22+
1923
// Default fallback image for posts without images
2024
const defaultImage = "/images/png/defguard.png";
2125
@@ -104,6 +108,14 @@ const blogTags = ["defguard", "blog", "vpn", "wireguard", "cybersecurity", "ente
104108

105109
<section class="blog-posts-listing">
106110
<div class="container">
111+
{sortedPosts.length > 0 && allTags.length > 0 && (
112+
<div class="blog-tag-filter" role="group" aria-label="Filter by tag">
113+
<button type="button" class="filter-pill active" data-tag="">All</button>
114+
{allTags.map((tag) => (
115+
<button type="button" class="filter-pill" data-tag={tag}>{tag}</button>
116+
))}
117+
</div>
118+
)}
107119
{
108120
sortedPosts.length === 0 ? (
109121
<div class="no-posts">
@@ -112,7 +124,11 @@ const blogTags = ["defguard", "blog", "vpn", "wireguard", "cybersecurity", "ente
112124
) : (
113125
<div class="posts-list">
114126
{sortedPosts.map((post, index) => (
115-
<a href={`/blog/${post.slug}`} class="post-row">
127+
<a
128+
href={`/blog/${post.slug}`}
129+
class="post-row"
130+
data-tags={post.data.tags?.map((t) => t.toLowerCase()).join(",") ?? ""}
131+
>
116132
<div class="post-thumbnail">
117133
<img
118134
src={getPostImage(post)}
@@ -159,6 +175,63 @@ const blogTags = ["defguard", "blog", "vpn", "wireguard", "cybersecurity", "ente
159175
</section>
160176
</main>
161177

178+
<script>
179+
const tagParam = "tag";
180+
181+
function getTagFromUrl(): string {
182+
const params = new URLSearchParams(window.location.search);
183+
const raw = params.get(tagParam);
184+
return raw ? raw.toLowerCase().trim() : "";
185+
}
186+
187+
function setTagInUrl(tag: string): void {
188+
const url = new URL(window.location.href);
189+
if (tag) {
190+
url.searchParams.set(tagParam, tag);
191+
} else {
192+
url.searchParams.delete(tagParam);
193+
}
194+
window.history.replaceState({ tag }, "", url.toString());
195+
}
196+
197+
function applyFilter(needle: string): void {
198+
const pillTags = Array.from(document.querySelectorAll(".blog-tag-filter .filter-pill")).map(
199+
(b) => ((b as HTMLButtonElement).dataset.tag ?? "").toLowerCase()
200+
);
201+
const validNeedle = needle && pillTags.includes(needle) ? needle : "";
202+
if (needle && !validNeedle) {
203+
setTagInUrl("");
204+
}
205+
document.querySelectorAll(".blog-tag-filter .filter-pill").forEach((b) => {
206+
const pillTag = (b as HTMLButtonElement).dataset.tag ?? "";
207+
const isActive = (pillTag.toLowerCase() === validNeedle) || (!validNeedle && pillTag === "");
208+
b.classList.toggle("active", isActive);
209+
});
210+
document.querySelectorAll(".posts-list .post-row").forEach((row) => {
211+
const tags = (row.getAttribute("data-tags") ?? "").split(",").filter(Boolean);
212+
const show = !validNeedle || tags.some((t) => t === validNeedle);
213+
(row as HTMLElement).style.display = show ? "" : "none";
214+
});
215+
}
216+
217+
function initFromUrl(): void {
218+
const tag = getTagFromUrl();
219+
applyFilter(tag);
220+
}
221+
222+
document.querySelectorAll(".blog-tag-filter .filter-pill").forEach((btn) => {
223+
btn.addEventListener("click", (e) => {
224+
e.preventDefault();
225+
const tag = (e.currentTarget as HTMLButtonElement).dataset.tag ?? "";
226+
const needle = tag.toLowerCase();
227+
setTagInUrl(needle);
228+
applyFilter(needle);
229+
});
230+
});
231+
232+
initFromUrl();
233+
</script>
234+
162235
<style lang="scss">
163236
@use "../../styles/mixins/typography" as *;
164237
@use "../../styles/mixins/breakpoints" as *;
@@ -177,6 +250,38 @@ const blogTags = ["defguard", "blog", "vpn", "wireguard", "cybersecurity", "ente
177250
padding: 0 1rem;
178251
}
179252

253+
.blog-tag-filter {
254+
display: flex;
255+
flex-wrap: wrap;
256+
gap: 0.5rem;
257+
margin-bottom: 1.5rem;
258+
padding: 0.25rem 0;
259+
}
260+
261+
.filter-pill {
262+
padding: 0.4rem 0.9rem;
263+
font-size: 0.875rem;
264+
font-weight: 500;
265+
color: var(--text-body-secondary, #555);
266+
background: #fff;
267+
border: 1px solid #e0e0e0;
268+
border-radius: 999px;
269+
cursor: pointer;
270+
transition: background-color 0.2s, border-color 0.2s, color 0.2s;
271+
272+
&:hover {
273+
background: #f5f5f5;
274+
border-color: #ccc;
275+
color: var(--text-body-primary);
276+
}
277+
278+
&.active {
279+
background: var(--primary-button-bg, #0c8ce0);
280+
border-color: var(--primary-button-bg, #0c8ce0);
281+
color: #fff;
282+
}
283+
}
284+
180285
// Posts List
181286
.posts-list {
182287
display: flex;

0 commit comments

Comments
 (0)