From 52411f6cb9efc10dd683096b34e5c279a11f7e0a Mon Sep 17 00:00:00 2001 From: Dawid Rycerz Date: Tue, 13 Jan 2026 15:31:19 +0100 Subject: Rework how tags are working and make them native --- src/content.config.ts | 1 + src/data/post.ts | 10 +++-- src/loaders/pleroma.ts | 8 ++-- src/pages/micro/[...page].astro | 10 +++-- src/pages/micro/rss.xml.ts | 6 ++- src/pages/micro/tags/[tag]/[...page].astro | 71 ------------------------------ src/pages/micro/tags/index.astro | 36 --------------- src/pages/tags/[tag]/[...page].astro | 64 ++++++++++++++++++++------- src/pages/tags/[tag]/rss.xml.ts | 69 +++++++++++++++++++++++++++++ src/pages/tags/index.astro | 24 +++++++++- src/site.config.ts | 2 +- 11 files changed, 163 insertions(+), 138 deletions(-) delete mode 100644 src/pages/micro/tags/[tag]/[...page].astro delete mode 100644 src/pages/micro/tags/index.astro create mode 100644 src/pages/tags/[tag]/rss.xml.ts 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[]> { +/** filter out draft posts based on the environment and optionally archived posts and micro posts */ +export async function getAllPosts( + includeArchived = false, + includeMicro = false, +): Promise[]> { 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(/(? { 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 = {

Micro - + Browse tags - + RSS feed 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; - -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, - }, - }), -}; ---- - - - -

Micro posts about {tag}

-
    - { - page.data.map((note) => ( -
  • - -
  • - )) - } -
- -
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", -}; ---- - - -

Micro Tags

-
    - { - allTags.map(([tag, val]) => ( -
  • - - #{tag} - - - - {val} Post{val > 1 && "s"} - -
  • - )) - } -
-
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 = {
  • {tag}
  • -

    {tagMeta?.data.title ?? `Posts about ${tag}`}

    -
    +

    + {tagMeta?.data.title ?? `${tagDisplay} posts`} + + RSS feed + +

    +
    {tagMeta?.data.description &&

    {tagMeta.data.description}

    } {TagContent && }
      { - page.data.map((p) => ( -
    • - -
    • - )) + page.data.map((item) => + item.collection === "post" ? ( +
    • + +
    • + ) : ( +
    • + +
    • + ), + ) }
    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: ``, + }); +}; 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(); +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", }, { -- cgit v1.2.3