From 30f24e137ee2d1acec11a0da61e134f512ee02dd Mon Sep 17 00:00:00 2001 From: yyamashita Date: Fri, 19 Jun 2026 15:18:55 +0900 Subject: Initial implementation of microblog Markdown-based personal microblog with REST API. XSS protection via sanitize-html on server-side markdown rendering. Co-Authored-By: Claude Sonnet 4.6 --- app/routes/home.tsx | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 app/routes/home.tsx (limited to 'app/routes/home.tsx') diff --git a/app/routes/home.tsx b/app/routes/home.tsx new file mode 100644 index 0000000..51d6f04 --- /dev/null +++ b/app/routes/home.tsx @@ -0,0 +1,87 @@ +import { Form, redirect, useNavigation } from "react-router"; +import type { Route } from "./+types/home"; +import { createPost, listPosts } from "~/lib/db.server"; +import { renderMarkdown } from "~/lib/md.server"; + +export async function loader() { + const posts = listPosts(); + return { posts: posts.map((p) => ({ ...p, html: renderMarkdown(p.content) })) }; +} + +export async function action({ request }: Route.ActionArgs) { + const form = await request.formData(); + const content = String(form.get("content") ?? "").trim(); + + if (!content) { + return { error: "内容を入力してください", content }; + } + if (content.length > 10000) { + return { error: "投稿が長すぎます(最大10,000文字)", content }; + } + + createPost({ id: crypto.randomUUID(), content }); + return redirect("/"); +} + +export default function Home({ loaderData, actionData }: Route.ComponentProps) { + const { posts } = loaderData; + const navigation = useNavigation(); + const isSubmitting = navigation.state === "submitting"; + + return ( +
+
+

log

+
+ +
+
+