Start creating blog posts
This commit is contained in:
parent
9ff1986adc
commit
4a3ee88f77
35 changed files with 1015 additions and 157 deletions
79
src/pages/blog.astro
Normal file
79
src/pages/blog.astro
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
import { getCollection, getEntries, type CollectionEntry } from "astro:content";
|
||||
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
||||
import { t } from "../i18n";
|
||||
import UserComponent from "../components/UserComponent.astro";
|
||||
import { markdownToPlaintext } from "../utils/markdown_to_plaintext";
|
||||
|
||||
type PostWithPubDate = CollectionEntry<"blog"> & { data: { pubDate: Date } };
|
||||
|
||||
const posts = await Promise.all(
|
||||
((await getCollection("blog", (post) => !post.data.isDraft && post.data.pubDate)) as PostWithPubDate[])
|
||||
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||||
.map(async (post) => ({
|
||||
...post,
|
||||
authors: await getEntries(post.data.authors),
|
||||
})),
|
||||
);
|
||||
---
|
||||
|
||||
<GalleryLayout pageTitle="Blog" class="h-feed">
|
||||
<meta slot="head" property="og:description" content="Bad Manners || A game that I've gone and done." />
|
||||
<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" />
|
||||
<p class="p-summary my-4">Posts on whatever has been rattling in my head as of late.</p>
|
||||
<ul class="my-6 flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
|
||||
{
|
||||
posts.map((post, i) => (
|
||||
<li class="h-entry" lang={post.data.lang}>
|
||||
<a
|
||||
class="u-url text-link hover:underline focus:underline"
|
||||
href={`/blog/${post.slug}`}
|
||||
title={markdownToPlaintext(post.data.description)}
|
||||
data-tooltip
|
||||
>
|
||||
{post.data.thumbnail ? (
|
||||
<div class="flex aspect-[630/500] max-w-[288px] justify-center">
|
||||
<Image
|
||||
loading={i < 10 ? "eager" : "lazy"}
|
||||
class="u-photo m-auto"
|
||||
src={post.data.thumbnail}
|
||||
alt={`Thumbnail for ${post.data.title}`}
|
||||
width={288}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div class="max-w-[288px] text-sm">
|
||||
<span class="p-name" aria-label="Title">
|
||||
{post.data.title}
|
||||
</span>
|
||||
<br />
|
||||
<time
|
||||
class="dt-published italic"
|
||||
datetime={post.data.pubDate.toISOString().slice(0, 10)}
|
||||
aria-label="Publish date"
|
||||
>
|
||||
{post.data.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
|
||||
</time>
|
||||
</div>
|
||||
</a>
|
||||
<div class="sr-only select-none">
|
||||
<p class="p-category" aria-label="Category">
|
||||
Blog post
|
||||
</p>
|
||||
<p class="p-summary" aria-label="Summary">
|
||||
{post.data.description}
|
||||
</p>
|
||||
<div aria-label="Authors">
|
||||
<span>{post.authors.length == 1 ? "Author:" : "Authors:"}</span>
|
||||
{post.authors.map((author) => (
|
||||
<UserComponent rel="author" class="p-author" user={author} lang={post.data.lang} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</GalleryLayout>
|
||||
29
src/pages/blog/[...slug].astro
Normal file
29
src/pages/blog/[...slug].astro
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
import type { GetStaticPaths } from "astro";
|
||||
import { type CollectionEntry, getCollection } from "astro:content";
|
||||
import { PUBLISH_DRAFTS } from "astro:env/server";
|
||||
import BlogLayout from "../../layouts/BlogLayout.astro";
|
||||
|
||||
type Props = CollectionEntry<"blog">;
|
||||
|
||||
type Params = {
|
||||
slug: CollectionEntry<"blog">["slug"];
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
const posts = await getCollection("blog");
|
||||
return posts
|
||||
.filter((post) => import.meta.env.DEV || PUBLISH_DRAFTS || !post.data.isDraft)
|
||||
.map((post) => ({
|
||||
params: { slug: post.slug } satisfies Params,
|
||||
props: post satisfies Props,
|
||||
}));
|
||||
};
|
||||
|
||||
const post = Astro.props;
|
||||
const { Content } = await post.render();
|
||||
---
|
||||
|
||||
<BlogLayout {...post.data}>
|
||||
<Content />
|
||||
</BlogLayout>
|
||||
|
|
@ -106,6 +106,35 @@ async function gameFeedItem(
|
|||
};
|
||||
}
|
||||
|
||||
async function blogFeedItem(
|
||||
site: URL | undefined,
|
||||
data: EntryWithPubDate<"blog">["data"],
|
||||
slug: CollectionEntry<"blog">["slug"],
|
||||
body: string,
|
||||
): Promise<FeedItem> {
|
||||
return {
|
||||
title: `New blog post! "${data.title}"`,
|
||||
pubDate: toNoonUTCDate(data.pubDate),
|
||||
link: `/blog/${slug}`,
|
||||
description: markdownToPlaintext(await qualifyLocalURLsInMarkdown(data.description, data.lang, site)).replaceAll(
|
||||
/[\n ]+/g,
|
||||
" ",
|
||||
),
|
||||
categories: ["blog post"],
|
||||
commentsUrl: data.posts.mastodon?.link,
|
||||
content: sanitizeHtml(
|
||||
`<h1>${data.title}</h1>` +
|
||||
`<p>${t(
|
||||
data.lang,
|
||||
"story/authors",
|
||||
(await getEntries(data.authors)).map((author) => getLinkForUser(author, data.lang)),
|
||||
)}</p>` +
|
||||
`<hr>${await markdown(await qualifyLocalURLsInMarkdown(data.description, data.lang, site))}` +
|
||||
`<hr>${await markdown(body)}`,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export const GET: APIRoute = async ({ site }) => {
|
||||
const stories = (
|
||||
(await getCollection(
|
||||
|
|
@ -120,6 +149,11 @@ export const GET: APIRoute = async ({ site }) => {
|
|||
)
|
||||
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||||
.slice(0, MAX_ITEMS);
|
||||
const posts = (
|
||||
(await getCollection("blog", (post) => !post.data.isDraft && post.data.pubDate)) as EntryWithPubDate<"blog">[]
|
||||
)
|
||||
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||||
.slice(0, MAX_ITEMS);
|
||||
|
||||
return rss({
|
||||
title: "Gallery | Bad Manners",
|
||||
|
|
@ -135,6 +169,10 @@ export const GET: APIRoute = async ({ site }) => {
|
|||
date: data.pubDate,
|
||||
fn: () => gameFeedItem(site, data, slug, body),
|
||||
})),
|
||||
posts.map(({ data, slug, body }) => ({
|
||||
date: data.pubDate,
|
||||
fn: () => blogFeedItem(site, data, slug, body),
|
||||
})),
|
||||
]
|
||||
.flat()
|
||||
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const games = await Promise.all(
|
|||
class="u-url text-link hover:underline focus:underline"
|
||||
href={`/games/${game.slug}`}
|
||||
title={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
|
||||
data-clipboard={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
|
||||
data-tooltip
|
||||
>
|
||||
{game.data.thumbnail ? (
|
||||
<div class="flex aspect-[630/500] max-w-[288px] justify-center">
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Image } from "astro:assets";
|
|||
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
||||
import { t, type Lang } from "../i18n";
|
||||
import UserComponent from "../components/UserComponent.astro";
|
||||
import { markdownToPlaintext } from "../utils/markdown_to_plaintext";
|
||||
|
||||
const MAX_ITEMS = 10;
|
||||
|
||||
|
|
@ -34,6 +35,11 @@ const games = (
|
|||
)
|
||||
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||||
.slice(0, MAX_ITEMS);
|
||||
const posts = (
|
||||
(await getCollection("blog", (post) => !post.data.isDraft && post.data.pubDate)) as EntryWithPubDate<"blog">[]
|
||||
)
|
||||
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||||
.slice(0, MAX_ITEMS);
|
||||
|
||||
const latestItems: LatestItemsEntry[] = await Promise.all(
|
||||
[
|
||||
|
|
@ -65,6 +71,20 @@ const latestItems: LatestItemsEntry[] = await Promise.all(
|
|||
pubDate: game.data.pubDate,
|
||||
}) satisfies LatestItemsEntry,
|
||||
})),
|
||||
posts.map((post) => ({
|
||||
date: post.data.pubDate,
|
||||
fn: async () =>
|
||||
({
|
||||
type: "Game",
|
||||
thumbnail: post.data.thumbnail,
|
||||
href: `/blog/${post.slug}`,
|
||||
title: post.data.title,
|
||||
authors: await getEntries(post.data.authors),
|
||||
lang: post.data.lang,
|
||||
altText: markdownToPlaintext(post.data.description),
|
||||
pubDate: post.data.pubDate,
|
||||
}) satisfies LatestItemsEntry,
|
||||
})),
|
||||
]
|
||||
.flat()
|
||||
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||
|
|
@ -75,7 +95,7 @@ const latestItems: LatestItemsEntry[] = await Promise.all(
|
|||
|
||||
<GalleryLayout pageTitle="Gallery" class="h-feed">
|
||||
<meta slot="head" property="og:description" content="Bad Manners || Welcome to my gallery!" />
|
||||
<h1 class="p-name m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Welcome to my gallery!</h1>
|
||||
<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" />
|
||||
<div class="p-summary">
|
||||
<p class="my-4">
|
||||
|
|
@ -105,7 +125,7 @@ const latestItems: LatestItemsEntry[] = await Promise.all(
|
|||
class="u-url text-link hover:underline focus:underline"
|
||||
href={entry.href}
|
||||
title={entry.altText}
|
||||
data-clipboard={entry.altText}
|
||||
data-tooltip
|
||||
>
|
||||
{entry.thumbnail ? (
|
||||
<div class="flex aspect-square max-w-[192px] justify-center">
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ const totalPages = Math.ceil(page.total / page.size);
|
|||
class="u-url text-link hover:underline focus:underline"
|
||||
href={`/stories/${story.slug}`}
|
||||
title={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
|
||||
data-clipboard={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
|
||||
data-tooltip
|
||||
>
|
||||
{story.data.thumbnail ? (
|
||||
<div class="flex aspect-square max-w-[192px] justify-center">
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ if (uncategorizedTagsSet.size > 0) {
|
|||
class="rounded-full bg-bm-300 px-3 py-1 text-sm text-black shadow-sm hover:underline focus:underline dark:bg-bm-600 dark:text-white"
|
||||
href={`/tags/${id}`}
|
||||
title={description}
|
||||
data-clipboard={description}
|
||||
data-tooltip
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue