summaryrefslogtreecommitdiff
path: root/src/pages
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2025-07-03 10:56:21 +0300
committerDawid Rycerz <dawid@rycerz.xyz>2025-07-03 10:56:21 +0300
commit456cf011b36de91c9936994b1fa45703adcd309b (patch)
tree8e60daf998f731ac50d100fa490eaecae1168042 /src/pages
Initial fork of chrismwilliams/astro-theme-cactus theme
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/404.astro13
-rw-r--r--src/pages/about.astro36
-rw-r--r--src/pages/index.astro61
-rw-r--r--src/pages/notes/[...page].astro63
-rw-r--r--src/pages/notes/[...slug].astro31
-rw-r--r--src/pages/notes/rss.xml.ts18
-rw-r--r--src/pages/og-image/[...slug].png.ts90
-rw-r--r--src/pages/posts/[...page].astro125
-rw-r--r--src/pages/posts/[...slug].astro24
-rw-r--r--src/pages/rss.xml.ts19
-rw-r--r--src/pages/tags/[tag]/[...page].astro79
-rw-r--r--src/pages/tags/index.astro35
12 files changed, 594 insertions, 0 deletions
diff --git a/src/pages/404.astro b/src/pages/404.astro
new file mode 100644
index 0000000..d02cca3
--- /dev/null
+++ b/src/pages/404.astro
@@ -0,0 +1,13 @@
+---
+import PageLayout from "@/layouts/Base.astro";
+
+const meta = {
+ description: "Oops! It looks like this page is lost in space!",
+ title: "Oops! You found a missing page!",
+};
+---
+
+<PageLayout meta={meta}>
+ <h1 class="title mb-6">404 | Oops something went wrong</h1>
+ <p class="mb-8">Please use the navigation to find your way back</p>
+</PageLayout>
diff --git a/src/pages/about.astro b/src/pages/about.astro
new file mode 100644
index 0000000..190bbd3
--- /dev/null
+++ b/src/pages/about.astro
@@ -0,0 +1,36 @@
+---
+import PageLayout from "@/layouts/Base.astro";
+
+const meta = {
+ description: "I'm a starter theme for Astro.build",
+ title: "About",
+};
+---
+
+<PageLayout meta={meta}>
+ <h1 class="title mb-6">About</h1>
+ <div class="prose prose-sm prose-cactus max-w-none">
+ <p>
+ Hi, I’m a starter Astro. I’m particularly great for getting you started with your own blogging
+ website.
+ </p>
+ <p>Here are my some of my awesome built in features:</p>
+ <ul class="list-inside list-disc" role="list">
+ <li>I'm ultra fast as I'm a static site</li>
+ <li>I'm fully responsive</li>
+ <li>I come with a light and dark mode</li>
+ <li>I'm easy to customise and add additional content</li>
+ <li>I have Tailwind CSS styling</li>
+ <li>Shiki code syntax highlighting</li>
+ <li>Satori for auto generating OG images for blog posts</li>
+ </ul>
+ <p>
+ Clone or fork my <a
+ class="cactus-link inline-block"
+ href="https://github.com/chrismwilliams/astro-cactus"
+ rel="noreferrer"
+ target="_blank">repo</a
+ > if you like me!
+ </p>
+ </div>
+</PageLayout>
diff --git a/src/pages/index.astro b/src/pages/index.astro
new file mode 100644
index 0000000..d953797
--- /dev/null
+++ b/src/pages/index.astro
@@ -0,0 +1,61 @@
+---
+import { type CollectionEntry, getCollection } from "astro:content";
+import SocialList from "@/components/SocialList.astro";
+import PostPreview from "@/components/blog/PostPreview.astro";
+import Note from "@/components/note/Note.astro";
+import { getAllPosts } from "@/data/post";
+import PageLayout from "@/layouts/Base.astro";
+import { collectionDateSort } from "@/utils/date";
+
+// Posts
+const MAX_POSTS = 10;
+const allPosts = await getAllPosts();
+const allPostsByDate = allPosts
+ .sort(collectionDateSort)
+ .slice(0, MAX_POSTS) as CollectionEntry<"post">[];
+
+// Notes
+const MAX_NOTES = 5;
+const allNotes = await getCollection("note");
+const latestNotes = allNotes.sort(collectionDateSort).slice(0, MAX_NOTES);
+---
+
+<PageLayout meta={{ title: "Home" }}>
+ <section>
+ <h1 class="title mb-6">Hello World!</h1>
+ <p class="mb-4">
+ Hi, I’m a theme for Astro, a simple starter that you can use to create your website or blog.
+ If you want to know more about how you can customise me, add more posts, and make it your own,
+ click on the GitHub icon link below and it will take you to my repo.
+ </p>
+ <SocialList />
+ </section>
+ <section class="mt-16">
+ <h2 class="title text-accent mb-6 text-xl"><a href="/posts/">Posts</a></h2>
+ <ul class="space-y-6" role="list">
+ {
+ allPostsByDate.map((p) => (
+ <li class="grid gap-2 sm:grid-cols-[auto_1fr]">
+ <PostPreview post={p} />
+ </li>
+ ))
+ }
+ </ul>
+ </section>
+ {
+ latestNotes.length > 0 && (
+ <section class="mt-16">
+ <h2 class="title text-accent mb-6 text-xl">
+ <a href="/notes/">Notes</a>
+ </h2>
+ <ul class="space-y-6" role="list">
+ {latestNotes.map((note) => (
+ <li>
+ <Note note={note} as="h3" isPreview />
+ </li>
+ ))}
+ </ul>
+ </section>
+ )
+ }
+</PageLayout>
diff --git a/src/pages/notes/[...page].astro b/src/pages/notes/[...page].astro
new file mode 100644
index 0000000..fdc5af9
--- /dev/null
+++ b/src/pages/notes/[...page].astro
@@ -0,0 +1,63 @@
+---
+import { type CollectionEntry, getCollection } from "astro:content";
+import Pagination from "@/components/Paginator.astro";
+import Note from "@/components/note/Note.astro";
+import PageLayout from "@/layouts/Base.astro";
+import { collectionDateSort } from "@/utils/date";
+import type { GetStaticPaths, Page } from "astro";
+import { Icon } from "astro-icon/components";
+
+export const getStaticPaths = (async ({ paginate }) => {
+ const MAX_NOTES_PER_PAGE = 10;
+ const allNotes = await getCollection("note");
+ return paginate(allNotes.sort(collectionDateSort), { pageSize: MAX_NOTES_PER_PAGE });
+}) satisfies GetStaticPaths;
+
+interface Props {
+ page: Page<CollectionEntry<"note">>;
+ uniqueTags: string[];
+}
+
+const { page } = Astro.props;
+
+const meta = {
+ description: "Read my collection of notes",
+ title: "Notes",
+};
+
+const paginationProps = {
+ ...(page.url.prev && {
+ prevUrl: {
+ text: "← Previous Page",
+ url: page.url.prev,
+ },
+ }),
+ ...(page.url.next && {
+ nextUrl: {
+ text: "Next Page →",
+ url: page.url.next,
+ },
+ }),
+};
+---
+
+<PageLayout meta={meta}>
+ <section>
+ <h1 class="title mb-6 flex items-center gap-3">
+ Notes <a class="text-accent" href="/notes/rss.xml" target="_blank">
+ <span class="sr-only">RSS feed</span>
+ <Icon aria-hidden="true" class="h-6 w-6" focusable="false" name="mdi:rss" />
+ </a>
+ </h1>
+ <ul class="mt-6 space-y-8 text-start">
+ {
+ page.data.map((note) => (
+ <li class="">
+ <Note note={note} as="h2" isPreview />
+ </li>
+ ))
+ }
+ </ul>
+ <Pagination {...paginationProps} />
+ </section>
+</PageLayout>
diff --git a/src/pages/notes/[...slug].astro b/src/pages/notes/[...slug].astro
new file mode 100644
index 0000000..2ce847d
--- /dev/null
+++ b/src/pages/notes/[...slug].astro
@@ -0,0 +1,31 @@
+---
+import { getCollection } from "astro:content";
+
+import Note from "@/components/note/Note.astro";
+import PageLayout from "@/layouts/Base.astro";
+import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
+
+// if you're using an adaptor in SSR mode, getStaticPaths wont work -> https://docs.astro.build/en/guides/routing/#modifying-the-slug-example-for-ssr
+export const getStaticPaths = (async () => {
+ const allNotes = await getCollection("note");
+ return allNotes.map((note) => ({
+ params: { slug: note.id },
+ props: { note },
+ }));
+}) satisfies GetStaticPaths;
+
+export type Props = InferGetStaticPropsType<typeof getStaticPaths>;
+
+const { note } = Astro.props;
+
+const meta = {
+ description:
+ note.data.description ||
+ `Read about my note posted on: ${note.data.publishDate.toLocaleDateString()}`,
+ title: note.data.title,
+};
+---
+
+<PageLayout meta={meta}>
+ <Note as="h1" note={note} />
+</PageLayout>
diff --git a/src/pages/notes/rss.xml.ts b/src/pages/notes/rss.xml.ts
new file mode 100644
index 0000000..0f1f945
--- /dev/null
+++ b/src/pages/notes/rss.xml.ts
@@ -0,0 +1,18 @@
+import { getCollection } from "astro:content";
+import { siteConfig } from "@/site.config";
+import rss from "@astrojs/rss";
+
+export const GET = async () => {
+ const notes = await getCollection("note");
+
+ return rss({
+ title: siteConfig.title,
+ description: siteConfig.description,
+ site: import.meta.env.SITE,
+ items: notes.map((note) => ({
+ title: note.data.title,
+ pubDate: note.data.publishDate,
+ link: `notes/${note.id}/`,
+ })),
+ });
+};
diff --git a/src/pages/og-image/[...slug].png.ts b/src/pages/og-image/[...slug].png.ts
new file mode 100644
index 0000000..a4982d8
--- /dev/null
+++ b/src/pages/og-image/[...slug].png.ts
@@ -0,0 +1,90 @@
+import RobotoMonoBold from "@/assets/roboto-mono-700.ttf";
+import RobotoMono from "@/assets/roboto-mono-regular.ttf";
+import { getAllPosts } from "@/data/post";
+import { siteConfig } from "@/site.config";
+import { getFormattedDate } from "@/utils/date";
+import { Resvg } from "@resvg/resvg-js";
+import type { APIContext, InferGetStaticPropsType } from "astro";
+import satori, { type SatoriOptions } from "satori";
+import { html } from "satori-html";
+
+const ogOptions: SatoriOptions = {
+ // debug: true,
+ fonts: [
+ {
+ data: Buffer.from(RobotoMono),
+ name: "Roboto Mono",
+ style: "normal",
+ weight: 400,
+ },
+ {
+ data: Buffer.from(RobotoMonoBold),
+ name: "Roboto Mono",
+ style: "normal",
+ weight: 700,
+ },
+ ],
+ height: 630,
+ width: 1200,
+};
+
+const markup = (title: string, pubDate: string) =>
+ html`<div tw="flex flex-col w-full h-full bg-[#1d1f21] text-[#c9cacc]">
+ <div tw="flex flex-col flex-1 w-full p-10 justify-center">
+ <p tw="text-2xl mb-6">${pubDate}</p>
+ <h1 tw="text-6xl font-bold leading-snug text-white">${title}</h1>
+ </div>
+ <div tw="flex items-center justify-between w-full p-10 border-t border-[#2bbc89] text-xl">
+ <div tw="flex items-center">
+ <svg height="60" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 272 480">
+ <path
+ d="M181.334 93.333v-40L226.667 80v40l-45.333-26.667ZM136.001 53.333 90.667 26.667v426.666L136.001 480V53.333Z"
+ fill="#B04304"
+ ></path>
+ <path
+ d="m136.001 119.944 45.333-26.667 45.333 26.667-45.333 26.667-45.333-26.667ZM90.667 26.667 136.001 0l45.333 26.667-45.333 26.666-45.334-26.666ZM181.334 53.277l45.333-26.666L272 53.277l-45.333 26.667-45.333-26.667ZM0 213.277l45.333-26.667 45.334 26.667-45.334 26.667L0 213.277ZM136 239.944l-45.333-26.667v53.333L136 239.944Z"
+ fill="#FF5D01"
+ ></path>
+ <path
+ d="m136 53.333 45.333-26.666v120L226.667 120V80L272 53.333V160l-90.667 53.333v240L136 480V306.667L45.334 360V240l45.333-26.667v53.334L136 240V53.333Z"
+ fill="#53C68C"
+ ></path>
+ <path d="M45.334 240 0 213.334v120L45.334 360V240Z" fill="#B04304"></path>
+ </svg>
+ <p tw="ml-3 font-semibold">${siteConfig.title}</p>
+ </div>
+ <p>by ${siteConfig.author}</p>
+ </div>
+ </div>`;
+
+type Props = InferGetStaticPropsType<typeof getStaticPaths>;
+
+export async function GET(context: APIContext) {
+ const { pubDate, title } = context.props as Props;
+
+ const postDate = getFormattedDate(pubDate, {
+ month: "long",
+ weekday: "long",
+ });
+ const svg = await satori(markup(title, postDate), ogOptions);
+ const png = new Resvg(svg).render().asPng();
+ return new Response(png, {
+ headers: {
+ "Cache-Control": "public, max-age=31536000, immutable",
+ "Content-Type": "image/png",
+ },
+ });
+}
+
+export async function getStaticPaths() {
+ const posts = await getAllPosts();
+ return posts
+ .filter(({ data }) => !data.ogImage)
+ .map((post) => ({
+ params: { slug: post.id },
+ props: {
+ pubDate: post.data.updatedDate ?? post.data.publishDate,
+ title: post.data.title,
+ },
+ }));
+}
diff --git a/src/pages/posts/[...page].astro b/src/pages/posts/[...page].astro
new file mode 100644
index 0000000..495fc7b
--- /dev/null
+++ b/src/pages/posts/[...page].astro
@@ -0,0 +1,125 @@
+---
+import type { CollectionEntry } from "astro:content";
+import Pagination from "@/components/Paginator.astro";
+import PostPreview from "@/components/blog/PostPreview.astro";
+import { getAllPosts, getUniqueTags, groupPostsByYear } from "@/data/post";
+import PageLayout from "@/layouts/Base.astro";
+import { collectionDateSort } from "@/utils/date";
+import type { GetStaticPaths, Page } from "astro";
+import { Icon } from "astro-icon/components";
+
+export const getStaticPaths = (async ({ paginate }) => {
+ const MAX_POSTS_PER_PAGE = 10;
+ const MAX_TAGS = 7;
+ const allPosts = await getAllPosts();
+ const uniqueTags = getUniqueTags(allPosts).slice(0, MAX_TAGS);
+ return paginate(allPosts.sort(collectionDateSort), {
+ pageSize: MAX_POSTS_PER_PAGE,
+ props: { uniqueTags },
+ });
+}) satisfies GetStaticPaths;
+
+interface Props {
+ page: Page<CollectionEntry<"post">>;
+ uniqueTags: string[];
+}
+
+const { page, uniqueTags } = Astro.props;
+
+const meta = {
+ description: "Read my collection of posts and the things that interest me",
+ title: "Posts",
+};
+
+const paginationProps = {
+ ...(page.url.prev && {
+ prevUrl: {
+ text: "← Previous Page",
+ url: page.url.prev,
+ },
+ }),
+ ...(page.url.next && {
+ nextUrl: {
+ text: "Next Page →",
+ url: page.url.next,
+ },
+ }),
+};
+
+const groupedByYear = groupPostsByYear(page.data);
+const descYearKeys = Object.keys(groupedByYear).sort((a, b) => +b - +a);
+---
+
+<PageLayout meta={meta}>
+ <div class="mb-6 flex items-center gap-3">
+ <h1 class="title">Posts</h1>
+ <a class="text-accent" href="/rss.xml" target="_blank">
+ <span class="sr-only">RSS feed</span>
+ <Icon aria-hidden="true" class="h-6 w-6" focusable="false" name="mdi:rss" />
+ </a>
+ </div>
+ <div class="grid sm:grid-cols-[3fr_1fr] sm:gap-x-8 sm:gap-y-16">
+ <div>
+ {
+ descYearKeys.map((yearKey) => (
+ <section aria-labelledby={`year-${yearKey}`}>
+ <h2 id={`year-${yearKey}`} class="title text-lg">
+ <span class="sr-only">Posts in</span>
+ {yearKey}
+ </h2>
+ <ul class="mt-5 mb-16 space-y-6 text-start">
+ {groupedByYear[yearKey]?.map((p) => (
+ <li class="grid gap-2 sm:grid-cols-[auto_1fr] sm:[&_q]:col-start-2">
+ <PostPreview post={p} />
+ </li>
+ ))}
+ </ul>
+ </section>
+ ))
+ }
+ <Pagination {...paginationProps} />
+ </div>
+ {
+ !!uniqueTags.length && (
+ <aside>
+ <h2 class="title mb-4 flex items-center gap-2 text-lg">
+ Tags
+ <svg
+ aria-hidden="true"
+ class="h-6 w-6"
+ fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="1.5"
+ viewBox="0 0 24 24"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path d="M0 0h24v24H0z" fill="none" stroke="none" />
+ <path d="M7.859 6h-2.834a2.025 2.025 0 0 0 -2.025 2.025v2.834c0 .537 .213 1.052 .593 1.432l6.116 6.116a2.025 2.025 0 0 0 2.864 0l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-6.117 -6.116a2.025 2.025 0 0 0 -1.431 -.593z" />
+ <path d="M17.573 18.407l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-7.117 -7.116" />
+ <path d="M6 9h-.01" />
+ </svg>
+ </h2>
+ <ul class="flex flex-wrap gap-2">
+ {uniqueTags.map((tag) => (
+ <li>
+ <a class="cactus-link flex items-center justify-center" href={`/tags/${tag}/`}>
+ <span aria-hidden="true">#</span>
+ <span class="sr-only">View all posts with the tag</span>
+ {tag}
+ </a>
+ </li>
+ ))}
+ </ul>
+ <span class="mt-4 block sm:text-end">
+ <a class="hover:text-link" href="/tags/">
+ View all <span aria-hidden="true">→</span>
+ <span class="sr-only">blog tags</span>
+ </a>
+ </span>
+ </aside>
+ )
+ }
+ </div>
+</PageLayout>
diff --git a/src/pages/posts/[...slug].astro b/src/pages/posts/[...slug].astro
new file mode 100644
index 0000000..ca9c491
--- /dev/null
+++ b/src/pages/posts/[...slug].astro
@@ -0,0 +1,24 @@
+---
+import { render } from "astro:content";
+import { getAllPosts } from "@/data/post";
+import PostLayout from "@/layouts/BlogPost.astro";
+import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
+
+// if you're using an adaptor in SSR mode, getStaticPaths wont work -> https://docs.astro.build/en/guides/routing/#modifying-the-slug-example-for-ssr
+export const getStaticPaths = (async () => {
+ const blogEntries = await getAllPosts();
+ return blogEntries.map((post) => ({
+ params: { slug: post.id },
+ props: { post },
+ }));
+}) satisfies GetStaticPaths;
+
+type Props = InferGetStaticPropsType<typeof getStaticPaths>;
+
+const { post } = Astro.props;
+const { Content } = await render(post);
+---
+
+<PostLayout post={post}>
+ <Content />
+</PostLayout>
diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts
new file mode 100644
index 0000000..1c305af
--- /dev/null
+++ b/src/pages/rss.xml.ts
@@ -0,0 +1,19 @@
+import { getAllPosts } from "@/data/post";
+import { siteConfig } from "@/site.config";
+import rss from "@astrojs/rss";
+
+export const GET = async () => {
+ const posts = await getAllPosts();
+
+ return rss({
+ title: siteConfig.title,
+ description: siteConfig.description,
+ site: import.meta.env.SITE,
+ items: posts.map((post) => ({
+ title: post.data.title,
+ description: post.data.description,
+ pubDate: post.data.publishDate,
+ link: `posts/${post.id}/`,
+ })),
+ });
+};
diff --git a/src/pages/tags/[tag]/[...page].astro b/src/pages/tags/[tag]/[...page].astro
new file mode 100644
index 0000000..56923fb
--- /dev/null
+++ b/src/pages/tags/[tag]/[...page].astro
@@ -0,0 +1,79 @@
+---
+import { render } from "astro:content";
+import Pagination from "@/components/Paginator.astro";
+import PostPreview from "@/components/blog/PostPreview.astro";
+import { getAllPosts, getTagMeta, getUniqueTags } from "@/data/post";
+import PageLayout from "@/layouts/Base.astro";
+import { collectionDateSort } from "@/utils/date";
+import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
+import { Icon } from "astro-icon/components";
+
+export const getStaticPaths = (async ({ paginate }) => {
+ const allPosts = await getAllPosts();
+ const sortedPosts = allPosts.sort(collectionDateSort);
+ const uniqueTags = getUniqueTags(sortedPosts);
+
+ return uniqueTags.flatMap((tag) => {
+ const postsWithTag = sortedPosts.filter((post) => post.data.tags.includes(tag));
+ return paginate(postsWithTag, {
+ pageSize: 10,
+ params: { tag },
+ });
+ });
+}) satisfies GetStaticPaths;
+
+type Props = InferGetStaticPropsType<typeof getStaticPaths>;
+
+const { page } = Astro.props as Props;
+const { tag } = Astro.params;
+const tagMeta = await getTagMeta(tag);
+
+const TagContent = tagMeta ? (await render(tagMeta)).Content : null;
+
+const meta = {
+ description: tagMeta?.data.description ?? `View all posts with the tag - ${tag}`,
+ title: tagMeta?.data.title ?? `Posts about ${tag}`,
+};
+
+const paginationProps = {
+ ...(page.url.prev && {
+ prevUrl: {
+ text: "← Previous Tags",
+ url: page.url.prev,
+ },
+ }),
+ ...(page.url.next && {
+ nextUrl: {
+ text: "Next Tags →",
+ url: page.url.next,
+ },
+ }),
+};
+---
+
+<PageLayout meta={meta}>
+ <nav class="mb-8" aria-label="Breadcrumbs">
+ <ul class="flex items-center">
+ <li class="flex items-center">
+ <a class="text-accent" href="/tags/">Tags</a>
+ <Icon aria-hidden="true" name="mdi:chevron-right" class="mx-1.5" />
+ </li>
+ <li aria-current="page" class=""><span aria-hidden="true">#</span>{tag}</li>
+ </ul>
+ </nav>
+ <h1 class="title capitalize">{tagMeta?.data.title ?? `Posts about ${tag}`}</h1>
+ <div class="prose prose-sm prose-cactus mb-16 max-w-none">
+ {tagMeta?.data.description && <p>{tagMeta.data.description}</p>}
+ {TagContent && <TagContent />}
+ </div>
+ <ul class="space-y-6">
+ {
+ page.data.map((p) => (
+ <li class="grid gap-2 sm:grid-cols-[auto_1fr]">
+ <PostPreview as="h2" post={p} />
+ </li>
+ ))
+ }
+ </ul>
+ <Pagination {...paginationProps} />
+</PageLayout>
diff --git a/src/pages/tags/index.astro b/src/pages/tags/index.astro
new file mode 100644
index 0000000..df1f630
--- /dev/null
+++ b/src/pages/tags/index.astro
@@ -0,0 +1,35 @@
+---
+import { getAllPosts, getUniqueTagsWithCount } from "@/data/post";
+import PageLayout from "@/layouts/Base.astro";
+
+const allPosts = await getAllPosts();
+const allTags = getUniqueTagsWithCount(allPosts);
+
+const meta = {
+ description: "A list of all the topics I've written about in my posts",
+ title: "All Tags",
+};
+---
+
+<PageLayout meta={meta}>
+ <h1 class="title mb-6">Tags</h1>
+ <ul class="space-y-6">
+ {
+ allTags.map(([tag, val]) => (
+ <li class="flex items-center gap-x-2">
+ <a
+ class="cactus-link inline-block"
+ data-astro-prefetch
+ href={`/tags/${tag}/`}
+ title={`View posts with the tag: ${tag}`}
+ >
+ &#35;{tag}
+ </a>
+ <span class="inline-block">
+ - {val} Post{val > 1 && "s"}
+ </span>
+ </li>
+ ))
+ }
+ </ul>
+</PageLayout>