179 lines
6.2 KiB
Text
179 lines
6.2 KiB
Text
---
|
||
import type { GetStaticPaths, Page } from "astro";
|
||
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 { IconSquareRSS } from "@components/icons";
|
||
|
||
type StoryWithPubDate = CollectionEntry<"stories"> & { data: { pubDate: Date } };
|
||
|
||
type Props = {
|
||
page: Page<StoryWithPubDate & { authors: CollectionEntry<"users">[] }>;
|
||
};
|
||
|
||
export const getStaticPaths: GetStaticPaths = async ({ paginate }) => {
|
||
const stories = await Promise.all(
|
||
((await getCollection("stories", (story) => !story.data.isDraft && story.data.pubDate)) as StoryWithPubDate[])
|
||
.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
|
||
.map(async (story) => ({
|
||
...story,
|
||
authors: await getEntries(story.data.authors),
|
||
})),
|
||
);
|
||
return paginate(stories, { pageSize: 30 }) satisfies { props: Props }[];
|
||
};
|
||
|
||
const { page } = Astro.props;
|
||
const totalPages = Math.ceil(page.total / page.size);
|
||
---
|
||
|
||
<GalleryLayout pageTitle="Stories" class="h-feed">
|
||
<meta slot="head" property="og:description" content={`Bad Manners || ${page.total} stories.`} />
|
||
<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" />
|
||
<div class="my-4 flex">
|
||
<p class="p-summary grow">The bulk of my content!</p>
|
||
<a class="u-url text-link ml-2 mr-10 p-2" href="/stories/feed.xml" rel="alternate" title="RSS feed" data-tooltip>
|
||
<IconSquareRSS width="2rem" height="2rem" />
|
||
<span class="sr-only">RSS feed</span>
|
||
</a>
|
||
</div>
|
||
<p class="text-center font-light text-stone-950 dark:text-white">
|
||
{
|
||
page.start === page.end
|
||
? `Displaying story #${page.start + 1}`
|
||
: `Displaying stories #${page.start + 1}–${page.end + 1}`
|
||
} / {page.total}
|
||
</p>
|
||
<div class="mx-auto mb-6 mt-2 flex w-fit rounded-lg border border-stone-400 dark:border-stone-500">
|
||
{
|
||
page.url.prev && (
|
||
<a
|
||
class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
|
||
rel="prev"
|
||
href={page.url.prev}
|
||
>
|
||
Previous page
|
||
</a>
|
||
)
|
||
}
|
||
{
|
||
[...Array(totalPages).keys()]
|
||
.map((p) => p + 1)
|
||
.map((p) =>
|
||
p === page.currentPage ? (
|
||
<span class="border-r border-stone-400 px-4 py-1 font-semibold text-stone-900 dark:border-stone-500 dark:text-stone-50">
|
||
{p}
|
||
</span>
|
||
) : (
|
||
<a
|
||
class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
|
||
href={p === 1 ? "/stories/" : `/stories/${p}`}
|
||
>
|
||
{p}
|
||
</a>
|
||
),
|
||
)
|
||
}
|
||
{
|
||
page.url.next && (
|
||
<a class="text-link px-2 py-1 underline" rel="next" href={page.url.next}>
|
||
Next page
|
||
</a>
|
||
)
|
||
}
|
||
</div>
|
||
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
|
||
{
|
||
page.data.map((story, i) => (
|
||
<li class="h-entry break-inside-avoid" lang={story.data.lang}>
|
||
<a
|
||
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-tooltip
|
||
>
|
||
{story.data.thumbnail ? (
|
||
<div class="flex aspect-square max-w-[192px] justify-center">
|
||
<Image
|
||
loading={i < 10 ? "eager" : "lazy"}
|
||
class="u-photo m-auto"
|
||
src={story.data.thumbnail}
|
||
alt={`Thumbnail for ${story.data.title}`}
|
||
width={192}
|
||
/>
|
||
</div>
|
||
) : null}
|
||
<div class="max-w-[192px] text-sm">
|
||
<span class="p-name" aria-label="Title">
|
||
{story.data.title}
|
||
</span>
|
||
<br />
|
||
<time
|
||
class="dt-published italic"
|
||
datetime={story.data.pubDate.toISOString().slice(0, 10)}
|
||
aria-label="Publish date"
|
||
>
|
||
{story.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">
|
||
Story
|
||
</p>
|
||
<p class="p-summary" aria-label="Summary">
|
||
{t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
|
||
</p>
|
||
<div aria-label="Authors">
|
||
<span>{story.authors.length == 1 ? "Author:" : "Authors:"}</span>
|
||
{story.authors.map((author) => (
|
||
<UserComponent rel="author" class="p-author" user={author} lang={story.data.lang} />
|
||
))}
|
||
</div>
|
||
</div>
|
||
</li>
|
||
))
|
||
}
|
||
</ul>
|
||
<div class="mx-auto my-6 flex w-fit rounded-lg border border-stone-400 dark:border-stone-500">
|
||
{
|
||
page.url.prev && (
|
||
<a
|
||
class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
|
||
rel="prev"
|
||
href={page.url.prev}
|
||
>
|
||
Previous page
|
||
</a>
|
||
)
|
||
}
|
||
{
|
||
[...Array(totalPages).keys()]
|
||
.map((p) => p + 1)
|
||
.map((p) =>
|
||
p === page.currentPage ? (
|
||
<span class="border-r border-stone-400 px-4 py-1 font-semibold text-stone-900 dark:border-stone-500 dark:text-stone-50">
|
||
{p}
|
||
</span>
|
||
) : (
|
||
<a
|
||
class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500"
|
||
href={p === 1 ? "/stories/" : `/stories/${p}`}
|
||
>
|
||
{p}
|
||
</a>
|
||
),
|
||
)
|
||
}
|
||
{
|
||
page.url.next && (
|
||
<a class="text-link px-2 py-1 underline" rel="next" href={page.url.next}>
|
||
Next page
|
||
</a>
|
||
)
|
||
}
|
||
</div>
|
||
</GalleryLayout>
|