Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/components/Footer.astro
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
---
import SocialLinks from "@components/SocialLinks.astro";
import { FOOTER_COLUMNS, TERMS, SOCIALS } from "@data/nav";
import { buildLinkChecker } from "@utils/nav";
import EPSLogo from "/public/theme/eps-logo.svg";

const linkExists = await buildLinkChecker();

// Filter footer columns to only show links that point to existing pages
const activeFooterColumns = FOOTER_COLUMNS.map((col) => ({
...col,
items: col.items.filter((item) => linkExists(item.url)),
})).filter((col) => col.items.length > 0);

const buildTimestamp = __TIMESTAMP__;
const gitVersion = __GIT_VERSION__;
---
Expand All @@ -18,7 +27,7 @@ const gitVersion = __GIT_VERSION__;
</div>
<div class="footer-grid">
{
FOOTER_COLUMNS.map((col) => (
activeFooterColumns.map((col) => (
<div>
<p class="footer-col-title">{col.title}</p>
<ul class="footer-links">
Expand Down
40 changes: 2 additions & 38 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,9 @@ import Search from "@components/Search.astro";
import ThemeToggle from "@components/ThemeToggle.astro";

import { NAV_MENUS, type NavMenu } from "@data/nav";
import { getCollection } from "astro:content";
import { readFileSync } from "fs";

// Parse redirect paths from astro.config.mjs at build time
const configText = readFileSync("astro.config.mjs", "utf-8");
const redirectMatch = configText.match(/redirects:\s*{([^}]+)}/s);
const redirectPathsFromConfig: string[] = redirectMatch
? [...redirectMatch[1].matchAll(/"([^"]+)"\s*:/g)].map((m) => m[1].replace(/^\//, "").replace(/\/$/, ""))
: [];

// Build list of existing content page slugs to filter nav
const existingPages = await getCollection("pages");
const existingSlugs = new Set(existingPages.map((p) => p.id));

// Standalone .astro pages that always exist regardless of content collection
const alwaysExist = new Set([
"sessions", "speakers", "schedule", "posters", "talks", "tutorials",
"sprints", "jobs", "sponsors", "community-partners", "media-partners",
]);

function linkExists(url: string): boolean {
if (url.startsWith("http")) return true;
const slug = url.replace(/^\//, "").replace(/\/$/, "");
if (!slug) return true;
if (alwaysExist.has(slug)) return true;
const redirectPaths = [...new Set(redirectPathsFromConfig)];
if (redirectPaths.includes(slug)) return true;
// Check exact match in content pages
if (existingSlugs.has(slug)) return true;
// Check if slug is the short form of a nested page (e.g. "mentorship" from "programme/mentorship")
for (const existing of existingSlugs) {
if (existing.endsWith("/" + slug) || existing === slug) return true;
}
return false;
}
import { buildLinkChecker } from "@utils/nav";

function sectionHasItems(section: { items: { url: string }[] }): boolean {
return section.items.some((item) => linkExists(item.url));
}
const linkExists = await buildLinkChecker();

// Filter nav menus to only show links that point to existing pages
const activeMenus = NAV_MENUS.map((menu) => ({
Expand Down
56 changes: 56 additions & 0 deletions src/utils/nav.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { getCollection } from "astro:content";
import { readFileSync } from "fs";

/** Standalone .astro pages that always exist regardless of content collection */
const ALWAYS_EXIST = new Set([
"sessions",
"speakers",
"schedule",
"posters",
"talks",
"tutorials",
"sprints",
"jobs",
"sponsors",
"community-partners",
"media-partners",
]);

/**
* Build a link-checker function that returns `true` for URLs pointing to
* existing pages (content collection, standalone pages, or redirect targets).
*
* Call once in `.astro` frontmatter, then pass the returned function to
* any filter step.
*/
export async function buildLinkChecker(): Promise<(url: string) => boolean> {
// Parse redirect paths from astro.config.mjs at build time
const configText = readFileSync("astro.config.mjs", "utf-8");
const redirectMatch = configText.match(/redirects:\s*{([^}]+)}/s);
const redirectPathsFromConfig: string[] = redirectMatch
? [...redirectMatch[1].matchAll(/"([^"]+)"\s*:/g)].map((m) =>
m[1].replace(/^\//, "").replace(/\/$/, "")
)
: [];

// Build list of existing content page slugs
const existingPages = await getCollection("pages");
const existingSlugs = new Set(existingPages.map((p) => p.id));

const redirectPaths = [...new Set(redirectPathsFromConfig)];

return function linkExists(url: string): boolean {
if (url.startsWith("http")) return true;
const slug = url.replace(/^\//, "").replace(/\/$/, "");
if (!slug) return true;
if (ALWAYS_EXIST.has(slug)) return true;
if (redirectPaths.includes(slug)) return true;
// Check exact match in content pages
if (existingSlugs.has(slug)) return true;
// Check if slug is the short form of a nested page (e.g. "mentorship" from "programme/mentorship")
for (const existing of existingSlugs) {
if (existing.endsWith("/" + slug) || existing === slug) return true;
}
return false;
};
}
Loading