Migrate to Astro 5

This commit is contained in:
Bad Manners 2024-12-03 19:09:09 -03:00
parent 5d701069e9
commit bb1e533a00
No known key found for this signature in database
GPG key ID: 8C88292CCB075609
129 changed files with 1408 additions and 1448 deletions

View file

@ -8,6 +8,6 @@ import GalleryLayout from "@layouts/GalleryLayout.astro";
<meta name="description" content="The requested link could not be found." />
</Fragment>
<h1 class="m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">404 &ndash; Not Found</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<p class="my-4">The requested link could not be found. Make sure that the URL is correct.</p>
</GalleryLayout>

View file

@ -1,6 +1,6 @@
import type { APIRoute, GetStaticPaths } from "astro";
import { getCollection, type CollectionEntry, getEntries } from "astro:content";
import type { PostWebsite } from "@content/config";
import type { PostWebsite } from "src/content.config";
import { t } from "@i18n";
import { formatCopyrightedCharacters } from "@utils/format_copyrighted_characters";
import { markdownToBbcode } from "@utils/markdown_to_bbcode";
@ -36,7 +36,7 @@ type Props = {
};
type Params = {
slug: CollectionEntry<"stories">["slug"];
id: CollectionEntry<"stories">["id"];
};
export const getStaticPaths: GetStaticPaths = async () => {
@ -44,7 +44,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
return [];
}
return (await getCollection("stories")).map((story) => ({
params: { slug: story.slug } satisfies Params,
params: { id: story.id } satisfies Params,
props: { story } satisfies Props,
}));
};
@ -52,6 +52,9 @@ export const getStaticPaths: GetStaticPaths = async () => {
export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) => {
try {
const { lang } = story.data;
if (!story.body) {
throw new Error("Story body cannot be empty");
}
const copyrightedCharacters = await formatCopyrightedCharacters(story.data.copyrightedCharacters);
const authorsList = await getEntries(story.data.authors);
const commissionersList = story.data.commissioners && (await getEntries(story.data.commissioners));

View file

@ -1,13 +1,13 @@
---
import type { GetStaticPaths } from "astro";
import { type CollectionEntry, getCollection } from "astro:content";
import { type CollectionEntry, getCollection, render } from "astro:content";
import { PUBLISH_DRAFTS } from "astro:env/server";
import BlogPostLayout from "@layouts/BlogPostLayout.astro";
type Props = CollectionEntry<"blog">;
type Params = {
slug: CollectionEntry<"blog">["slug"];
id: CollectionEntry<"blog">["id"];
};
export const getStaticPaths: GetStaticPaths = async () => {
@ -15,13 +15,13 @@ export const getStaticPaths: GetStaticPaths = async () => {
return posts
.filter((post) => import.meta.env.DEV || PUBLISH_DRAFTS || !post.data.isDraft)
.map((post) => ({
params: { slug: post.slug } satisfies Params,
params: { id: post.id } satisfies Params,
props: post satisfies Props,
}));
};
const post = Astro.props;
const { Content } = await post.render();
const { Content } = await render(post);
---
<BlogPostLayout {...post.data}>

View file

@ -1,6 +1,6 @@
import rss from "@astrojs/rss";
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
import { getCollection, render } from "astro:content";
import { blogFeedItem, type EntryWithPubDate } from "@utils/feed";
const MAX_ITEMS = 8;
@ -22,7 +22,7 @@ export const GET: APIRoute = async ({ site }) => {
xmlns: { atom: "http://www.w3.org/2005/Atom" },
customData: `<link href="${new URL("blog", site)}" rel="alternate" type="text/html" /><atom:link href="${new URL("blog/feed.xml", site)}" rel="self" type="application/rss+xml" />`,
items: await Promise.all(
posts.map(async ({ data, slug, render }) => blogFeedItem(site, data, slug, (await render()).Content)),
posts.map(async (post) => blogFeedItem(site, post.data, post.id, (await render(post)).Content)),
),
});
};

View file

@ -25,7 +25,7 @@ const posts = await Promise.all(
<meta name="description" content="A collection of blog posts written by Bad Manners." />
</Fragment>
<h1 class="p-name m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Blog</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<div class="my-4 flex w-full">
<p class="p-summary grow">Whatever has been rattling in my head as of late.</p>
<a class="u-url text-link ml-2 p-1 md:mr-5" href="/blog/feed.xml" rel="alternate" title="RSS feed" data-tooltip>
@ -39,7 +39,7 @@ const posts = await Promise.all(
<li class="h-entry">
<a
class="u-url text-link hover:underline focus:underline"
href={`/blog/${post.slug}`}
href={`/blog/${post.id}`}
title={markdownToPlaintext(post.data.description)}
data-tooltip
>

View file

@ -1,6 +1,6 @@
import rss from "@astrojs/rss";
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
import { getCollection, render } from "astro:content";
import { blogFeedItem, gameFeedItem, storyFeedItem, type EntryWithPubDate } from "@utils/feed";
const MAX_ITEMS = 8;
@ -36,17 +36,17 @@ export const GET: APIRoute = async ({ site }) => {
customData: `<link href="${site}" rel="alternate" type="text/html" /><atom:link href="${new URL("feed.xml", site)}" rel="self" type="application/rss+xml" />`,
items: await Promise.all(
[
stories.map(({ data, slug, render }) => ({
date: data.pubDate,
fn: async () => storyFeedItem(site, data, slug, (await render()).Content),
stories.map((story) => ({
date: story.data.pubDate,
fn: async () => storyFeedItem(site, story.data, story.id, (await render(story)).Content),
})),
games.map(({ data, slug, render }) => ({
date: data.pubDate,
fn: async () => gameFeedItem(site, data, slug, (await render()).Content),
games.map((game) => ({
date: game.data.pubDate,
fn: async () => gameFeedItem(site, game.data, game.id, (await render(game)).Content),
})),
posts.map(({ data, slug, render }) => ({
date: data.pubDate,
fn: async () => blogFeedItem(site, data, slug, (await render()).Content),
posts.map((post) => ({
date: post.data.pubDate,
fn: async () => blogFeedItem(site, post.data, post.id, (await render(post)).Content),
})),
]
.flat()

View file

@ -1,13 +1,13 @@
---
import type { GetStaticPaths } from "astro";
import { type CollectionEntry, getCollection } from "astro:content";
import { type CollectionEntry, getCollection, render } from "astro:content";
import GameLayout from "@layouts/GameLayout.astro";
import { PUBLISH_DRAFTS } from "astro:env/server";
type Props = CollectionEntry<"games">;
type Params = {
slug: CollectionEntry<"games">["slug"];
id: CollectionEntry<"games">["id"];
};
export const getStaticPaths: GetStaticPaths = async () => {
@ -15,13 +15,13 @@ export const getStaticPaths: GetStaticPaths = async () => {
return games
.filter((game) => import.meta.env.DEV || PUBLISH_DRAFTS || !game.data.isDraft)
.map((game) => ({
params: { slug: game.slug } satisfies Params,
params: { id: game.id } satisfies Params,
props: game satisfies Props,
}));
};
const game = Astro.props;
const { Content } = await game.render();
const { Content } = await render(game);
---
<GameLayout {...game.data}>

View file

@ -1,6 +1,6 @@
import rss from "@astrojs/rss";
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
import { getCollection, render } from "astro:content";
import { gameFeedItem, type EntryWithPubDate } from "@utils/feed";
const MAX_ITEMS = 8;
@ -9,7 +9,7 @@ export const GET: APIRoute = async ({ site }) => {
if (!site) {
throw new Error("site is required.");
}
const stories = (
const games = (
(await getCollection("games", (game) => !game.data.isDraft && game.data.pubDate)) as EntryWithPubDate<"games">[]
)
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
@ -22,7 +22,7 @@ export const GET: APIRoute = async ({ site }) => {
xmlns: { atom: "http://www.w3.org/2005/Atom" },
customData: `<link href="${new URL("games", site)}" rel="alternate" type="text/html" /><atom:link href="${new URL("games/feed.xml", site)}" rel="self" type="application/rss+xml" />`,
items: await Promise.all(
stories.map(async ({ data, slug, render }) => gameFeedItem(site, data, slug, (await render()).Content)),
games.map(async (game) => gameFeedItem(site, game.data, game.id, (await render(game)).Content)),
),
});
};

View file

@ -24,7 +24,7 @@ const games = await Promise.all(
<meta name="description" content="A safe vore videogame created by Bad Manners." />
</Fragment>
<h1 class="p-name m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Games</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<div class="my-4 flex w-full">
<p class="p-summary grow">A game that I've gone and done.</p>
<a class="u-url text-link ml-2 p-1 md:mr-5" href="/games/feed.xml" rel="alternate" title="RSS feed" data-tooltip>
@ -38,7 +38,7 @@ const games = await Promise.all(
<li class="h-entry">
<a
class="u-url text-link hover:underline focus:underline"
href={`/games/${game.slug}`}
href={`/games/${game.id}`}
title={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
data-tooltip
>

View file

@ -68,7 +68,7 @@ async function storyEntry(story: EntryWithPubDate<"stories">): Promise<LatestIte
type: "Story",
isAgeRestricted: story.data.isAgeRestricted,
thumbnail: story.data.thumbnail,
href: `/stories/${story.slug}`,
href: `/stories/${story.id}`,
title: story.data.title,
authors: await getEntries(story.data.authors),
lang: story.data.lang,
@ -82,7 +82,7 @@ async function gameEntry(game: EntryWithPubDate<"games">): Promise<LatestItemsEn
type: "Game",
isAgeRestricted: game.data.isAgeRestricted,
thumbnail: game.data.thumbnail,
href: `/games/${game.slug}`,
href: `/games/${game.id}`,
title: game.data.title,
authors: await getEntries(game.data.authors),
lang: game.data.lang,
@ -96,7 +96,7 @@ async function blogEntry(post: EntryWithPubDate<"blog">): Promise<LatestItemsEnt
type: "Blog post",
isAgeRestricted: post.data.isAgeRestricted,
thumbnail: post.data.thumbnail,
href: `/blog/${post.slug}`,
href: `/blog/${post.id}`,
title: post.data.title,
authors: await getEntries(post.data.authors),
lang: post.data.lang,
@ -157,7 +157,7 @@ if (featuredItems.length > MAX_FEATURED_ITEMS) {
<meta name="description" content="Find stories, games, and blog posts made by Bad Manners." />
</Fragment>
<h1 class="p-name m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Gallery</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<div class="p-summary">
<div class="my-4 flex">
<p class="grow">

View file

@ -33,6 +33,6 @@ const bundlePath = `${import.meta.env.BASE_URL}pagefind/`;
<meta name="description" content="Search for stories, games, and blog posts made by Bad Manners." />
</Fragment>
<h1 class="m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Search</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<div id="search" class="pagefind-ui pagefind-init my-4" data-pagefind-ui data-bundle-path={bundlePath}></div>
</GalleryLayout>

View file

@ -1,6 +1,6 @@
---
import type { GetStaticPaths } from "astro";
import { type CollectionEntry, getCollection } from "astro:content";
import { type CollectionEntry, getCollection, render } from "astro:content";
import getReadingTime from "reading-time";
import StoryLayout from "@layouts/StoryLayout.astro";
import { PUBLISH_DRAFTS } from "astro:env/server";
@ -8,7 +8,7 @@ import { PUBLISH_DRAFTS } from "astro:env/server";
type Props = CollectionEntry<"stories">;
type Params = {
slug: CollectionEntry<"stories">["slug"];
id: CollectionEntry<"stories">["id"];
};
export const getStaticPaths: GetStaticPaths = async () => {
@ -16,22 +16,25 @@ export const getStaticPaths: GetStaticPaths = async () => {
return stories
.filter((story) => import.meta.env.DEV || PUBLISH_DRAFTS || !story.data.isDraft)
.map((story) => ({
params: { slug: story.slug } satisfies Params,
params: { id: story.id } satisfies Params,
props: story satisfies Props,
}));
};
const story = Astro.props;
if (!story.body) {
throw new Error("Story body cannot be empty");
}
const readingTime = getReadingTime(story.body);
if (story.data.wordCount && Math.abs(story.data.wordCount - readingTime.words) >= 150) {
console.warn(
`WARNING: "wordCount" differs greatly from actual word count for published story ` +
`${story.data.title} ("${story.slug}") ` +
`${story.data.title} ("${story.id}") ` +
`(expected ~${story.data.wordCount}, found ${readingTime.words})`,
);
}
const { Content } = await story.render();
const { Content } = await render(story);
---
<StoryLayout {...story.data}>

View file

@ -35,7 +35,7 @@ const totalPages = Math.ceil(page.total / page.size);
<meta name="description" content="Safe vore stories written by Bad Manners." />
</Fragment>
<h1 class="p-name m-2 grow text-3xl font-semibold text-stone-800 dark:text-stone-100">Stories</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<div class="my-4 flex">
<p class="p-summary grow">The bulk of my content!</p>
<a class="u-url text-link ml-2 p-1 md:mr-5" href="/stories/feed.xml" rel="alternate" title="RSS feed" data-tooltip>
@ -84,7 +84,7 @@ const totalPages = Math.ceil(page.total / page.size);
<li class="h-entry break-inside-avoid">
<a
class="u-url text-link hover:underline focus:underline"
href={`/stories/${story.slug}`}
href={`/stories/${story.id}`}
title={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
data-tooltip
>

View file

@ -1,6 +1,6 @@
import rss from "@astrojs/rss";
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
import { getCollection, render } from "astro:content";
import { storyFeedItem, type EntryWithPubDate } from "@utils/feed";
const MAX_ITEMS = 8;
@ -25,7 +25,7 @@ export const GET: APIRoute = async ({ site }) => {
xmlns: { atom: "http://www.w3.org/2005/Atom" },
customData: `<link href="${new URL("stories/1", site)}" rel="alternate" type="text/html" /><atom:link href="${new URL("stories/feed.xml", site)}" rel="self" type="application/rss+xml" />`,
items: await Promise.all(
stories.map(async ({ data, slug, render }) => storyFeedItem(site, data, slug, (await render()).Content)),
stories.map(async (story) => storyFeedItem(site, story.data, story.id, (await render(story)).Content)),
),
});
};

View file

@ -14,10 +14,10 @@ const stories = (await getCollection(
(story) => !story.data.isDraft && story.data.pubDate && story.data.series?.id === series.id,
)) as StoryWithPubDate[];
const mainChapters = stories
.filter((story) => story.slug.startsWith("the-lost-of-the-marshes/chapter-"))
.filter((story) => story.id.startsWith("the-lost-of-the-marshes/chapter-"))
.sort((a, b) => a.data.pubDate.getTime() - b.data.pubDate.getTime());
const bonusChapters = stories
.filter((story) => story.slug.startsWith("the-lost-of-the-marshes/bonus-"))
.filter((story) => story.id.startsWith("the-lost-of-the-marshes/bonus-"))
.sort((a, b) => a.data.pubDate.getTime() - b.data.pubDate.getTime());
const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summary);
---
@ -31,7 +31,7 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
/>
</Fragment>
<h1 class="p-name m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">{series.data.name}</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<p class="p-summary my-4">
This is the main hub for the story of Quince, Nikili, and Suu, as well as all bonus content.
</p>
@ -59,7 +59,7 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
.filter((story) => story.data.summary)
.map((story) => (
<li class="my-2">
<a class="text-link underline" href={`/stories/${story.slug}`}>
<a class="text-link underline" href={`/stories/${story.id}`}>
{story.data.shortTitle || story.data.title}:
</a>
<span>{story.data.summary}</span>
@ -72,7 +72,7 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
{
mainChapters.map((story) => (
<li class="h-entry break-inside-avoid">
<a class="u-url text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
<a class="u-url text-link hover:underline focus:underline" href={`/stories/${story.id}`}>
{story.data.thumbnail ? (
<Image
class="u-photo w-48"
@ -106,7 +106,7 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
{
bonusChapters.map((story) => (
<li class="h-entry break-inside-avoid">
<a class="u-url text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
<a class="u-url text-link hover:underline focus:underline" href={`/stories/${story.id}`}>
{story.data.thumbnail ? (
<Image
class="u-photo w-48"

View file

@ -81,7 +81,7 @@ if (uncategorizedTagsSet.size > 0) {
<meta name="description" content="Find all content by Bad Manners with a specific tag." />
</Fragment>
<h1 class="m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">All available tags</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<section class="my-2" aria-labelledby="category-series">
<h2 id="category-series" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">Series</h2>
<ul class="list-disc pl-8">

View file

@ -28,6 +28,6 @@ const { badTag } = Astro.props;
<meta name="robots" content="noindex" />
</Fragment>
<h1 class="p-name m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Works tagged "{badTag}"</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<p class="my-4">No.</p>
</GalleryLayout>

View file

@ -142,7 +142,7 @@ const totalWorksWithTag = t(
<meta name="description" content={`Works by Bad Manners tagged with "${props.tag}".`} />
</Fragment>
<h1 class="p-name m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Works tagged "{props.tag}"</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden="true" />
<div class="my-4">
<Prose>
{description ? <Markdown of={description} /> : null}
@ -167,7 +167,7 @@ const totalWorksWithTag = t(
<li class="h-entry break-inside-avoid">
<a
class="u-url text-link hover:underline focus:underline"
href={`/stories/${story.slug}`}
href={`/stories/${story.id}`}
title={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
data-tooltip
>
@ -238,7 +238,7 @@ const totalWorksWithTag = t(
<li class="h-entry break-inside-avoid">
<a
class="u-url text-link hover:underline focus:underline"
href={`/games/${game.slug}`}
href={`/games/${game.id}`}
title={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
data-tooltip
>
@ -305,7 +305,7 @@ const totalWorksWithTag = t(
<li class="h-entry break-inside-avoid">
<a
class="u-url text-link hover:underline focus:underline"
href={`/blog/${post.slug}`}
href={`/blog/${post.id}`}
title={markdownToPlaintext(post.data.description)}
data-tooltip
>