非常好的选择!实现社交功能可以大大提高用户参与度和互动性。我们将添加用户评论和文章分享功能。让我们开始吧:

第二十九部分:实现社交功能(评论和分享)

  1. 首先,我们需要创建一个新的评论表。更新 src/db/schema.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
 
// ... 保留现有的表定义
 
export const comments = sqliteTable("comments", {
  id: integer("id").primaryKey(),
  content: text("content").notNull(),
  postId: integer("post_id").notNull().references(() => posts.id),
  userId: integer("user_id").notNull().references(() => users.id),
  createdAt: integer("created_at", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`),
});
  1. 创建新的迁移文件 src/db/migrations/0013_add_comments_table.ts
export async function up(db) {
  await db.run(sql`
    CREATE TABLE comments (
      id INTEGER PRIMARY KEY,
      content TEXT NOT NULL,
      post_id INTEGER NOT NULL,
      user_id INTEGER NOT NULL,
      created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (post_id) REFERENCES posts(id),
      FOREIGN KEY (user_id) REFERENCES users(id)
    );
  `);
}
 
export async function down(db) {
  await db.run(sql`DROP TABLE IF EXISTS comments;`);
}
  1. 运行迁移:
bun run migrate
  1. 更新 src/routes/posts.ts 文件,添加评论相关的路由:
import { db } from "../db";
import { posts, comments, users } from "../db/schema";
import { eq } from "drizzle-orm";
 
export const postRoutes = new Elysia()
  // ... 保留现有的路由
  .get("/posts/:id", async ({ params, session, render }) => {
    const post = await db.select().from(posts).where(eq(posts.id, parseInt(params.id))).get();
    if (!post) {
      return "Post not found";
    }
 
    const postComments = await db
      .select({
        id: comments.id,
        content: comments.content,
        createdAt: comments.createdAt,
        userName: users.name,
      })
      .from(comments)
      .innerJoin(users, eq(comments.userId, users.id))
      .where(eq(comments.postId, post.id))
      .orderBy(desc(comments.createdAt))
      .all();
 
    if (session.user) {
      await db.insert(userActivities).values({
        userId: session.user.id,
        postId: post.id,
        activityType: "read",
      });
    }
 
    return render("posts/show", { post, comments: postComments, user: session.user });
  })
  .post("/posts/:id/comments", async ({ params, body, session, set }) => {
    if (!session.user) {
      set.status = 401;
      return "Unauthorized";
    }
 
    const { content } = body;
    await db.insert(comments).values({
      content,
      postId: parseInt(params.id),
      userId: session.user.id,
    });
 
    set.redirect = `/posts/${params.id}`;
  });
  1. 更新 src/views/posts/show.eta 文件,添加评论部分:
<% layout('../layouts/main.eta', { title: it.post.title }) %>
 
<h1><%= it.post.title %></h1>
<p><%= it.post.content %></p>
 
<h2>Comments</h2>
<% if (it.comments.length > 0) { %>
  <ul>
    <% it.comments.forEach(comment => { %>
      <li>
        <p><%= comment.content %></p>
        <small>By <%= comment.userName %> on <%= new Date(comment.createdAt).toLocaleString() %></small>
      </li>
    <% }) %>
  </ul>
<% } else { %>
  <p>No comments yet.</p>
<% } %>
 
<% if (it.user) { %>
  <h3>Add a comment</h3>
  <form action="/posts/<%= it.post.id %>/comments" method="POST">
    <textarea name="content" required></textarea>
    <button type="submit">Submit Comment</button>
  </form>
<% } else { %>
  <p>Please <a href="/login">login</a> to leave a comment.</p>
<% } %>
 
<!-- 添加分享按钮 -->
<h3>Share this post</h3>
<button onclick="shareOnFacebook()">Share on Facebook</button>
<button onclick="shareOnTwitter()">Share on Twitter</button>
<button onclick="shareOnLinkedIn()">Share on LinkedIn</button>
 
<script>
function shareOnFacebook() {
  const url = encodeURIComponent(window.location.href);
  const title = encodeURIComponent('<%= it.post.title %>');
  window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}&t=${title}`, '_blank');
}
 
function shareOnTwitter() {
  const url = encodeURIComponent(window.location.href);
  const text = encodeURIComponent('<%= it.post.title %>');
  window.open(`https://twitter.com/intent/tweet?url=${url}&text=${text}`, '_blank');
}
 
function shareOnLinkedIn() {
  const url = encodeURIComponent(window.location.href);
  const title = encodeURIComponent('<%= it.post.title %>');
  window.open(`https://www.linkedin.com/shareArticle?mini=true&url=${url}&title=${title}`, '_blank');
}
</script>
  1. 为了改善用户体验,我们可以使用 AJAX 来提交评论。创建一个新文件 src/public/js/comments.js
document.addEventListener('DOMContentLoaded', () => {
  const commentForm = document.getElementById('comment-form');
  const commentsList = document.getElementById('comments-list');
 
  if (commentForm) {
    commentForm.addEventListener('submit', async (e) => {
      e.preventDefault();
      const formData = new FormData(commentForm);
      const response = await fetch(commentForm.action, {
        method: 'POST',
        body: formData,
        headers: {
          'Accept': 'application/json',
        },
      });
 
      if (response.ok) {
        const comment = await response.json();
        const li = document.createElement('li');
        li.innerHTML = `
          <p>${comment.content}</p>
          <small>By ${comment.userName} on ${new Date(comment.createdAt).toLocaleString()}</small>
        `;
        commentsList.prepend(li);
        commentForm.reset();
      } else {
        alert('Error submitting comment');
      }
    });
  }
});
  1. 更新 src/routes/posts.ts 文件,添加 AJAX 支持:
import { db } from "../db";
import { posts, comments, users } from "../db/schema";
import { eq } from "drizzle-orm";
 
export const postRoutes = new Elysia()
  // ... 保留现有的路由
  .post("/posts/:id/comments", async ({ params, body, session, set, headers }) => {
    if (!session.user) {
      set.status = 401;
      return "Unauthorized";
    }
 
    const { content } = body;
    const [newComment] = await db.insert(comments).values({
      content,
      postId: parseInt(params.id),
      userId: session.user.id,
    }).returning();
 
    if (headers.accept === 'application/json') {
      return {
        id: newComment.id,
        content: newComment.content,
        createdAt: newComment.createdAt,
        userName: session.user.name,
      };
    }
 
    set.redirect = `/posts/${params.id}`;
  });
  1. 更新 src/views/layouts/main.eta 文件,引入新的 JavaScript 文件:
<!DOCTYPE html>
<html lang="en">
<head>
    <!-- ... 其他头部内容 ... -->
    <script src="/js/comments.js"></script>
</head>
<body>
    <!-- ... 页面内容 ... -->
</body>
</html>
  1. 最后,更新 src/views/posts/show.eta 文件,使用 AJAX 提交评论:
<!-- ... 其他内容保持不变 ... -->
 
<% if (it.user) { %>
  <h3>Add a comment</h3>
  <form id="comment-form" action="/posts/<%= it.post.id %>/comments" method="POST">
    <textarea name="content" required></textarea>
    <button type="submit">Submit Comment</button>
  </form>
<% } else { %>
  <p>Please <a href="/login">login</a> to leave a comment.</p>
<% } %>
 
<ul id="comments-list">
  <% it.comments.forEach(comment => { %>
    <li>
      <p><%= comment.content %></p>
      <small>By <%= comment.userName %> on <%= new Date(comment.createdAt).toLocaleString() %></small>
    </li>
  <% }) %>
</ul>
 
<!-- ... 分享按钮和脚本保持不变 ... -->

这些更改实现了以下功能:

  1. 用户可以在文章页面查看和发表评论。
  2. 评论以 AJAX 方式提交,提供更好的用户体验。
  3. 用户可以通过社交媒体按钮轻松分享文章。

主要改进包括:

  • 增加了用户互动性,允许读者参与讨论。
  • 提供了即时反馈,评论提交后立即显示。
  • 通过社交分享功能扩大了文章的潜在受众。

这个实现为博客系统添加了基本的社交功能,大大提升了用户参与度和内容传播能力。用户现在可以更容易地与其他读者互动,并将有趣的文章分享给他们的社交网络。

接下来,我们可以考虑以下方向来进一步完善系统:

  1. 实现评论的回复功能,允许用户回复特定评论。
  2. 添加评论管理功能,允许作者或管理员审核和删除评论。
  3. 实现用户通知系统,当有人回复评论时通知用户。
  4. 添加”喜欢”或”赞”功能,让用户可以快速表达对文章或评论的赞赏。
  5. 实现文章的协作编辑功能。
  6. 添加文章版本控制系统。

你希望继续哪个方向,或者有其他想法吗?