mirror of
https://github.com/KazooTTT/kazoottt-blog-v2.git
synced 2025-06-24 11:11:29 +08:00
Initial commit
This commit is contained in:
13
src/pages/404.astro
Normal file
13
src/pages/404.astro
Normal file
@ -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>
|
36
src/pages/about.astro
Normal file
36
src/pages/about.astro
Normal file
@ -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>
|
61
src/pages/index.astro
Normal file
61
src/pages/index.astro
Normal file
@ -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-4" 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-4" role="list">
|
||||
{latestNotes.map((note) => (
|
||||
<li>
|
||||
<Note note={note} as="h3" isPreview />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
</PageLayout>
|
63
src/pages/notes/[...page].astro
Normal file
63
src/pages/notes/[...page].astro
Normal file
@ -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>
|
31
src/pages/notes/[...slug].astro
Normal file
31
src/pages/notes/[...slug].astro
Normal file
@ -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>
|
18
src/pages/notes/rss.xml.ts
Normal file
18
src/pages/notes/rss.xml.ts
Normal file
@ -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}/`,
|
||||
})),
|
||||
});
|
||||
};
|
90
src/pages/og-image/[...slug].png.ts
Normal file
90
src/pages/og-image/[...slug].png.ts
Normal file
@ -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,
|
||||
},
|
||||
}));
|
||||
}
|
125
src/pages/posts/[...page].astro
Normal file
125
src/pages/posts/[...page].astro
Normal file
@ -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-4 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>
|
24
src/pages/posts/[...slug].astro
Normal file
24
src/pages/posts/[...slug].astro
Normal file
@ -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>
|
19
src/pages/rss.xml.ts
Normal file
19
src/pages/rss.xml.ts
Normal file
@ -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}/`,
|
||||
})),
|
||||
});
|
||||
};
|
72
src/pages/tags/[tag]/[...page].astro
Normal file
72
src/pages/tags/[tag]/[...page].astro
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import Pagination from "@/components/Paginator.astro";
|
||||
import PostPreview from "@/components/blog/PostPreview.astro";
|
||||
import { getAllPosts, getUniqueTags } from "@/data/post";
|
||||
import PageLayout from "@/layouts/Base.astro";
|
||||
import { collectionDateSort } from "@/utils/date";
|
||||
import type { GetStaticPaths, Page } from "astro";
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async ({ paginate }) => {
|
||||
const allPosts = await getAllPosts();
|
||||
const sortedPosts = allPosts.sort(collectionDateSort);
|
||||
const uniqueTags = getUniqueTags(sortedPosts);
|
||||
|
||||
return uniqueTags.flatMap((tag) => {
|
||||
const filterPosts = sortedPosts.filter((post) => post.data.tags.includes(tag));
|
||||
return paginate(filterPosts, {
|
||||
pageSize: 10,
|
||||
params: { tag },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
interface Props {
|
||||
page: Page<CollectionEntry<"post">>;
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const { tag } = Astro.params;
|
||||
|
||||
const meta = {
|
||||
description: `View all posts with the tag - ${tag}`,
|
||||
title: `Tag: ${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}>
|
||||
<div class="mb-6 flex items-center">
|
||||
<h1 class="sr-only">Posts with the tag {tag}</h1>
|
||||
<a class="title text-accent" href="/tags/"><span class="sr-only">All {" "}</span>Tags</a>
|
||||
<span aria-hidden="true" class="ms-2 me-3 text-xl">→</span>
|
||||
<span aria-hidden="true" class="text-xl">#{tag}</span>
|
||||
</div>
|
||||
<section aria-labelledby={`tags-${tag}`}>
|
||||
<h2 id={`tags-${tag}`} class="sr-only">Post List</h2>
|
||||
<ul class="space-y-4">
|
||||
{
|
||||
page.data.map((p) => (
|
||||
<li class="grid gap-2 sm:grid-cols-[auto_1fr]">
|
||||
<PostPreview as="h2" post={p} />
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<Pagination {...paginationProps} />
|
||||
</section>
|
||||
</PageLayout>
|
35
src/pages/tags/index.astro
Normal file
35
src/pages/tags/index.astro
Normal file
@ -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-4">
|
||||
{
|
||||
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}`}
|
||||
>
|
||||
#{tag}
|
||||
</a>
|
||||
<span class="inline-block">
|
||||
- {val} Post{val > 1 && "s"}
|
||||
</span>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</PageLayout>
|
Reference in New Issue
Block a user