diff --git a/models-built/reply.js b/models-built/reply.js new file mode 100644 index 0000000..7ba6636 --- /dev/null +++ b/models-built/reply.js @@ -0,0 +1,71 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +exports.__esModule = true; +var TypeORM = require("typeorm"); +var common_1 = require("./common"); +var Reply = /** @class */ (function (_super) { + __extends(Reply, _super); + function Reply() { + return _super !== null && _super.apply(this, arguments) || this; + } + Reply.cache = false; + __decorate([ + TypeORM.PrimaryGeneratedColumn(), + __metadata("design:type", Number) + ], Reply.prototype, "id"); + __decorate([ + TypeORM.Column({ nullable: true, type: "mediumtext" }), + __metadata("design:type", String) + ], Reply.prototype, "content"); + __decorate([ + TypeORM.Index(), + TypeORM.Column({ nullable: true, type: "integer" }), + __metadata("design:type", Number) + ], Reply.prototype, "user_id"); + __decorate([ + TypeORM.Index(), + TypeORM.Column({ nullable: true, type: "integer" }), + __metadata("design:type", Number) + ], Reply.prototype, "comment_id"); + __decorate([ + TypeORM.Index(), + TypeORM.Column({ nullable: true, type: "integer" }), + __metadata("design:type", Number) + ], Reply.prototype, "article_id"); + __decorate([ + TypeORM.Column({ nullable: true, type: "integer" }), + __metadata("design:type", Number) + ], Reply.prototype, "public_time"); + __decorate([ + TypeORM.Column({ nullable: true, type: "integer" }), + __metadata("design:type", Number) + ], Reply.prototype, "update_time"); + Reply = __decorate([ + TypeORM.Entity() + ], Reply); + return Reply; +}(common_1["default"])); +exports["default"] = Reply; +; +//# sourceMappingURL=reply.js.map \ No newline at end of file diff --git a/models-built/reply.js.map b/models-built/reply.js.map new file mode 100644 index 0000000..7526ae4 --- /dev/null +++ b/models-built/reply.js.map @@ -0,0 +1 @@ +{"version":3,"file":"reply.js","sourceRoot":"","sources":["../models/reply.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,iCAAmC;AACnC,mCAA6B;AAO7B;IAAmC,yBAAK;IAAxC;;IA0BA,CAAC;IAzBU,WAAK,GAAG,KAAK,CAAC;IAGrB;QADC,OAAO,CAAC,sBAAsB,EAAE;;6BACtB;IAGX;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;;kCACvC;IAIhB;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;kCACpC;IAIhB;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;qCACjC;IAInB;QAFC,OAAO,CAAC,KAAK,EAAE;QACf,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;qCACjC;IAGnB;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;sCAChC;IAGpB;QADC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;sCAChC;IAzBH,KAAK;QADzB,OAAO,CAAC,MAAM,EAAE;OACI,KAAK,CA0BzB;IAAD,YAAC;CAAA,AA1BD,CAAmC,mBAAK,GA0BvC;qBA1BoB,KAAK;AA0BzB,CAAC"} \ No newline at end of file diff --git a/models/reply.ts b/models/reply.ts new file mode 100644 index 0000000..a96f386 --- /dev/null +++ b/models/reply.ts @@ -0,0 +1,35 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; +import User from "./user"; +import Article from "./article"; + +declare var syzoj: any; + +@TypeORM.Entity() +export default class Reply extends Model { + static cache = false; + + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Column({ nullable: true, type: "mediumtext" }) + content: string; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + user_id: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + comment_id: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + article_id: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + public_time: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + update_time: number; +}; diff --git a/modules/discussion.js b/modules/discussion.js index cd208f2..028d25d 100644 --- a/modules/discussion.js +++ b/modules/discussion.js @@ -2,6 +2,7 @@ let Problem = syzoj.model('problem'); let Article = syzoj.model('article'); let ArticleComment = syzoj.model('article-comment'); let User = syzoj.model('user'); +let Reply = syzoj.model('reply'); app.get('/discussion/:type?', async (req, res) => { try { @@ -76,6 +77,7 @@ app.get('/discussion/problem/:pid', async (req, res) => { app.get('/article/:id', async (req, res) => { try { let id = parseInt(req.params.id); + let replyQuery = await Reply.createQueryBuilder(); let article = await Article.findById(id); if (!article) throw new ErrorMessage('无此帖子。'); @@ -93,6 +95,17 @@ app.get('/article/:id', async (req, res) => { }); for (let comment of comments) { + const commentId = comment.id; + const reply = await replyQuery.where("comment_id = :commentId", { commentId }).getMany(); + if (reply.length === 0) { + comment.reply = []; + } else { + comment.reply = await reply.mapAsync(async (item) => { + const replyUserId = item.user_id; + item.reply_user = await User.findById(replyUserId); + return item + }); + } comment.content = await syzoj.utils.markdown(comment.content); comment.allowedEdit = await comment.isAllowedEditBy(res.locals.user); await comment.loadRelationships(); @@ -105,8 +118,8 @@ app.get('/article/:id', async (req, res) => { throw new ErrorMessage('您没有权限进行此操作。'); } } - res.render('article', { + user: res.locals.user, article: article, comments: comments, paginate: paginate, @@ -203,7 +216,11 @@ app.post('/article/:id/delete', async (req, res) => { await Promise.all((await ArticleComment.find({ article_id: article.id - })).map(comment => comment.destroy())) + })).map(comment => comment.destroy())); + + await Promise.all((await Reply.find({ + article_id: article.id + })).map(reply => reply.destroy())); await article.destroy(); @@ -249,13 +266,55 @@ app.post('/article/:id/comment', async (req, res) => { } }); +app.post('/article/:id/comment/:commentId', async (req, res) => { + try { + if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); + + let id = parseInt(req.params.id); + let commentId = parseInt(req.params.commentId); + let userId = parseInt(res.locals.user.id); + let article = await Article.findById(id); + let articleComment = await ArticleComment.findById(commentId); + + if (!articleComment) { + throw new ErrorMessage('无此回复。'); + } + + if (!article) { + throw new ErrorMessage('无此帖子。'); + } else { + if (!await article.isAllowedCommentBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); + } + + let newReply = await Reply.create({ + content: req.body.comment, + article_id: id, + user_id: userId, + comment_id: commentId, + public_time: syzoj.utils.getCurrentDate(), + update_time: syzoj.utils.getCurrentDate() + }); + + const reply = await newReply.save(); + + await article.resetReplyCountAndTime(); + // + res.redirect(syzoj.utils.makeUrl(['article', article.id])); + } catch (e) { + syzoj.log(e); + res.render('error', { + err: e + }); + } +}); + app.post('/article/:article_id/comment/:id/delete', async (req, res) => { try { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); let id = parseInt(req.params.id); let comment = await ArticleComment.findById(id); - + let replyQuery = await Reply.createQueryBuilder(); if (!comment) { throw new ErrorMessage('无此评论。'); } else { @@ -265,6 +324,7 @@ app.post('/article/:article_id/comment/:id/delete', async (req, res) => { const article = await Article.findById(comment.article_id); await comment.destroy(); + await replyQuery.delete().from(Reply).where("comment_id = :id", { id }).execute(); await article.resetReplyCountAndTime(); @@ -276,3 +336,26 @@ app.post('/article/:article_id/comment/:id/delete', async (req, res) => { }); } }); + +app.post('/article/:articleId/reply/:id/delete', async (req, res) => { + try { + if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); + + let id = parseInt(req.params.id); + let reply = await Reply.findById(id); + let articleId = parseInt(req.params.articleId); + let replyQuery = await Reply.createQueryBuilder(); + if (!reply) { + throw new ErrorMessage('无此回复。'); + } + + await reply.destroy(); + + res.redirect(`/article/${articleId}`); + } catch (e) { + syzoj.log(e); + res.render('error', { + err: e + }); + } +}); diff --git a/views/article.ejs b/views/article.ejs index 3386e33..2b2a7a3 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -4,6 +4,18 @@ .small{ font-size: 0.7em; } +.reply{ + margin-left: 45px !important; +} +.article-comment{ + cursor: pointer; + display: inline-block; + margin: 0 .75em 0 0; + color: rgba(0,0,0,.4); +} +.article-comment:hover{ + color: rgba(0,0,0,.8); +}
+

