119 lines
3.1 KiB
TypeScript
119 lines
3.1 KiB
TypeScript
import { Handlers, PageProps } from "$fresh/server.ts";
|
|
import { listPosts, Post } from "@/libs/post/post.ts";
|
|
import { renderMarkdown } from "@/libs/post/markdown.ts";
|
|
import { format } from "$std/datetime/format.ts";
|
|
import {
|
|
EmojiHandWiving,
|
|
IconFresh,
|
|
IconGitea,
|
|
IconWoodpecker,
|
|
} from "@/components/icons.tsx";
|
|
|
|
function Hero() {
|
|
return (
|
|
<div
|
|
class={`px-4 py-8 mx-auto bg-[#a9def9] -top-20 -mb-20 pt-24 relative`}
|
|
>
|
|
<div class="max-w-screen-md mx-auto">
|
|
<div class="w-full h-full flex flex-col items-center justify-center">
|
|
<h1 class="text-4xl font-bold">
|
|
Welcome to my blog! <EmojiHandWiving class="h-14 w-14" />
|
|
</h1>
|
|
<p class="my-4 text-gray-500 text-lg text-center">
|
|
This is a simple blog built with{" "}
|
|
<a href="https://fresh.deno.dev/">
|
|
<IconFresh class="inline align-top" />
|
|
</a>{" "}
|
|
on{" "}
|
|
<a href="https://gitea.aireone.xyz">
|
|
<IconGitea class="inline align-top mt-0.5" />
|
|
</a>{" "}
|
|
and deployed using{" "}
|
|
<a href="https://woodpecker.aireone.xyz">
|
|
<IconWoodpecker class="inline align-top text-black" />
|
|
</a>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PostItem(
|
|
{ slug, title, publishedAt, html }:
|
|
& Pick<Post, "slug" | "title" | "publishedAt">
|
|
& {
|
|
html: string; // this should be the post.markdown rendered as HTML
|
|
},
|
|
) {
|
|
return (
|
|
<div class="min-w-0 w-full md:max-w-[80ch]">
|
|
<div class="flex items-center justify-between">
|
|
<span class="group">
|
|
<a // href={`/blog/${slug}`}
|
|
class="text-4xl text-gray-900 tracking-tight font-bold md:mt-0 px-4 md:px-0 mb-4">
|
|
{title}
|
|
</a>
|
|
<a
|
|
id={slug}
|
|
class="invisible group-hover:visible ml-1 text-[#0969da] text-4xl font-bold"
|
|
tabindex={-1}
|
|
href={`#${slug}`}
|
|
>
|
|
<span aria-hidden="true">#</span>
|
|
</a>
|
|
</span>
|
|
<span class="text-gray-500">
|
|
{format(publishedAt, "yyyy-MM-dd")}
|
|
</span>
|
|
</div>
|
|
<div
|
|
class="markdown-body mb-8"
|
|
dangerouslySetInnerHTML={{
|
|
__html: html,
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface Props {
|
|
posts: Post[];
|
|
}
|
|
|
|
export const handler: Handlers<Props> = {
|
|
async GET(_, ctx) {
|
|
const posts = await listPosts();
|
|
|
|
return ctx.render({
|
|
posts,
|
|
});
|
|
},
|
|
};
|
|
|
|
export default function Home({ data: { posts } }: PageProps<Props>) {
|
|
return (
|
|
<>
|
|
<Hero />
|
|
<div class="max-w-screen-md mx-auto py-8">
|
|
<h2 class="text-2xl font-bold">Posts</h2>
|
|
{posts.map(({ slug, title, publishedAt, markdown }, index) => (
|
|
<>
|
|
<PostItem
|
|
key={slug}
|
|
slug={slug}
|
|
title={title}
|
|
publishedAt={publishedAt}
|
|
html={renderMarkdown(markdown).html}
|
|
/>
|
|
|
|
{index < posts.length - 1 && (
|
|
<hr class="my-8 border-t border-gray-200" />
|
|
)}
|
|
</>
|
|
))}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|