4.10 留言
4.10.1 留言模型设计
我们只需要留言的作者 id、留言内容和关联的文章 id 这几个字段,修改 lib/mongo.js,添加如下代码:
lib/mongo.js
exports.Comment = mongolass.model('Comment', {
author: { type: Mongolass.Types.ObjectId },
content: { type: 'string' },
postId: { type: Mongolass.Types.ObjectId }
});
exports.Post.index({ postId: 1, _id: 1 }).exec();// 通过文章 id 获取该文章下所有留言,按留言创建时间升序
exports.Post.index({ author: 1, _id: 1 }).exec();// 通过用户 id 和留言 id 删除一个留言
4.10.2 显示留言
在实现留言功能之前,我们先让文章页可以显示留言列表。首先创建留言的模板,新建 views/components/comments.ejs,添加如下代码:
views/components/comments.ejs
<div class="ui grid">
<div class="four wide column"></div>
<div class="eight wide column">
<div class="ui segment">
<div class="ui minimal comments">
<h3 class="ui dividing header">留言</h3>
<% comments.forEach(function (comment) { %>
<div class="comment">
<span class="avatar">
<img src="/img/<%= comment.author.avatar %>">
</span>
<div class="content">
<a class="author" href="/posts?author=<%= comment.author._id %>"><%= comment.author.name %></a>
<div class="metadata">
<span class="date"><%= comment.created_at %></span>
</div>
<div class="text"><%- comment.content %></div>
<% if (user && comment.author._id && user._id.toString() === comment.author._id.toString()) { %>
<div class="actions">
<a class="reply" href="/posts/<%= post._id %>/comment/<%= comment._id %>/remove">删除</a>
</div>
<% } %>
</div>
</div>
<% }) %>
<% if (user) { %>
<form class="ui reply form" method="post" action="/posts/<%= post._id %>/comment">
<div class="field">
<textarea name="content"></textarea>
</div>
<input type="submit" class="ui icon button" value="留言" />
</form>
<% } %>
</div>
</div>
</div>
</div>
在文章页引入留言的模板片段,修改 views/post.ejs 为:
views/post.ejs
<%- include('header') %>
<%- include('components/post-content') %>
<%- include('components/comments') %>
<%- include('footer') %>
新建 models/comments.js,添加如下代码:
models/comments.js
var marked = require('marked');
var Comment = require('../lib/mongo').Comment;
// 将 comment 的 content 从 markdown 转换成 html
Comment.plugin('contentToHtml', {
afterFind: function (comments) {
return comments.map(function (comment) {
comment.content = marked(comment.content);
return comment;
});
}
});
module.exports = {
// 创建一个留言
create: function create(comment) {
return Comment.create(comment).exec();
},
// 通过用户 id 和留言 id 删除一个留言
delCommentById: function delCommentById(commentId, author) {
return Comment.remove({ author: author, _id: commentId }).exec();
},
// 通过文章 id 获取该文章下所有留言,按留言创建时间升序
getComments: function getComments(postId) {
return Comment
.find({ postId: postId })
.populate({ path: 'author', model: 'User' })
.sort({ _id: 1 })
.addCreatedAt()
.contentToHtml()
.exec();
},
// 通过文章 id 获取该文章下留言数
getCommentsCount: function getCommentsCount(postId) {
return Comment.count({ postId: postId }).exec();
}
};
小提示:我们让留言也支持了 markdown。
注意:其实通过 commentId 就可以唯一确定并删除一条留言,添加 author 的限制是为了防止用户删除他人的留言。
修改 models/posts.js,在:
models/posts.js
var Post = require('../lib/mongo').Post;
下添加如下代码:
var CommentModel = require('./comments');
// 给 post 添加留言数 commentsCount
Post.plugin('addCommentsCount', {
afterFind: function (posts) {
return Promise.all(posts.map(function (post) {
return CommentModel.getCommentsCount(post._id).then(function (commentsCount) {
post.commentsCount = commentsCount;
return post;
});
}));
},
afterFindOne: function (post) {
if (post) {
return CommentModel.getCommentsCount(post._id).then(function (count) {
post.commentsCount = count;
return post;
});
}
return post;
}
});
在 PostModel 上注册了 addCommentsCount
用来给每篇文章添加留言数 commentsCount
,在 getPostById
和 getPosts
方法里的:
.addCreatedAt()
下添加:
.addCommentsCount()
这样主页和文章页的文章就可以正常显示留言数了。
小提示:虽然目前看起来使用 Mongolass 自定义插件并不能节省代码,反而使代码变多了。Mongolass 插件真正的优势在于:在项目非常庞大时,可通过自定义的插件随意组合(及顺序)实现不同的输出,如上面的
getPostById
需要将取出 markdown 转换成 html,则使用.contentToHtml()
,否则像getRawPostById
则不使用。
修改 routes/posts.js,在:
routes/posts.js
var PostModel = require('../models/posts');
下引入 CommentModel:
var CommentModel = require('../models/comments');
在文章页传入留言列表,将:
// GET /posts/:postId 单独一篇的文章页
router.get('/:postId', function(req, res, next) {
var postId = req.params.postId;
Promise.all([
PostModel.getPostById(postId),// 获取文章信息
PostModel.incPv(postId)// pv 加 1
])
.then(function (result) {
var post = result[0];
if (!post) {
throw new Error('该文章不存在');
}
res.render('post', {
post: post
});
})
.catch(next);
});
修改为:
// GET /posts/:postId 单独一篇的文章页
router.get('/:postId', function(req, res, next) {
var postId = req.params.postId;
Promise.all([
PostModel.getPostById(postId),// 获取文章信息
CommentModel.getComments(postId),// 获取该文章所有留言
PostModel.incPv(postId)// pv 加 1
])
.then(function (result) {
var post = result[0];
var comments = result[1];
if (!post) {
throw new Error('该文章不存在');
}
res.render('post', {
post: post,
comments: comments
});
})
.catch(next);
});
现在刷新文章页试试吧。
4.10.3 发表与删除留言
现在我们来实现发表与删除留言的功能。修改 routes/posts.js,将:
routes/posts.js
// POST /posts/:postId/comment 创建一条留言
router.post('/:postId/comment', checkLogin, function(req, res, next) {
res.send(req.flash());
});
// GET /posts/:postId/comment/:commentId/remove 删除一条留言
router.get('/:postId/comment/:commentId/remove', checkLogin, function(req, res, next) {
res.send(req.flash());
});
修改为:
// POST /posts/:postId/comment 创建一条留言
router.post('/:postId/comment', checkLogin, function(req, res, next) {
var author = req.session.user._id;
var postId = req.params.postId;
var content = req.fields.content;
var comment = {
author: author,
postId: postId,
content: content
};
CommentModel.create(comment)
.then(function () {
req.flash('success', '留言成功');
// 留言成功后跳转到上一页
res.redirect('back');
})
.catch(next);
});
// GET /posts/:postId/comment/:commentId/remove 删除一条留言
router.get('/:postId/comment/:commentId/remove', checkLogin, function(req, res, next) {
var commentId = req.params.commentId;
var author = req.session.user._id;
CommentModel.delCommentById(commentId, author)
.then(function () {
req.flash('success', '删除留言成功');
// 删除成功后跳转到上一页
res.redirect('back');
})
.catch(next);
});
上一节:4.9 文章
下一节:4.11 404页面