diff --git a/CTFd/api/v1/comments.py b/CTFd/api/v1/comments.py index 64767ec7..4b3fac44 100644 --- a/CTFd/api/v1/comments.py +++ b/CTFd/api/v1/comments.py @@ -3,7 +3,6 @@ from typing import List from flask import request, session from flask_restx import Namespace, Resource -from CTFd.api.v1.helpers.models import build_model_filters from CTFd.api.v1.helpers.request import validate_args from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse @@ -18,6 +17,7 @@ from CTFd.models import ( ) from CTFd.schemas.comments import CommentSchema from CTFd.utils.decorators import admins_only +from CTFd.utils.helpers.models import build_model_filters comments_namespace = Namespace("comments", description="Endpoint to retrieve Comments") @@ -87,14 +87,32 @@ class CommentList(Resource): CommentModel = get_comment_model(data=query_args) filters = build_model_filters(model=CommentModel, query=q, field=field) - comments = CommentModel.query.filter_by(**query_args).filter(*filters).all() + comments = ( + CommentModel.query.filter_by(**query_args) + .filter(*filters) + .order_by(CommentModel.id.desc()) + .paginate(max_per_page=100) + ) schema = CommentSchema(many=True) - response = schema.dump(comments) + response = schema.dump(comments.items) if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return { + "meta": { + "pagination": { + "page": comments.page, + "next": comments.next_num, + "prev": comments.prev_num, + "pages": comments.pages, + "per_page": comments.per_page, + "total": comments.total, + } + }, + "success": True, + "data": response.data, + } @admins_only @comments_namespace.doc( diff --git a/CTFd/themes/admin/assets/js/components/comments/CommentBox.vue b/CTFd/themes/admin/assets/js/components/comments/CommentBox.vue index b1bcc7f2..649202ee 100644 --- a/CTFd/themes/admin/assets/js/components/comments/CommentBox.vue +++ b/CTFd/themes/admin/assets/js/components/comments/CommentBox.vue @@ -20,11 +20,42 @@ + +
+
+
+ + + +
+
+
+
+ Page {{ page }} of {{ total }} comments +
+
+
@@ -53,6 +84,36 @@
+
+
+
+ + + +
+
+
+
+ Page {{ page }} of {{ total }} comments +
+
+
@@ -69,6 +130,11 @@ export default { }, data: function() { return { + page: 1, + pages: null, + next: null, + prev: null, + total: null, comment: "", comments: [], urlRoot: CTFd.config.urlRoot @@ -80,6 +146,14 @@ export default { .local() .format("MMMM Do, h:mm:ss A"); }, + nextPage: function() { + this.page++; + this.loadComments(); + }, + prevPage: function() { + this.page--; + this.loadComments(); + }, getArgs: function() { let args = {}; args[`${this.$props.type}_id`] = this.$props.id; @@ -87,7 +161,15 @@ export default { }, loadComments: function() { let apiArgs = this.getArgs(); + apiArgs[`page`] = this.page; + apiArgs[`per_page`] = 10; + helpers.comments.get_comments(apiArgs).then(response => { + this.page = response.meta.pagination.page; + this.pages = response.meta.pagination.pages; + this.next = response.meta.pagination.next; + this.prev = response.meta.pagination.prev; + this.total = response.meta.pagination.total; this.comments = response.data; return this.comments; }); diff --git a/CTFd/themes/admin/static/js/components.dev.js b/CTFd/themes/admin/static/js/components.dev.js index 71ab3d6f..39a8ef9a 100644 --- a/CTFd/themes/admin/static/js/components.dev.js +++ b/CTFd/themes/admin/static/js/components.dev.js @@ -164,7 +164,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod /***/ (function(module, exports, __webpack_require__) { ; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _helpers = _interopRequireDefault(__webpack_require__(/*! core/helpers */ \"./CTFd/themes/core/assets/js/helpers.js\"));\n\nvar _moment = _interopRequireDefault(__webpack_require__(/*! moment */ \"./node_modules/moment/moment.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar _default = {\n props: {\n // These props are passed to the api via query string.\n // See this.getArgs()\n type: String,\n id: Number\n },\n data: function data() {\n return {\n comment: \"\",\n comments: [],\n urlRoot: _CTFd.default.config.urlRoot\n };\n },\n methods: {\n toLocalTime: function toLocalTime(date) {\n return (0, _moment.default)(date).local().format(\"MMMM Do, h:mm:ss A\");\n },\n getArgs: function getArgs() {\n var args = {};\n args[\"\".concat(this.$props.type, \"_id\")] = this.$props.id;\n return args;\n },\n loadComments: function loadComments() {\n var _this = this;\n\n var apiArgs = this.getArgs();\n\n _helpers.default.comments.get_comments(apiArgs).then(function (response) {\n _this.comments = response.data;\n return _this.comments;\n });\n },\n submitComment: function submitComment() {\n var _this2 = this;\n\n var comment = this.comment.trim();\n\n if (comment.length > 0) {\n _helpers.default.comments.add_comment(comment, this.$props.type, this.getArgs(), function () {\n _this2.loadComments();\n });\n }\n\n this.comment = \"\";\n },\n deleteComment: function deleteComment(commentId) {\n var _this3 = this;\n\n if (confirm(\"Are you sure you'd like to delete this comment?\")) {\n _helpers.default.comments.delete_comment(commentId).then(function (response) {\n if (response.success === true) {\n for (var i = _this3.comments.length - 1; i >= 0; --i) {\n if (_this3.comments[i].id == commentId) {\n _this3.comments.splice(i, 1);\n }\n }\n }\n });\n }\n }\n },\n created: function created() {\n this.loadComments();\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?./node_modules/babel-loader/lib??ref--0!./node_modules/vue-loader/lib??vue-loader-options"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _helpers = _interopRequireDefault(__webpack_require__(/*! core/helpers */ \"./CTFd/themes/core/assets/js/helpers.js\"));\n\nvar _moment = _interopRequireDefault(__webpack_require__(/*! moment */ \"./node_modules/moment/moment.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar _default = {\n props: {\n // These props are passed to the api via query string.\n // See this.getArgs()\n type: String,\n id: Number\n },\n data: function data() {\n return {\n page: 1,\n pages: null,\n next: null,\n prev: null,\n total: null,\n comment: \"\",\n comments: [],\n urlRoot: _CTFd.default.config.urlRoot\n };\n },\n methods: {\n toLocalTime: function toLocalTime(date) {\n return (0, _moment.default)(date).local().format(\"MMMM Do, h:mm:ss A\");\n },\n nextPage: function nextPage() {\n this.page++;\n this.loadComments();\n },\n prevPage: function prevPage() {\n this.page--;\n this.loadComments();\n },\n getArgs: function getArgs() {\n var args = {};\n args[\"\".concat(this.$props.type, \"_id\")] = this.$props.id;\n return args;\n },\n loadComments: function loadComments() {\n var _this = this;\n\n var apiArgs = this.getArgs();\n apiArgs[\"page\"] = this.page;\n apiArgs[\"per_page\"] = 10;\n\n _helpers.default.comments.get_comments(apiArgs).then(function (response) {\n _this.page = response.meta.pagination.page;\n _this.pages = response.meta.pagination.pages;\n _this.next = response.meta.pagination.next;\n _this.prev = response.meta.pagination.prev;\n _this.total = response.meta.pagination.total;\n _this.comments = response.data;\n return _this.comments;\n });\n },\n submitComment: function submitComment() {\n var _this2 = this;\n\n var comment = this.comment.trim();\n\n if (comment.length > 0) {\n _helpers.default.comments.add_comment(comment, this.$props.type, this.getArgs(), function () {\n _this2.loadComments();\n });\n }\n\n this.comment = \"\";\n },\n deleteComment: function deleteComment(commentId) {\n var _this3 = this;\n\n if (confirm(\"Are you sure you'd like to delete this comment?\")) {\n _helpers.default.comments.delete_comment(commentId).then(function (response) {\n if (response.success === true) {\n for (var i = _this3.comments.length - 1; i >= 0; --i) {\n if (_this3.comments[i].id == commentId) {\n _this3.comments.splice(i, 1);\n }\n }\n }\n });\n }\n }\n },\n created: function created() {\n this.loadComments();\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?./node_modules/babel-loader/lib??ref--0!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), @@ -223,7 +223,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../. /***/ (function(module, __webpack_exports__, __webpack_require__) { ; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", [\n _c(\"div\", { staticClass: \"row mb-3\" }, [\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"comment\" }, [\n _c(\"textarea\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.comment,\n expression: \"comment\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-control mb-2\",\n attrs: {\n rows: \"2\",\n id: \"comment-input\",\n placeholder: \"Add comment\"\n },\n domProps: { value: _vm.comment },\n on: {\n change: function($event) {\n _vm.comment = $event.target.value\n }\n }\n }),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n staticClass: \"btn btn-sm btn-success btn-outlined float-right\",\n attrs: { type: \"submit\" },\n on: {\n click: function($event) {\n return _vm.submitComment()\n }\n }\n },\n [_vm._v(\"\\n Comment\\n \")]\n )\n ])\n ])\n ]),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"comments\" },\n [\n _c(\n \"transition-group\",\n { attrs: { name: \"comment-card\" } },\n _vm._l(_vm.comments.slice().reverse(), function(comment) {\n return _c(\n \"div\",\n { key: comment.id, staticClass: \"comment-card card mb-2\" },\n [\n _c(\"div\", { staticClass: \"card-body pl-0 pb-0 pt-2 pr-2\" }, [\n _c(\n \"button\",\n {\n staticClass: \"close float-right\",\n attrs: { type: \"button\", \"aria-label\": \"Close\" },\n on: {\n click: function($event) {\n return _vm.deleteComment(comment.id)\n }\n }\n },\n [\n _c(\"span\", { attrs: { \"aria-hidden\": \"true\" } }, [\n _vm._v(\"×\")\n ])\n ]\n )\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"card-body\" }, [\n _c(\"div\", {\n staticClass: \"card-text\",\n domProps: { innerHTML: _vm._s(comment.html) }\n }),\n _vm._v(\" \"),\n _c(\"small\", { staticClass: \"text-muted float-left\" }, [\n _c(\"span\", [\n _c(\n \"a\",\n {\n attrs: {\n href:\n _vm.urlRoot + \"/admin/users/\" + comment.author_id\n }\n },\n [_vm._v(_vm._s(comment.author.name))]\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"small\", { staticClass: \"text-muted float-right\" }, [\n _c(\"span\", { staticClass: \"float-right\" }, [\n _vm._v(_vm._s(_vm.toLocalTime(comment.date)))\n ])\n ])\n ])\n ]\n )\n }),\n 0\n )\n ],\n 1\n )\n ])\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", [\n _c(\"div\", { staticClass: \"row mb-3\" }, [\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"comment\" }, [\n _c(\"textarea\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model.lazy\",\n value: _vm.comment,\n expression: \"comment\",\n modifiers: { lazy: true }\n }\n ],\n staticClass: \"form-control mb-2\",\n attrs: {\n rows: \"2\",\n id: \"comment-input\",\n placeholder: \"Add comment\"\n },\n domProps: { value: _vm.comment },\n on: {\n change: function($event) {\n _vm.comment = $event.target.value\n }\n }\n }),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n staticClass: \"btn btn-sm btn-success btn-outlined float-right\",\n attrs: { type: \"submit\" },\n on: {\n click: function($event) {\n return _vm.submitComment()\n }\n }\n },\n [_vm._v(\"\\n Comment\\n \")]\n )\n ])\n ])\n ]),\n _vm._v(\" \"),\n _vm.pages > 1\n ? _c(\"div\", { staticClass: \"row\" }, [\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"text-center\" }, [\n _c(\n \"button\",\n {\n staticClass: \"btn btn-link p-0\",\n attrs: { type: \"button\", disabled: _vm.prev ? false : true },\n on: {\n click: function($event) {\n return _vm.prevPage()\n }\n }\n },\n [_vm._v(\"\\n <<<\\n \")]\n ),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n staticClass: \"btn btn-link p-0\",\n attrs: { type: \"button\", disabled: _vm.next ? false : true },\n on: {\n click: function($event) {\n return _vm.nextPage()\n }\n }\n },\n [_vm._v(\"\\n >>>\\n \")]\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"text-center\" }, [\n _c(\"small\", { staticClass: \"text-muted\" }, [\n _vm._v(\n \"Page \" +\n _vm._s(_vm.page) +\n \" of \" +\n _vm._s(_vm.total) +\n \" comments\"\n )\n ])\n ])\n ])\n ])\n : _vm._e(),\n _vm._v(\" \"),\n _c(\n \"div\",\n { staticClass: \"comments\" },\n [\n _c(\n \"transition-group\",\n { attrs: { name: \"comment-card\" } },\n _vm._l(_vm.comments, function(comment) {\n return _c(\n \"div\",\n { key: comment.id, staticClass: \"comment-card card mb-2\" },\n [\n _c(\"div\", { staticClass: \"card-body pl-0 pb-0 pt-2 pr-2\" }, [\n _c(\n \"button\",\n {\n staticClass: \"close float-right\",\n attrs: { type: \"button\", \"aria-label\": \"Close\" },\n on: {\n click: function($event) {\n return _vm.deleteComment(comment.id)\n }\n }\n },\n [\n _c(\"span\", { attrs: { \"aria-hidden\": \"true\" } }, [\n _vm._v(\"×\")\n ])\n ]\n )\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"card-body\" }, [\n _c(\"div\", {\n staticClass: \"card-text\",\n domProps: { innerHTML: _vm._s(comment.html) }\n }),\n _vm._v(\" \"),\n _c(\"small\", { staticClass: \"text-muted float-left\" }, [\n _c(\"span\", [\n _c(\n \"a\",\n {\n attrs: {\n href:\n _vm.urlRoot + \"/admin/users/\" + comment.author_id\n }\n },\n [_vm._v(_vm._s(comment.author.name))]\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"small\", { staticClass: \"text-muted float-right\" }, [\n _c(\"span\", { staticClass: \"float-right\" }, [\n _vm._v(_vm._s(_vm.toLocalTime(comment.date)))\n ])\n ])\n ])\n ]\n )\n }),\n 0\n )\n ],\n 1\n ),\n _vm._v(\" \"),\n _vm.pages > 1\n ? _c(\"div\", { staticClass: \"row\" }, [\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"text-center\" }, [\n _c(\n \"button\",\n {\n staticClass: \"btn btn-link p-0\",\n attrs: { type: \"button\", disabled: _vm.prev ? false : true },\n on: {\n click: function($event) {\n return _vm.prevPage()\n }\n }\n },\n [_vm._v(\"\\n <<<\\n \")]\n ),\n _vm._v(\" \"),\n _c(\n \"button\",\n {\n staticClass: \"btn btn-link p-0\",\n attrs: { type: \"button\", disabled: _vm.next ? false : true },\n on: {\n click: function($event) {\n return _vm.nextPage()\n }\n }\n },\n [_vm._v(\"\\n >>>\\n \")]\n )\n ])\n ]),\n _vm._v(\" \"),\n _c(\"div\", { staticClass: \"col-md-12\" }, [\n _c(\"div\", { staticClass: \"text-center\" }, [\n _c(\"small\", { staticClass: \"text-muted\" }, [\n _vm._v(\n \"Page \" +\n _vm._s(_vm.page) +\n \" of \" +\n _vm._s(_vm.total) +\n \" comments\"\n )\n ])\n ])\n ])\n ])\n : _vm._e()\n ])\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }), diff --git a/CTFd/themes/admin/static/js/components.min.js b/CTFd/themes/admin/static/js/components.min.js index fa48c7bd..f8fca7d3 100644 --- a/CTFd/themes/admin/static/js/components.min.js +++ b/CTFd/themes/admin/static/js/components.min.js @@ -1 +1 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue":function(e,t,s){s.r(t);var i=s("./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=template&id=1fd2c08a&scoped=true&"),a=s("./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=script&lang=js&");for(var n in a)"default"!==n&&function(e){s.d(t,e,function(){return a[e]})}(n);s("./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=style&index=0&id=1fd2c08a&scoped=true&lang=css&");var l=s("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),o=Object(l.a)(a.default,i.a,i.b,!1,null,"1fd2c08a",null);o.options.__file="CTFd/themes/admin/assets/js/components/comments/CommentBox.vue",t.default=o.exports},"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=script&lang=js&":function(e,t,s){s.r(t);var i=s("./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=script&lang=js&"),a=s.n(i);for(var n in i)"default"!==n&&function(e){s.d(t,e,function(){return i[e]})}(n);t.default=a.a},"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=style&index=0&id=1fd2c08a&scoped=true&lang=css&":function(e,t,s){var i=s("./node_modules/vue-style-loader/index.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=style&index=0&id=1fd2c08a&scoped=true&lang=css&");s.n(i).a},"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=template&id=1fd2c08a&scoped=true&":function(e,t,s){function i(){var s=this,e=s.$createElement,i=s._self._c||e;return i("div",[i("div",{staticClass:"row mb-3"},[i("div",{staticClass:"col-md-12"},[i("div",{staticClass:"comment"},[i("textarea",{directives:[{name:"model",rawName:"v-model.lazy",value:s.comment,expression:"comment",modifiers:{lazy:!0}}],staticClass:"form-control mb-2",attrs:{rows:"2",id:"comment-input",placeholder:"Add comment"},domProps:{value:s.comment},on:{change:function(e){s.comment=e.target.value}}}),s._v(" "),i("button",{staticClass:"btn btn-sm btn-success btn-outlined float-right",attrs:{type:"submit"},on:{click:function(e){return s.submitComment()}}},[s._v("\n Comment\n ")])])])]),s._v(" "),i("div",{staticClass:"comments"},[i("transition-group",{attrs:{name:"comment-card"}},s._l(s.comments.slice().reverse(),function(t){return i("div",{key:t.id,staticClass:"comment-card card mb-2"},[i("div",{staticClass:"card-body pl-0 pb-0 pt-2 pr-2"},[i("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return s.deleteComment(t.id)}}},[i("span",{attrs:{"aria-hidden":"true"}},[s._v("×")])])]),s._v(" "),i("div",{staticClass:"card-body"},[i("div",{staticClass:"card-text",domProps:{innerHTML:s._s(t.html)}}),s._v(" "),i("small",{staticClass:"text-muted float-left"},[i("span",[i("a",{attrs:{href:s.urlRoot+"/admin/users/"+t.author_id}},[s._v(s._s(t.author.name))])])]),s._v(" "),i("small",{staticClass:"text-muted float-right"},[i("span",{staticClass:"float-right"},[s._v(s._s(s.toLocalTime(t.date)))])])])])}),0)],1)])}var a=[];i._withStripped=!0,s.d(t,"a",function(){return i}),s.d(t,"b",function(){return a})},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue":function(e,t,s){s.r(t);var i=s("./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=template&id=30e0f744&scoped=true&"),a=s("./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&");for(var n in a)"default"!==n&&function(e){s.d(t,e,function(){return a[e]})}(n);var l=s("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),o=Object(l.a)(a.default,i.a,i.b,!1,null,"30e0f744",null);o.options.__file="CTFd/themes/admin/assets/js/components/configs/fields/Field.vue",t.default=o.exports},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&":function(e,t,s){s.r(t);var i=s("./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&"),a=s.n(i);for(var n in i)"default"!==n&&function(e){s.d(t,e,function(){return i[e]})}(n);t.default=a.a},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=template&id=30e0f744&scoped=true&":function(e,t,s){function i(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{staticClass:"border-bottom"},[t("div",[t("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return n.deleteField()}}},[t("span",{attrs:{"aria-hidden":"true"}},[n._v("×")])])]),n._v(" "),t("div",{staticClass:"row"},[t("div",{staticClass:"col-md-3"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Type")]),n._v(" "),t("select",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.field_type,expression:"field.field_type",modifiers:{lazy:!0}}],staticClass:"form-control custom-select",on:{change:function(e){var t=Array.prototype.filter.call(e.target.options,function(e){return e.selected}).map(function(e){return"_value"in e?e._value:e.value});n.$set(n.field,"field_type",e.target.multiple?t:t[0])}}},[t("option",{attrs:{value:"text"}},[n._v("Text Field")]),n._v(" "),t("option",{attrs:{value:"checkbox"}},[n._v("Checkbox")])]),n._v(" "),t("small",{staticClass:"form-text text-muted"},[n._v("Type of field shown to the user")])])]),n._v(" "),t("div",{staticClass:"col-md-9"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Name")]),n._v(" "),t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.name,expression:"field.name",modifiers:{lazy:!0}}],staticClass:"form-control",attrs:{type:"text"},domProps:{value:n.field.name},on:{change:function(e){return n.$set(n.field,"name",e.target.value)}}}),n._v(" "),t("small",{staticClass:"form-text text-muted"},[n._v("Field name")])])]),n._v(" "),t("div",{staticClass:"col-md-12"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Description")]),n._v(" "),t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.description,expression:"field.description",modifiers:{lazy:!0}}],staticClass:"form-control",attrs:{type:"text"},domProps:{value:n.field.description},on:{change:function(e){return n.$set(n.field,"description",e.target.value)}}}),n._v(" "),t("small",{staticClass:"form-text text-muted",attrs:{id:"emailHelp"}},[n._v("Field Description")])])]),n._v(" "),t("div",{staticClass:"col-md-12"},[t("div",{staticClass:"form-check"},[t("label",{staticClass:"form-check-label"},[t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.editable,expression:"field.editable",modifiers:{lazy:!0}}],staticClass:"form-check-input",attrs:{type:"checkbox"},domProps:{checked:Array.isArray(n.field.editable)?-1>>\n ")])])]),s._v(" "),i("div",{staticClass:"col-md-12"},[i("div",{staticClass:"text-center"},[i("small",{staticClass:"text-muted"},[s._v("Page "+s._s(s.page)+" of "+s._s(s.total)+" comments")])])])]):s._e(),s._v(" "),i("div",{staticClass:"comments"},[i("transition-group",{attrs:{name:"comment-card"}},s._l(s.comments,function(t){return i("div",{key:t.id,staticClass:"comment-card card mb-2"},[i("div",{staticClass:"card-body pl-0 pb-0 pt-2 pr-2"},[i("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return s.deleteComment(t.id)}}},[i("span",{attrs:{"aria-hidden":"true"}},[s._v("×")])])]),s._v(" "),i("div",{staticClass:"card-body"},[i("div",{staticClass:"card-text",domProps:{innerHTML:s._s(t.html)}}),s._v(" "),i("small",{staticClass:"text-muted float-left"},[i("span",[i("a",{attrs:{href:s.urlRoot+"/admin/users/"+t.author_id}},[s._v(s._s(t.author.name))])])]),s._v(" "),i("small",{staticClass:"text-muted float-right"},[i("span",{staticClass:"float-right"},[s._v(s._s(s.toLocalTime(t.date)))])])])])}),0)],1),s._v(" "),1>>\n ")])])]),s._v(" "),i("div",{staticClass:"col-md-12"},[i("div",{staticClass:"text-center"},[i("small",{staticClass:"text-muted"},[s._v("Page "+s._s(s.page)+" of "+s._s(s.total)+" comments")])])])]):s._e()])}var a=[];i._withStripped=!0,s.d(t,"a",function(){return i}),s.d(t,"b",function(){return a})},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue":function(e,t,s){s.r(t);var i=s("./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=template&id=30e0f744&scoped=true&"),a=s("./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&");for(var n in a)"default"!==n&&function(e){s.d(t,e,function(){return a[e]})}(n);var l=s("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),o=Object(l.a)(a.default,i.a,i.b,!1,null,"30e0f744",null);o.options.__file="CTFd/themes/admin/assets/js/components/configs/fields/Field.vue",t.default=o.exports},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&":function(e,t,s){s.r(t);var i=s("./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=script&lang=js&"),a=s.n(i);for(var n in i)"default"!==n&&function(e){s.d(t,e,function(){return i[e]})}(n);t.default=a.a},"./CTFd/themes/admin/assets/js/components/configs/fields/Field.vue?vue&type=template&id=30e0f744&scoped=true&":function(e,t,s){function i(){var n=this,e=n.$createElement,t=n._self._c||e;return t("div",{staticClass:"border-bottom"},[t("div",[t("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return n.deleteField()}}},[t("span",{attrs:{"aria-hidden":"true"}},[n._v("×")])])]),n._v(" "),t("div",{staticClass:"row"},[t("div",{staticClass:"col-md-3"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Type")]),n._v(" "),t("select",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.field_type,expression:"field.field_type",modifiers:{lazy:!0}}],staticClass:"form-control custom-select",on:{change:function(e){var t=Array.prototype.filter.call(e.target.options,function(e){return e.selected}).map(function(e){return"_value"in e?e._value:e.value});n.$set(n.field,"field_type",e.target.multiple?t:t[0])}}},[t("option",{attrs:{value:"text"}},[n._v("Text Field")]),n._v(" "),t("option",{attrs:{value:"checkbox"}},[n._v("Checkbox")])]),n._v(" "),t("small",{staticClass:"form-text text-muted"},[n._v("Type of field shown to the user")])])]),n._v(" "),t("div",{staticClass:"col-md-9"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Name")]),n._v(" "),t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.name,expression:"field.name",modifiers:{lazy:!0}}],staticClass:"form-control",attrs:{type:"text"},domProps:{value:n.field.name},on:{change:function(e){return n.$set(n.field,"name",e.target.value)}}}),n._v(" "),t("small",{staticClass:"form-text text-muted"},[n._v("Field name")])])]),n._v(" "),t("div",{staticClass:"col-md-12"},[t("div",{staticClass:"form-group"},[t("label",[n._v("Field Description")]),n._v(" "),t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.description,expression:"field.description",modifiers:{lazy:!0}}],staticClass:"form-control",attrs:{type:"text"},domProps:{value:n.field.description},on:{change:function(e){return n.$set(n.field,"description",e.target.value)}}}),n._v(" "),t("small",{staticClass:"form-text text-muted",attrs:{id:"emailHelp"}},[n._v("Field Description")])])]),n._v(" "),t("div",{staticClass:"col-md-12"},[t("div",{staticClass:"form-check"},[t("label",{staticClass:"form-check-label"},[t("input",{directives:[{name:"model",rawName:"v-model.lazy",value:n.field.editable,expression:"field.editable",modifiers:{lazy:!0}}],staticClass:"form-check-input",attrs:{type:"checkbox"},domProps:{checked:Array.isArray(n.field.editable)?-1