gallery.badmanners.xyz/src/pages/index.astro

182 lines
6.8 KiB
Text

---
import type { ImageMetadata } from "astro";
import { type CollectionEntry, type CollectionKey, getCollection, getEntries } from "astro:content";
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";
import { IconNoOneUnder18, IconSquareRSS } from "@components/icons";
const MAX_ITEMS = 10;
interface LatestItemsEntry {
type: string;
isAgeRestricted: boolean;
thumbnail?: ImageMetadata;
href: string;
title: string;
lang: Lang;
authors: CollectionEntry<"users">[];
altText: string;
pubDate: Date;
}
type EntryWithPubDate<C extends CollectionKey> = CollectionEntry<C> & { data: { pubDate: Date } };
const stories = (
(await getCollection(
"stories",
(story) => !story.data.isDraft && story.data.pubDate,
)) as EntryWithPubDate<"stories">[]
)
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
.slice(0, MAX_ITEMS);
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())
.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(
[
stories.map((story) => ({
date: story.data.pubDate,
fn: async () =>
({
type: "Story",
isAgeRestricted: story.data.isAgeRestricted,
thumbnail: story.data.thumbnail,
href: `/stories/${story.slug}`,
title: story.data.title,
authors: await getEntries(story.data.authors),
lang: story.data.lang,
altText: t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning),
pubDate: story.data.pubDate,
}) satisfies LatestItemsEntry,
})),
games.map((game) => ({
date: game.data.pubDate,
fn: async () =>
({
type: "Game",
isAgeRestricted: game.data.isAgeRestricted,
thumbnail: game.data.thumbnail,
href: `/games/${game.slug}`,
title: game.data.title,
authors: await getEntries(game.data.authors),
lang: game.data.lang,
altText: t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning),
pubDate: game.data.pubDate,
}) satisfies LatestItemsEntry,
})),
posts.map((post) => ({
date: post.data.pubDate,
fn: async () =>
({
type: "Blog post",
isAgeRestricted: post.data.isAgeRestricted,
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())
.slice(0, MAX_ITEMS)
.map(async (entry) => await entry.fn()),
);
---
<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">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">
<div class="my-4 flex">
<p class="grow">
Hey there, welcome to my corner of the Internet! This is where I'll share all of the safe vore and endosoma
content that I'll make. You can check the latest uploads below, or use the navigation bar to dig through all of
my content.
</p>
<a class="u-url text-link ml-2 p-1 md:mr-5" href="/feed.xml" rel="alternate" title="RSS feed" data-tooltip>
<IconSquareRSS width="2rem" height="2rem" />
<span class="sr-only">RSS feed</span>
</a>
</div>
</div>
<section class="my-2" aria-labelledby="latest-uploads">
<h2 id="latest-uploads" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">Latest uploads</h2>
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
{
latestItems.map((entry) => (
<li class="h-entry break-inside-avoid" lang={entry.lang}>
<a
class="u-url text-link hover:underline focus:underline"
href={entry.href}
title={entry.altText}
data-tooltip
>
{entry.thumbnail ? (
<div class="flex aspect-square max-w-[192px] justify-center">
<Image
loading="eager"
class="u-photo m-auto"
src={entry.thumbnail}
alt={`Thumbnail for ${entry.title}`}
width={192}
/>
</div>
) : null}
<div class="max-w-[192px] text-sm">
<span class="p-name" aria-label="Title">
{entry.title}
</span>
<br />
<span class="italic">
{entry.isAgeRestricted ? (
<span class="inline-block align-middle" aria-label="Age restricted">
<IconNoOneUnder18 width="1.25rem" height="1.25rem" />
</span>
) : null}
<span class="p-category" aria-label="Category">
{entry.type}
</span>{" "}
&ndash;{" "}
<time
class="dt-published"
datetime={entry.pubDate.toISOString().slice(0, 10)}
aria-label="Publish date"
>
{entry.pubDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
</time>
</span>
</div>
</a>
<div class="sr-only select-none">
<p class="p-summary" aria-label="Summary">
{entry.altText}
</p>
<div aria-label="Authors">
<span>{entry.authors.length == 1 ? "Author:" : "Authors:"}</span>
{entry.authors.map((author) => (
<UserComponent rel="author" class="p-author" user={author} lang={entry.lang} />
))}
</div>
</div>
</li>
))
}
</ul>
</section>
</GalleryLayout>