summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2026-01-13 15:31:19 +0100
committerDawid Rycerz <dawid@rycerz.xyz>2026-01-13 15:39:07 +0100
commit52411f6cb9efc10dd683096b34e5c279a11f7e0a (patch)
treefd5e71902fbebe2e3f014c7ebb5f153fc8c7d1e7
parent26ffc44ee72522891b4fdacac15134dfcf9c4859 (diff)
Rework how tags are working and make them native
-rw-r--r--src/content.config.ts1
-rw-r--r--src/data/post.ts10
-rw-r--r--src/loaders/pleroma.ts8
-rw-r--r--src/pages/micro/[...page].astro10
-rw-r--r--src/pages/micro/rss.xml.ts6
-rw-r--r--src/pages/micro/tags/[tag]/[...page].astro71
-rw-r--r--src/pages/micro/tags/index.astro36
-rw-r--r--src/pages/tags/[tag]/[...page].astro64
-rw-r--r--src/pages/tags/[tag]/rss.xml.ts69
-rw-r--r--src/pages/tags/index.astro24
-rw-r--r--src/site.config.ts2
11 files changed, 163 insertions, 138 deletions
diff --git a/src/content.config.ts b/src/content.config.ts
index e7729e3..a9fa0e2 100644
--- a/src/content.config.ts
+++ b/src/content.config.ts
@@ -34,6 +34,7 @@ const post = defineCollection({
.string()
.optional()
.transform((str) => (str ? new Date(str) : undefined)),
+ language: z.string().optional(),
}),
});
diff --git a/src/data/post.ts b/src/data/post.ts
index c7df82f..7cadb93 100644
--- a/src/data/post.ts
+++ b/src/data/post.ts
@@ -1,11 +1,15 @@
import { type CollectionEntry, getCollection } from "astro:content";
-/** filter out draft posts based on the environment and optionally archived posts */
-export async function getAllPosts(includeArchived = false): Promise<CollectionEntry<"post">[]> {
+/** filter out draft posts based on the environment and optionally archived posts and micro posts */
+export async function getAllPosts(
+ includeArchived = false,
+ includeMicro = false,
+): Promise<CollectionEntry<"post">[]> {
return await getCollection("post", ({ data }) => {
const isDraftFilter = import.meta.env.PROD ? !data.draft : true;
const isArchivedFilter = includeArchived || !data.tags.includes("archived");
- return isDraftFilter && isArchivedFilter;
+ const isMicroFilter = includeMicro || !data.tags.includes("micro");
+ return isDraftFilter && isArchivedFilter && isMicroFilter;
});
}
diff --git a/src/loaders/pleroma.ts b/src/loaders/pleroma.ts
index 975f57e..c57a22b 100644
--- a/src/loaders/pleroma.ts
+++ b/src/loaders/pleroma.ts
@@ -552,14 +552,14 @@ function replaceHashtagsWithLinks(content: string): {
// First, replace existing markdown hashtag links: [#tag](any-url)
let modifiedContent = content.replace(/\[#(\w+)\]\([^)]+\)/g, (_match, tag) => {
tags.push(tag.toLowerCase());
- return `[#${tag}](/micro/tags/${tag.toLowerCase()})`;
+ return `[#${tag}](/tags/${tag.toLowerCase()})`;
});
// Then, replace plain #hashtags (not already in markdown link format)
// Negative lookbehind to avoid matching hashtags already in [#tag] format
modifiedContent = modifiedContent.replace(/(?<!\[)#(\w+)(?!\])/g, (_match, tag) => {
tags.push(tag.toLowerCase());
- return `[#${tag}](/micro/tags/${tag.toLowerCase()})`;
+ return `[#${tag}](/tags/${tag.toLowerCase()})`;
});
return {
@@ -699,7 +699,7 @@ export function pleromaLoader(config: PleromaFeedConfig): Loader {
const { content: contentWithTags, tags: extractedTags } = replaceHashtagsWithLinks(
merged.content,
);
- tags = extractedTags;
+ tags = [...extractedTags, "micro"];
cleanedContent = replacePleromaLinks(contentWithTags, instanceUrl, allPostIds);
attachments = merged.attachments;
postId = status.id;
@@ -709,7 +709,7 @@ export function pleromaLoader(config: PleromaFeedConfig): Loader {
const rawContent = cleanContent(content);
const { content: contentWithTags, tags: extractedTags } =
replaceHashtagsWithLinks(rawContent);
- tags = extractedTags;
+ tags = [...extractedTags, "micro"];
cleanedContent = replacePleromaLinks(contentWithTags, instanceUrl, allPostIds);
postId = status.id;
sourceUrl = status.url;
diff --git a/src/pages/micro/[...page].astro b/src/pages/micro/[...page].astro
index 8e7e814..d11d9ce 100644
--- a/src/pages/micro/[...page].astro
+++ b/src/pages/micro/[...page].astro
@@ -9,8 +9,10 @@ import PageLayout from "@/layouts/Base.astro";
export const getStaticPaths = (async ({ paginate }) => {
const MAX_MICRO_PER_PAGE = 10;
- // Get only Pleroma posts
- const allMicro = await getCollection("micro").catch(() => []); // Fallback to empty array if micro collection fails
+ // Get only Pleroma posts tagged with "micro"
+ const allMicro = await getCollection("micro", ({ data }) => data.tags?.includes("micro")).catch(
+ () => [],
+ ); // Fallback to empty array if micro collection fails
// Sort all micro posts
const allMicroPosts = allMicro.sort(
@@ -52,11 +54,11 @@ const paginationProps = {
<section>
<h1 class="title mb-6 flex items-center gap-3">
Micro
- <a class="text-accent" href="/micro/tags/" title="Browse micro tags">
+ <a class="text-accent" href="/tags/" title="Browse all tags">
<span class="sr-only">Browse tags</span>
<Icon aria-hidden="true" class="h-6 w-6" focusable="false" name="mdi:tag-multiple" />
</a>
- <a class="text-accent" href="/micro/rss.xml" target="_blank">
+ <a class="text-accent" href="/tags/micro/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>
diff --git a/src/pages/micro/rss.xml.ts b/src/pages/micro/rss.xml.ts
index 37d7d39..9356341 100644
--- a/src/pages/micro/rss.xml.ts
+++ b/src/pages/micro/rss.xml.ts
@@ -4,8 +4,10 @@ import type { APIContext } from "astro";
import { siteConfig } from "@/site.config";
export const GET = async (context: APIContext) => {
- // Get only Pleroma posts
- const allMicro = await getCollection("micro").catch(() => []); // Fallback to empty array if micro collection fails
+ // Get only Pleroma posts tagged with "micro"
+ const allMicro = await getCollection("micro", ({ data }) => data.tags?.includes("micro")).catch(
+ () => [],
+ ); // Fallback to empty array if micro collection fails
// Sort all micro posts
const allMicroPosts = allMicro.sort(
diff --git a/src/pages/micro/tags/[tag]/[...page].astro b/src/pages/micro/tags/[tag]/[...page].astro
deleted file mode 100644
index 3f78663..0000000
--- a/src/pages/micro/tags/[tag]/[...page].astro
+++ /dev/null
@@ -1,71 +0,0 @@
----
-import { getCollection } from "astro:content";
-import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
-import { Icon } from "astro-icon/components";
-import Note from "@/components/note/Note.astro";
-import Pagination from "@/components/Paginator.astro";
-import PageLayout from "@/layouts/Base.astro";
-import { getUniqueMicroTags, sortMicroEntries } from "@/utils/micro";
-
-export const getStaticPaths = (async ({ paginate }) => {
- const allMicroPosts = await getCollection("micro");
- const sortedPosts = sortMicroEntries(allMicroPosts);
- const uniqueTags = getUniqueMicroTags(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 meta = {
- description: `View all micro posts with the tag - ${tag}`,
- title: `Micro posts about ${tag}`,
-};
-
-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}>
- <nav class="mb-8" aria-label="Breadcrumbs">
- <ul class="flex items-center">
- <li class="flex items-center">
- <a class="text-accent" href="/micro/tags/">Micro 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">Micro posts about {tag}</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} />
-</PageLayout>
diff --git a/src/pages/micro/tags/index.astro b/src/pages/micro/tags/index.astro
deleted file mode 100644
index 8d39b8a..0000000
--- a/src/pages/micro/tags/index.astro
+++ /dev/null
@@ -1,36 +0,0 @@
----
-import { getCollection } from "astro:content";
-import PageLayout from "@/layouts/Base.astro";
-import { getUniqueMicroTagsWithCount } from "@/utils/micro";
-
-const allMicroPosts = await getCollection("micro");
-const allTags = getUniqueMicroTagsWithCount(allMicroPosts);
-
-const meta = {
- description: "A list of all the topics I've written about in my micro posts",
- title: "Micro Tags",
-};
----
-
-<PageLayout meta={meta}>
- <h1 class="title mb-6">Micro 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={`/micro/tags/${tag}/`}
- title={`View micro posts with the tag: ${tag}`}
- >
- &#35;{tag}
- </a>
- <span class="inline-block">
- - {val} Post{val > 1 && "s"}
- </span>
- </li>
- ))
- }
- </ul>
-</PageLayout>
diff --git a/src/pages/tags/[tag]/[...page].astro b/src/pages/tags/[tag]/[...page].astro
index 1d2cc5d..9556309 100644
--- a/src/pages/tags/[tag]/[...page].astro
+++ b/src/pages/tags/[tag]/[...page].astro
@@ -1,21 +1,35 @@
---
-import { render } from "astro:content";
+import { getCollection, render } from "astro:content";
import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
import { Icon } from "astro-icon/components";
import PostPreview from "@/components/blog/PostPreview.astro";
+import Note from "@/components/note/Note.astro";
import Pagination from "@/components/Paginator.astro";
import { getAllPosts, getTagMeta, getUniqueTags } from "@/data/post";
import PageLayout from "@/layouts/Base.astro";
import { collectionDateSort } from "@/utils/date";
+import { getUniqueMicroTags } from "@/utils/micro";
export const getStaticPaths = (async ({ paginate }) => {
- const allPosts = await getAllPosts(true); // Include archived posts for tag filtering
- const sortedPosts = allPosts.sort(collectionDateSort);
- const uniqueTags = getUniqueTags(sortedPosts);
+ const allPosts = await getAllPosts(true, true); // Include archived and micro posts for tag filtering
+ const allMicro = await getCollection("micro", ({ data }) => data.tags?.includes("micro")).catch(
+ () => [],
+ );
- return uniqueTags.flatMap((tag) => {
- const postsWithTag = sortedPosts.filter((post) => post.data.tags.includes(tag));
- return paginate(postsWithTag, {
+ // Get unique tags from both collections
+ const postTags = getUniqueTags(allPosts);
+ const microTags = getUniqueMicroTags(allMicro);
+ const allTags = [...new Set([...postTags, ...microTags])];
+
+ return allTags.flatMap((tag) => {
+ // Filter posts and micro posts by tag
+ const postsWithTag = allPosts.filter((post) => post.data.tags.includes(tag));
+ const microWithTag = allMicro.filter((micro) => micro.data.tags?.includes(tag));
+
+ // Combine and sort chronologically
+ const allItems = [...postsWithTag, ...microWithTag].sort(collectionDateSort);
+
+ return paginate(allItems, {
pageSize: 10,
params: { tag },
});
@@ -30,9 +44,12 @@ const tagMeta = await getTagMeta(tag);
const TagContent = tagMeta ? (await render(tagMeta)).Content : null;
+// Capitalize first letter of tag for display
+const tagDisplay = tag.charAt(0).toUpperCase() + tag.slice(1);
+
const meta = {
description: tagMeta?.data.description ?? `View all posts with the tag - ${tag}`,
- title: tagMeta?.data.title ?? `Posts about ${tag}`,
+ title: tagMeta?.data.title ?? `${tagDisplay} posts`,
};
const paginationProps = {
@@ -61,18 +78,35 @@ const paginationProps = {
<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">
+ <h1 class="title mb-6 flex items-center gap-3 capitalize">
+ {tagMeta?.data.title ?? `${tagDisplay} posts`}
+ <a
+ class="text-accent"
+ href={`/tags/${tag}/rss.xml`}
+ target="_blank"
+ title={`RSS feed for ${tag}`}
+ >
+ <span class="sr-only">RSS feed</span>
+ <Icon aria-hidden="true" class="h-6 w-6" focusable="false" name="mdi:rss" />
+ </a>
+ </h1>
+ <div class="prose prose-sm prose-cactus mb-8 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>
- ))
+ page.data.map((item) =>
+ item.collection === "post" ? (
+ <li class="grid gap-2 sm:grid-cols-[auto_1fr]">
+ <PostPreview as="h2" post={item} />
+ </li>
+ ) : (
+ <li>
+ <Note note={item} as="h2" isPreview />
+ </li>
+ ),
+ )
}
</ul>
<Pagination {...paginationProps} />
diff --git a/src/pages/tags/[tag]/rss.xml.ts b/src/pages/tags/[tag]/rss.xml.ts
new file mode 100644
index 0000000..3fc8ff3
--- /dev/null
+++ b/src/pages/tags/[tag]/rss.xml.ts
@@ -0,0 +1,69 @@
+import { getCollection } from "astro:content";
+import rss from "@astrojs/rss";
+import type { APIContext } from "astro";
+import { getAllPosts, getUniqueTags } from "@/data/post";
+import { siteConfig } from "@/site.config";
+import { getUniqueMicroTags } from "@/utils/micro";
+
+export async function getStaticPaths() {
+ // Get all posts (including archived) and micro posts to extract all possible tags
+ const allPosts = await getAllPosts(true, true); // Include archived and micro
+ const allMicro = await getCollection("micro", ({ data }) => data.tags?.includes("micro")).catch(
+ () => [],
+ );
+
+ // Get unique tags from both collections
+ const postTags = getUniqueTags(allPosts);
+ const microTags = getUniqueMicroTags(allMicro);
+ const allTags = [...new Set([...postTags, ...microTags])];
+
+ return allTags.map((tag) => ({
+ params: { tag },
+ }));
+}
+
+export const GET = async (context: APIContext) => {
+ const { tag } = context.params;
+
+ if (!tag) {
+ throw new Error("Tag parameter is required");
+ }
+
+ // Get posts with this tag (include archived and micro)
+ const allPosts = await getAllPosts(true, true);
+ const postsWithTag = allPosts.filter((post) => post.data.tags.includes(tag));
+
+ // Get micro posts with this tag
+ const allMicro = await getCollection("micro", ({ data }) => data.tags?.includes("micro")).catch(
+ () => [],
+ );
+ const microWithTag = allMicro.filter((micro) => micro.data.tags?.includes(tag));
+
+ // Combine and sort chronologically
+ const allItems = [
+ ...postsWithTag.map((post) => ({
+ title: post.data.title,
+ description: post.data.description,
+ pubDate: post.data.publishDate,
+ link: `posts/${post.id}/`,
+ content: undefined,
+ })),
+ ...microWithTag.map((micro) => ({
+ title: micro.data.title,
+ description: micro.data.description,
+ pubDate: micro.data.publishDate,
+ link: `micro/${micro.id}/`,
+ content: micro.rendered?.html || micro.body || "",
+ })),
+ ].sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime());
+
+ const site = context.site || import.meta.env.SITE;
+
+ return rss({
+ title: `${siteConfig.title} - ${tag}`,
+ description: `Posts tagged with ${tag}`,
+ site,
+ items: allItems,
+ customData: `<atom:link href="${site}tags/${tag}/rss.xml" rel="self" type="application/rss+xml" xmlns:atom="http://www.w3.org/2005/Atom" />`,
+ });
+};
diff --git a/src/pages/tags/index.astro b/src/pages/tags/index.astro
index 376087f..2c7b07b 100644
--- a/src/pages/tags/index.astro
+++ b/src/pages/tags/index.astro
@@ -1,9 +1,29 @@
---
+import { getCollection } from "astro:content";
import { getAllPosts, getUniqueTagsWithCount } from "@/data/post";
import PageLayout from "@/layouts/Base.astro";
+import { getUniqueMicroTagsWithCount } from "@/utils/micro";
-const allPostsIncludingArchived = await getAllPosts(true);
-const allTags = getUniqueTagsWithCount(allPostsIncludingArchived);
+const allPostsIncludingArchived = await getAllPosts(true, true); // Include archived and micro
+const allMicro = await getCollection("micro", ({ data }) => data.tags?.includes("micro")).catch(
+ () => [],
+);
+
+// Get tags with counts from both collections
+const postTags = getUniqueTagsWithCount(allPostsIncludingArchived);
+const microTags = getUniqueMicroTagsWithCount(allMicro);
+
+// Merge tags and sum counts
+const tagMap = new Map<string, number>();
+for (const [tag, count] of postTags) {
+ tagMap.set(tag, count);
+}
+for (const [tag, count] of microTags) {
+ tagMap.set(tag, (tagMap.get(tag) || 0) + count);
+}
+
+// Convert back to array and sort by count
+const allTags = Array.from(tagMap.entries()).sort((a, b) => b[1] - a[1]);
const meta = {
description: "A list of all the topics I've written about in my posts",
diff --git a/src/site.config.ts b/src/site.config.ts
index 8c13238..d9b395c 100644
--- a/src/site.config.ts
+++ b/src/site.config.ts
@@ -46,7 +46,7 @@ export const menuLinks: { path: string; title: string }[] = [
title: "Blog",
},
{
- path: "/micro/",
+ path: "/tags/micro/",
title: "Micro",
},
{