Skip to content

Commit 2a5874c

Browse files
Merge branch 'ep2025' into ep2025-thank-you
2 parents 87d8dda + 5bcb344 commit 2a5874c

File tree

9 files changed

+173
-8
lines changed

9 files changed

+173
-8
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"hastscript": "^9.0.1",
3535
"js-yaml": "^4.1.0",
3636
"lite-youtube-embed": "^0.3.3",
37-
"marked": "^16.0.0",
37+
"marked": "^16.1.1",
3838
"nanostores": "^1.0.1",
3939
"pagefind": "^1.3.0",
4040
"rehype-autolink-headings": "^7.1.0",

pnpm-lock.yaml

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Breadcrumbs.astro

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
import Section from "@ui/Section.astro";
3+
4+
export interface Props {
5+
linksData: any;
6+
currentPath: string;
7+
homeLabel?: string;
8+
homePath?: string;
9+
separator?: string;
10+
}
11+
12+
const {
13+
linksData,
14+
currentPath,
15+
homeLabel = "Home",
16+
homePath = "/",
17+
separator = "/"
18+
} = Astro.props;
19+
20+
interface BreadcrumbItem {
21+
name: string;
22+
path: string;
23+
isActive: boolean;
24+
}
25+
26+
function normalizePath(path: string): string {
27+
console.log(path)
28+
if (!path) return ""
29+
if (path === "/") return "/";
30+
return path.replace(/\/+$/, "");
31+
}
32+
33+
const normalizedCurrentPath = normalizePath(currentPath);
34+
35+
// Recursive function to build breadcrumb trail
36+
function findBreadcrumbTrail(items: any[], currentPath: string, parentName?: string): BreadcrumbItem[] {
37+
for (const item of items) {
38+
const itemPath = normalizePath(item.path);
39+
40+
if (itemPath === currentPath) {
41+
const breadcrumb: BreadcrumbItem = {
42+
name: item.name,
43+
path: item.path,
44+
isActive: true
45+
};
46+
47+
if (parentName) {
48+
return [
49+
{ name: parentName, path: '', isActive: false },
50+
breadcrumb
51+
];
52+
}
53+
54+
return [breadcrumb];
55+
}
56+
57+
if (item.items) {
58+
const subTrail = findBreadcrumbTrail(item.items, currentPath, item.name);
59+
if (subTrail.length > 0) {
60+
return subTrail;
61+
}
62+
}
63+
}
64+
65+
return [];
66+
}
67+
68+
// Build the breadcrumb trail
69+
const breadcrumbs: BreadcrumbItem[] = [];
70+
71+
breadcrumbs.push({
72+
name: homeLabel,
73+
path: homePath,
74+
isActive: normalizedCurrentPath === normalizePath(homePath)
75+
});
76+
77+
if (normalizedCurrentPath !== normalizePath(homePath) && linksData?.header) {
78+
const trail = findBreadcrumbTrail(linksData.header, normalizedCurrentPath);
79+
breadcrumbs.push(...trail);
80+
}
81+
82+
if (breadcrumbs.length === 1 && normalizedCurrentPath !== normalizePath(homePath)) {
83+
const pathSegments = normalizedCurrentPath.split('/').filter(Boolean);
84+
const lastSegment = pathSegments[pathSegments.length - 1];
85+
86+
if (lastSegment) {
87+
breadcrumbs.push({
88+
name: lastSegment.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
89+
path: currentPath,
90+
isActive: true
91+
});
92+
}
93+
}
94+
---
95+
96+
<Section>
97+
{ currentPath !== homePath &&
98+
<nav aria-label="Breadcrumb" class="px-4">
99+
<ol class="flex flex-wrap items-center text-sm list-none m-0 p-0">
100+
{breadcrumbs.map((crumb, index) => (
101+
<li class="flex items-center">
102+
{crumb.isActive ? (
103+
<span class="text-gray-900 font-medium px-2 py-1" aria-current="page">
104+
{crumb.name}
105+
</span>
106+
) : crumb.path ? (
107+
<a href={crumb.path} class="underline hover:text-button-hover px-2 py-1 rounded transition-colors duration-200 hover:underline">
108+
{crumb.name}
109+
</a>
110+
) : (
111+
<span class="text-gray-500 italic px-2 py-1">
112+
{crumb.name}
113+
</span>
114+
)}
115+
{index < breadcrumbs.length - 1 && (
116+
<span class="mx-2 text-gray-400 select-none" aria-hidden="true">
117+
{separator}
118+
</span>
119+
)}
120+
</li>
121+
))}
122+
</ol>
123+
</nav>
124+
}
125+
</Section>

