diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/content.config.ts | 1 | ||||
| -rw-r--r-- | src/data/post.ts | 10 | ||||
| -rw-r--r-- | src/loaders/pleroma.ts | 8 | ||||
| -rw-r--r-- | src/pages/micro/[...page].astro | 10 | ||||
| -rw-r--r-- | src/pages/micro/rss.xml.ts | 6 | ||||
| -rw-r--r-- | src/pages/micro/tags/[tag]/[...page].astro | 71 | ||||
| -rw-r--r-- | src/pages/micro/tags/index.astro | 36 | ||||
| -rw-r--r-- | src/pages/tags/[tag]/[...page].astro | 64 | ||||
| -rw-r--r-- | src/pages/tags/[tag]/rss.xml.ts | 69 | ||||
| -rw-r--r-- | src/pages/tags/index.astro | 24 | ||||
| -rw-r--r-- | src/site.config.ts | 2 |
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}`} - > - #{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", }, { |
