feat: complete v0.3 reading and discovery experience
This commit is contained in:
+29
-3
@@ -1,3 +1,29 @@
|
||||
import { notFound, redirect } from "next/navigation"; import { db } from "@/lib/db"; import { getSession } from "@/lib/auth"; import { Attachments } from "@/app/components/attachments";
|
||||
export const dynamic="force-dynamic";
|
||||
export default async function PostPage({params}:{params:Promise<{id:string}>}){const {id:rawId}=await params;const id=Number(rawId);const post=db.prepare('SELECT p.*,u.username,s.name,s.base_url AS source_base_url FROM posts p JOIN users u ON u.id=p.author_id LEFT JOIN sources s ON s.id=p.source_id WHERE p.id=?').get(id) as any;if(!post||post.hidden)notFound();const user=await getSession();if(post.visibility!=='PUBLIC'&&post.author_id!==user?.id)redirect('/');const comments=db.prepare('SELECT c.*,u.username FROM comments c JOIN users u ON u.id=c.author_id WHERE c.post_id=? AND c.hidden=0 ORDER BY c.created_at').all(id) as any[];const reactions=db.prepare('SELECT emoji,count(*) count FROM reactions WHERE post_id=? GROUP BY emoji').all(id) as any[];return <article><p className="meta">@{post.username} · {post.name||'Hub'} · {new Date(post.created_at).toLocaleString('zh-TW')}</p><pre className="card">{post.content}</pre><Attachments json={post.attachments_json} sourceBaseUrl={post.source_base_url}/><section className="row">{reactions.map((r:any)=><span className="tag" key={r.emoji}>{r.emoji} {r.count}</span>)}{user&&['👍','❤️','🎉','🤔'].map(emoji=><form action="/api/reactions" method="post" key={emoji}><input type="hidden" name="postId" value={id}/><input type="hidden" name="emoji" value={emoji}/><button>{emoji}</button></form>)}</section><section><h2>留言</h2>{user?<form action="/api/comments" method="post"><input type="hidden" name="postId" value={id}/><textarea name="content" required placeholder="在 Hub 留下留言"/><button>送出留言</button></form>:<p>請先登入以留言或表情回應。</p>}{comments.map(c=><div className="card" key={c.id}><strong>@{c.username}</strong><p>{c.content}</p><span className="meta">{new Date(c.created_at).toLocaleString('zh-TW')}</span></div>)}</section></article>}
|
||||
import type { Metadata } from "next";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { db } from "@/lib/db";
|
||||
import { getSession } from "@/lib/auth";
|
||||
import { Attachments } from "@/app/components/attachments";
|
||||
import { Markdown } from "@/app/components/markdown";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
|
||||
const { id: rawId } = await params; const post = db.prepare("SELECT p.content,p.hidden,p.visibility,u.username,s.name FROM posts p JOIN users u ON u.id=p.author_id LEFT JOIN sources s ON s.id=p.source_id WHERE p.id=?").get(Number(rawId)) as { content: string; hidden: number; visibility: string; username: string; name: string | null } | undefined;
|
||||
if (!post || post.hidden || post.visibility !== "PUBLIC") return { title: "找不到貼文" };
|
||||
const description = post.content.replace(/\s+/g, " ").slice(0, 160);
|
||||
return { title: `@${post.username} 的貼文|Mebbling`, description, openGraph: { title: `@${post.username}${post.name ? ` · ${post.name}` : ""}|Mebbling`, description, type: "article" } };
|
||||
}
|
||||
|
||||
export default async function PostPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id: rawId } = await params; const id = Number(rawId);
|
||||
const post = db.prepare("SELECT p.*,u.username,s.name,s.base_url AS source_base_url FROM posts p JOIN users u ON u.id=p.author_id LEFT JOIN sources s ON s.id=p.source_id WHERE p.id=?").get(id) as any;
|
||||
if (!post || post.hidden) notFound(); const user = await getSession(); if (post.visibility !== "PUBLIC" && post.author_id !== user?.id) redirect("/");
|
||||
if (user && post.visibility === "PUBLIC") db.prepare("INSERT INTO reading_history(user_id,post_id) VALUES(?,?) ON CONFLICT(user_id,post_id) DO UPDATE SET last_read_at=CURRENT_TIMESTAMP").run(user.id, id);
|
||||
const bookmark = user ? db.prepare("SELECT kind FROM bookmarks WHERE user_id=? AND post_id=?").get(user.id, id) as { kind: string } | undefined : undefined;
|
||||
const comments = db.prepare("SELECT c.*,u.username FROM comments c JOIN users u ON u.id=c.author_id WHERE c.post_id=? AND c.hidden=0 ORDER BY c.created_at").all(id) as any[];
|
||||
const reactions = db.prepare("SELECT emoji,count(*) count FROM reactions WHERE post_id=? GROUP BY emoji").all(id) as any[];
|
||||
return <article><p className="meta">@{post.username} · {post.name || "Hub"} · {new Date(post.created_at).toLocaleString("zh-TW")}</p><section className="card"><Markdown content={post.content} /></section><Attachments json={post.attachments_json} sourceBaseUrl={post.source_base_url} />
|
||||
<section className="row">{reactions.map((reaction: any) => <span className="tag" key={reaction.emoji}>{reaction.emoji} {reaction.count}</span>)}{user && <><form action="/api/bookmarks" method="post"><input type="hidden" name="postId" value={id} /><input type="hidden" name="kind" value="saved" /><button>{bookmark?.kind === "saved" ? "取消收藏" : "收藏"}</button></form><form action="/api/bookmarks" method="post"><input type="hidden" name="postId" value={id} /><input type="hidden" name="kind" value="later" /><button>{bookmark?.kind === "later" ? "取消稍後閱讀" : "稍後閱讀"}</button></form></>}{user && ["👍", "❤️", "🎉", "🤔"].map((emoji) => <form action="/api/reactions" method="post" key={emoji}><input type="hidden" name="postId" value={id} /><input type="hidden" name="emoji" value={emoji} /><button>{emoji}</button></form>)}</section>
|
||||
<section><h2>留言</h2>{user ? <form action="/api/comments" method="post"><input type="hidden" name="postId" value={id} /><textarea name="content" required placeholder="在 Hub 留下留言" /><button>送出留言</button></form> : <p>請先登入以留言或表情回應。</p>}{comments.map((comment) => <div className="card" key={comment.id}><strong>@{comment.username}</strong><p>{comment.content}</p><span className="meta">{new Date(comment.created_at).toLocaleString("zh-TW")}</span></div>)}</section>
|
||||
</article>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user