src/components/SocialLinks.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const socialMap = {
1818
discord: { icon: "discord", label: "Discord", color: "#5865f2" },
1919
facebook: { icon: "facebook", label: "Facebook", color: "#1877f2" },
2020
youtube: { icon: "youtube", label: "YouTube", color: "#ff0000" },
21+
tiktok: { icon: "tiktok", label: "TikTok", color: "#000000" },
2122
};
2223
---
2324

src/components/SprintCard.astro

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ if (!sprint) {
1111
throw new Error(`Sprint entry "${slug}" not found`);
1212
}
1313
14+
if (sprint.body && /^#+\s/m.test(sprint.body)) {
15+
throw new Error(`No headlines allowed in description. [${sprint.id}]`);
16+
}
17+
18+
1419
const { data, body } = sprint;
1520
1621
// <a href={`/sprints/${slug}`} id={slug} class="text-blue-600 hover:text-blue-800 hover:underline"></a>

src/content/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ const speakers = defineCollection({
108108
mastodon_url: z.string().url().nullable(),
109109
bluesky_url: z.string().url().nullable().optional(),
110110
twitter_url: z.string().url().nullable(),
111+
discord: z.string().url().nullable().optional(),
112+
tiktok: z.string().url().nullable().optional(),
111113
}),
112114
});
113115

@@ -224,6 +226,7 @@ const sponsors = defineCollection({
224226
discord: z.string().url().optional().nullable(),
225227
facebook: z.string().url().optional().nullable(),
226228
youtube: z.string().url().optional().nullable(),
229+
tiktok: z.string().url().optional().nullable(),
227230
})
228231
.optional(),
229232
event_name: z.string().optional().nullable(),

src/layouts/Layout.astro

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@ import BaseHead from "@components/BaseHead.astro";
33
import Header from "@components/Header.astro";
44
import Footer from "@components/Footer.astro";
55
import Offline from "@components/Offline.astro";
6+
import Breadcrumbs from '@components/Breadcrumbs.astro';
7+
8+
import linksData from '@src/data/links.json';
69
710
import "@fortawesome/fontawesome-free/css/all.min.css";
811
import "@styles/tailwind.css";
912
import "@styles/global.css";
1013
import "@styles/search.css";
1114
15+
1216
export interface Props {
1317
title: string;
1418
description: string;
1519
}
1620
21+
const currentPath = Astro.url.pathname;
22+
1723
const { title, description } = Astro.props;
1824
1925
if (!title || !description) {
@@ -37,6 +43,13 @@ const externalDomain = new URL(Astro.site || "").hostname;
3743

3844
<main class="main pt-28" role="main">
3945
<Offline />
46+
<Breadcrumbs
47+
linksData={linksData}
48+
currentPath={currentPath}
49+
homeLabel="EuroPython 2025"
50+
homePath="/"
51+
separator=""
52+
/>
4053
<slot />
4154
</main>
4255

src/pages/sprints.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ if (!entry) {
1616
throw new Error('Could not find page entry.');
1717
}
1818
19-
const sprints = await getCollection("sprints", ({ data }) => {
19+
const sprints = (await getCollection("sprints", ({ data }) => {
2020
return import.meta.env.MODE === "production" ? data.draft !== true : true;
21-
});
21+
})).sort((a, b) => a.id.localeCompare(b.id));
2222
2323
---
2424

src/styles/global.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,20 @@ main a[href^="#"]::before {
288288
h2.sponsor {
289289
scroll-margin-top: 300px;
290290
}
291+
292+
.prose dl {
293+
display: grid;
294+
grid-template-columns: max-content 1fr;
295+
margin-bottom: 1rem;
296+
}
297+
298+
.prose dt {
299+
margin-top: 0.6em;
300+
margin-right: 0.5rem;
301+
}
302+
303+
@media (max-width: 640px) {
304+
.prose dl {
305+
display: block;
306+
}
307+
}

0 commit comments

Comments
 (0)