<%= article.title %>

<%= article.user.username %><% if (article.user.nameplate) { %><%- article.user.nameplate %><% } %> <%= syzoj.utils.formatDate(article.public_time) %> + <% if(user) {%> + 回复 + <%}%> <% if (article.public_time !== article.update_time) { %> <%= syzoj.utils.formatDate(article.update_time) %><% } %> <% if (article.allowedEdit) { %> 编辑 @@ -65,14 +81,14 @@

<%- comment.content %>
<% if (comment.allowedEdit) { %> -
删除
+
删除回复
- <% } %> + <% for (let reply of comment.reply) { %> +
+ + + +
+ <%= reply.reply_user.username %><% if (reply.reply_user.nameplate) { %><%- reply.reply_user.nameplate %><% } %> + +
<%- reply.content %>
+ <% if (comment.allowedEdit) { %> + + + <% } %> +
+
+ <% } %> + <% } %>
<% include page %> @@ -96,14 +154,14 @@ <% } %> <% if (article.allowedComment) { %> <% include monaco-editor %> -
+
<%- this.showLoadingEditor(); %>
@@ -111,7 +169,7 @@ var editors = { comment: { defaultValue: '' } }; - + window.onEditorLoaded(function () { for (var name in editors) { var editor = editors[name]; @@ -122,7 +180,29 @@ $('#submit_button').removeClass('disabled'); }); - + <% } %>
<% include footer %> +