diff options
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/events/[...id].astro | 168 | ||||
| -rw-r--r-- | src/pages/events/[...page].astro | 60 | ||||
| -rw-r--r-- | src/pages/index.astro | 90 | ||||
| -rw-r--r-- | src/pages/types/[...id].astro | 53 | ||||
| -rw-r--r-- | src/pages/types/index.astro | 35 |
5 files changed, 354 insertions, 52 deletions
diff --git a/src/pages/events/[...id].astro b/src/pages/events/[...id].astro new file mode 100644 index 0000000..44d8ec4 --- /dev/null +++ b/src/pages/events/[...id].astro @@ -0,0 +1,168 @@ +--- +import Breadcrumbs from '@/components/Breadcrumbs.astro' +import EventNavigation from '@/components/EventNavigation.astro' +import TOCHeader from '@/components/TOCHeader.astro' +import TOCSidebar from '@/components/TOCSidebar.astro' +import { badgeVariants } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import Layout from '@/layouts/Layout.astro' +import { + getAdjacentEvents, + getAllEvents, + getTOCSections, +} from '@/lib/data-utils' +import { formatDate, iconMappings } from '@/lib/utils' +import { Icon } from 'astro-icon/components' +import { Image } from 'astro:assets' +import { render } from 'astro:content' + +export async function getStaticPaths() { + const events = await getAllEvents() + return events.map((event) => ({ + params: { id: event.id }, + props: event, + })) +} + +const event = Astro.props +const currentPostId = Astro.params.id +const navigation = await getAdjacentEvents(currentPostId) +const { Content, headings } = await render(event) + +const tocSections = await getTOCSections(currentPostId) +--- + +<Layout> + { + headings?.length > 0 && + !(headings.length === 1 && headings[0].text === event.data.title) && ( + <TOCHeader slot="table-of-contents" headings={headings} /> + ) + } + + <section + class="grid grid-cols-[minmax(0px,1fr)_min(calc(var(--breakpoint-md)-2rem),100%)_minmax(0px,1fr)] gap-y-6" + > + <div class="col-start-2"> + <Breadcrumbs + items={[{ href: '/events', label: 'Events', icon: 'lucide:calendar' }]} + /> + </div> + + { + event.data.image && ( + <Image + src={event.data.image} + alt={event.data.title} + width={1200} + height={630} + class="col-span-full mx-auto w-full max-w-5xl object-cover" + /> + ) + } + + <section class="col-start-2 flex flex-col gap-y-6 text-center"> + <div class="flex flex-col"> + <h1 + class="mb-2 scroll-mt-31 text-3xl leading-tight font-medium sm:text-4xl" + id="event-title" + > + {event.data.title} + </h1> + + <div + class="text-muted-foreground divide-border mb-4 flex flex-col items-center justify-center divide-y text-xs sm:flex-row sm:flex-wrap sm:divide-x sm:divide-y-0 sm:text-sm" + > + <div + class="flex w-full items-center justify-center gap-2 py-2 sm:w-fit sm:px-2 sm:py-0 first:sm:pl-0 last:sm:pr-0" + > + <span>{formatDate(event.data.date)}</span> + </div> + </div> + <div class="flex flex-wrap justify-center gap-2"> + { + event.data.type && ( + <a + href={`/types/${event.data.type}`} + class={badgeVariants({ variant: 'muted' })} + > + <Icon + name={iconMappings[event.data.type] || 'lucide:hash'} + class="size-3" + /> + {event.data.type} + </a> + ) + } + </div> + </div> + </section> + + { + tocSections.length > 0 && ( + <TOCSidebar sections={tocSections} currentPostId={currentPostId} /> + ) + } + + <article class="prose col-start-2 max-w-none"> + <Content /> + </article> + + <EventNavigation + newerEvent={navigation.newer} + olderEvent={navigation.older} + /> + </section> + + <Button + variant="outline" + size="icon" + className="group fixed right-8 bottom-8 z-50 hidden" + id="scroll-to-top" + title="Scroll to top" + aria-label="Scroll to top" + > + <Icon + name="lucide:arrow-up" + class="mx-auto size-4 transition-all group-hover:-translate-y-0.5" + /> + </Button> + + <script> + document.addEventListener('astro:page-load', () => { + const scrollToTopButton = document.getElementById('scroll-to-top') + const footer = document.querySelector('footer') + + if (scrollToTopButton && footer) { + scrollToTopButton.addEventListener('click', () => { + window.scrollTo({ top: 0, behavior: 'smooth' }) + }) + + window.addEventListener('scroll', () => { + const footerRect = footer.getBoundingClientRect() + const isFooterVisible = footerRect.top <= window.innerHeight + + scrollToTopButton.classList.toggle( + 'hidden', + window.scrollY <= 300 || isFooterVisible, + ) + }) + } + }) + </script> +</Layout> + +<script> + const ensureKatexStyles = () => { + if (!document.querySelector('.katex')) return + if (document.querySelector('link[href*="katex.min.css"]')) return + + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = 'https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.css' + document.head.appendChild(link) + } + + document.addEventListener('astro:page-load', ensureKatexStyles) + document.addEventListener('astro:after-swap', ensureKatexStyles) +</script> diff --git a/src/pages/events/[...page].astro b/src/pages/events/[...page].astro new file mode 100644 index 0000000..2625ab0 --- /dev/null +++ b/src/pages/events/[...page].astro @@ -0,0 +1,60 @@ +--- +import Breadcrumbs from '@/components/Breadcrumbs.astro' +import EventCard from '@/components/EventCard.astro' +import PageHead from '@/components/PageHead.astro' +import PaginationComponent from '@/components/ui/pagination' +import { SITE } from '@/consts' +import Layout from '@/layouts/Layout.astro' +import { getAllEvents, groupEventsByYear } from '@/lib/data-utils' +import type { PaginateFunction } from 'astro' + +export async function getStaticPaths({ + paginate, +}: { + paginate: PaginateFunction +}) { + const allEvents = await getAllEvents() + return paginate(allEvents, { pageSize: SITE.eventsPerPage }) +} + +const { page } = Astro.props + +const eventsByYear = groupEventsByYear(page.data) +const years = Object.keys(eventsByYear).sort( + (a, b) => parseInt(b) - parseInt(a), +) +--- + +<Layout class="max-w-3xl"> + <PageHead slot="head" title="Events" /> + <Breadcrumbs + items={[ + { label: 'Events', href: '/events', icon: 'lucide:calendar-range' }, + { label: `Page ${page.currentPage}`, icon: 'lucide:calendar' }, + ]} + /> + + <div class="flex min-h-[calc(100vh-18rem)] flex-col gap-y-8"> + { + years.map((year) => ( + <section class="flex flex-col gap-y-4"> + <div class="font-medium">{year}</div> + <ul class="flex flex-col gap-4"> + {eventsByYear[year].map((event) => ( + <li> + <EventCard entry={event} /> + </li> + ))} + </ul> + </section> + )) + } + </div> + + <PaginationComponent + currentPage={page.currentPage} + totalPages={page.lastPage} + baseUrl="/events/" + client:load + /> +</Layout> diff --git a/src/pages/index.astro b/src/pages/index.astro index 019283d..9549a7c 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,13 +1,15 @@ --- import BlogCard from '@/components/BlogCard.astro' +import EventCard from '@/components/EventCard.astro' import Link from '@/components/Link.astro' import PageHead from '@/components/PageHead.astro' import { buttonVariants } from '@/components/ui/button' import { SITE } from '@/consts' import Layout from '@/layouts/Layout.astro' -import { getRecentPosts } from '@/lib/data-utils' +import { getRecentPosts, getRecentEvents } from '@/lib/data-utils' const blog = await getRecentPosts(SITE.featuredPostCount) +const events = await getRecentEvents(SITE.featuredEventsCount) --- <Layout class="max-w-3xl"> @@ -16,69 +18,53 @@ const blog = await getRecentPosts(SITE.featuredPostCount) <div class="flex flex-col space-y-1.5 p-6"> <h3 class="text-3xl leading-none font-medium">er·u·dite</h3> <p class="text-muted-foreground text-sm"> - /ˈer(y)əˌdīt/ • <span class="font-medium">adjective</span> + /ˈer(y)əˌdīt/ • <span class="font-medium">adjective</span> </p> </div> - <div class="p-6 pt-0"> - <p class="text-muted-foreground mb-2 text-sm"> - astro-erudite is an opinionated, unstyled static blogging template built - with <Link - href="https://astro.build" - class="text-foreground" - external - underline>Astro</Link - >, <Link - href="https://tailwindcss.com" - class="text-foreground" - external - underline>Tailwind</Link - >, and <Link - href="https://ui.shadcn.com" - class="text-foreground" - external - underline>shadcn/ui</Link - >. Extraordinarily loosely based on the <Link - href="https://astro-micro.vercel.app/" - class="text-foreground" - external - underline>Astro Micro</Link - > theme. - </p> - <p class="text-muted-foreground text-sm"> - To use this template, check out the <Link - href="https://github.com/jktrn/astro-erudite" - class="text-foreground" - underline - external>GitHub</Link - > repository. To learn more about why this template exists, read this blog - post: <Link - href="/blog/the-state-of-static-blogs" - class="text-foreground" - underline>The State of Static Blogs in 2024</Link - >. - </p> + </section> + <section class="flex flex-col gap-y-4"> + <h2 class="text-2xl font-medium">Latest events</h2> + <ul class="flex flex-col gap-y-4"> + { + events.map((event) => ( + <li> + <EventCard entry={event} /> + </li> + )) + } + </ul> + <div class="flex justify-center"> + <Link + href="/events" + class={buttonVariants({ variant: 'ghost' }) + ' group'} + > + See all posts <span + class="ml-1.5 transition-transform group-hover:translate-x-1" + >→</span + > + </Link> </div> </section> <section class="flex flex-col gap-y-4"> <h2 class="text-2xl font-medium">Latest posts</h2> <ul class="flex flex-col gap-y-4"> { - blog.map((post) => ( - <li> - <BlogCard entry={post} /> - </li> - )) + blog.map((post) => ( + <li> + <BlogCard entry={post} /> + </li> + )) } </ul> <div class="flex justify-center"> <Link - href="/blog" - class={buttonVariants({ variant: 'ghost' }) + ' group'} - > - See all posts <span - class="ml-1.5 transition-transform group-hover:translate-x-1" - >→</span - > + href="/blog" + class={buttonVariants({ variant: 'ghost' }) + ' group'} + > + See all posts <span + class="ml-1.5 transition-transform group-hover:translate-x-1" + >→</span + > </Link> </div> </section> diff --git a/src/pages/types/[...id].astro b/src/pages/types/[...id].astro new file mode 100644 index 0000000..26640a9 --- /dev/null +++ b/src/pages/types/[...id].astro @@ -0,0 +1,53 @@ +--- +import Breadcrumbs from '@/components/Breadcrumbs.astro' +import PageHead from '@/components/PageHead.astro' +import Layout from '@/layouts/Layout.astro' +import { getAllTypes, getEventsByType } from '@/lib/data-utils' +import { capitalizeFirstLetter, iconMappings } from '@/lib/utils' +import EventCard from '../../components/EventCard.astro' + +export async function getStaticPaths() { + const typeMap = await getAllTypes() + const uniqueTags = Array.from(typeMap.keys()) + + return Promise.all( + uniqueTags.map(async (type) => { + const events = await getEventsByType(type) + return { + params: { id: type }, + props: { + type, + events, + }, + } + }), + ) +} + +const { type, events } = Astro.props +--- + +<Layout class="max-w-3xl"> + <PageHead + slot="head" + title={`${capitalizeFirstLetter(type)} Events`} + description={`A collection of ${type} events.`} + noindex + /> + <Breadcrumbs + items={[ + { href: '/types', label: 'Event types', icon: 'lucide:calendar' }, + { label: type, icon: iconMappings[type] || 'lucide:ticket' }, + ]} + /> + + <ul class="flex flex-col gap-y-4"> + { + events.map((event) => ( + <li> + <EventCard entry={event} /> + </li> + )) + } + </ul> +</Layout> diff --git a/src/pages/types/index.astro b/src/pages/types/index.astro new file mode 100644 index 0000000..c269c22 --- /dev/null +++ b/src/pages/types/index.astro @@ -0,0 +1,35 @@ +--- +import Breadcrumbs from '@/components/Breadcrumbs.astro' +import Link from '@/components/Link.astro' +import PageHead from '@/components/PageHead.astro' +import { badgeVariants } from '@/components/ui/badge' +import Layout from '@/layouts/Layout.astro' +import { getAllTypes } from '@/lib/data-utils' +import { iconMappings } from '@/lib/utils' +import { Icon } from 'astro-icon/components' + +const types = await getAllTypes() +--- + +<Layout class="max-w-3xl"> + <PageHead slot="head" title="Tags" /> + <Breadcrumbs items={[{ label: 'Tags', icon: 'lucide:tags' }]} /> + + <div class="flex flex-col gap-4"> + <div class="flex flex-wrap gap-2"> + {console.log([...types.entries()].map((a) => console.log(a)))} + { + [...types.entries()].map(([type, count]) => ( + <Link + href={`/types/${type}`} + class={badgeVariants({ variant: 'muted' })} + > + <Icon name={iconMappings[type] || 'lucide:hash'} class="size-3" /> + {type} + <span class="text-muted-foreground ml-1.5">({count})</span> + </Link> + )) + } + </div> + </div> +</Layout> |
