From b29d9284598df359836d243877647a274d94c888 Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Wed, 19 Aug 2020 16:28:37 -0400 Subject: [PATCH 1/2] Stop using deprecated func --- CTFd/api/v1/comments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CTFd/api/v1/comments.py b/CTFd/api/v1/comments.py index 64767ec7..22925de3 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") From 0bc58d5af18070aadbc9a9275fb63970ee624447 Mon Sep 17 00:00:00 2001 From: Kevin Chung Date: Wed, 19 Aug 2020 19:42:56 -0400 Subject: [PATCH 2/2] Add pagination to /api/v1/comments (#1614) * Add pagination to `/api/v1/comments` * Update CommentBox component to have pagination buttons * Closes #1599 --- CTFd/api/v1/comments.py | 24 +++++- .../js/components/comments/CommentBox.vue | 84 ++++++++++++++++++- CTFd/themes/admin/static/js/components.dev.js | 4 +- CTFd/themes/admin/static/js/components.min.js | 2 +- 4 files changed, 107 insertions(+), 7 deletions(-) diff --git a/CTFd/api/v1/comments.py b/CTFd/api/v1/comments.py index 22925de3..4b3fac44 100644 --- a/CTFd/api/v1/comments.py +++ b/CTFd/api/v1/comments.py @@ -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 0fe0cca2..2667c603 100644 --- a/CTFd/themes/admin/static/js/components.dev.js +++ b/CTFd/themes/admin/static/js/components.dev.js @@ -92,7 +92,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"); /***/ }), @@ -127,7 +127,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 06a2ac04..0d4ed53c 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 a=s("./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=template&id=1fd2c08a&scoped=true&"),i=s("./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=script&lang=js&");for(var n in i)"default"!==n&&function(e){s.d(t,e,function(){return i[e]})}(n);s("./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=style&index=0&id=1fd2c08a&scoped=true&lang=css&");var o=s("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),l=Object(o.a)(i.default,a.a,a.b,!1,null,"1fd2c08a",null);l.options.__file="CTFd/themes/admin/assets/js/components/comments/CommentBox.vue",t.default=l.exports},"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=script&lang=js&":function(e,t,s){s.r(t);var a=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&"),i=s.n(a);for(var n in a)"default"!==n&&function(e){s.d(t,e,function(){return a[e]})}(n);t.default=i.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 a=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(a).a},"./CTFd/themes/admin/assets/js/components/comments/CommentBox.vue?vue&type=template&id=1fd2c08a&scoped=true&":function(e,t,s){function a(){var s=this,e=s.$createElement,a=s._self._c||e;return a("div",[a("div",{staticClass:"row mb-3"},[a("div",{staticClass:"col-md-12"},[a("div",{staticClass:"comment"},[a("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(" "),a("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(" "),a("div",{staticClass:"comments"},[a("transition-group",{attrs:{name:"comment-card"}},s._l(s.comments.slice().reverse(),function(t){return a("div",{key:t.id,staticClass:"comment-card card mb-2"},[a("div",{staticClass:"card-body pl-0 pb-0 pt-2 pr-2"},[a("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return s.deleteComment(t.id)}}},[a("span",{attrs:{"aria-hidden":"true"}},[s._v("×")])])]),s._v(" "),a("div",{staticClass:"card-body"},[a("div",{staticClass:"card-text",domProps:{innerHTML:s._s(t.html)}}),s._v(" "),a("small",{staticClass:"text-muted float-left"},[a("span",[a("a",{attrs:{href:s.urlRoot+"/admin/users/"+t.author_id}},[s._v(s._s(t.author.name))])])]),s._v(" "),a("small",{staticClass:"text-muted float-right"},[a("span",{staticClass:"float-right"},[s._v(s._s(s.toLocalTime(t.date)))])])])])}),0)],1)])}var i=[];a._withStripped=!0,s.d(t,"a",function(){return a}),s.d(t,"b",function(){return i})},"./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue":function(e,t,s){s.r(t);var a=s("./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=template&id=50f8d42a&"),i=s("./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=script&lang=js&");for(var n in i)"default"!==n&&function(e){s.d(t,e,function(){return i[e]})}(n);var o=s("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),l=Object(o.a)(i.default,a.a,a.b,!1,null,null,null);l.options.__file="CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue",t.default=l.exports},"./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=script&lang=js&":function(e,t,s){s.r(t);var a=s("./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=script&lang=js&"),i=s.n(a);for(var n in a)"default"!==n&&function(e){s.d(t,e,function(){return a[e]})}(n);t.default=i.a},"./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=template&id=50f8d42a&":function(e,t,s){function a(){var s=this,e=s.$createElement,a=s._self._c||e;return a("div",{staticClass:"modal fade",attrs:{id:"media-modal",tabindex:"-1"}},[a("div",{staticClass:"modal-dialog modal-lg"},[a("div",{staticClass:"modal-content"},[s._m(0),s._v(" "),a("div",{staticClass:"modal-body"},[a("div",{staticClass:"modal-header"},[a("div",{staticClass:"container"},[a("div",{staticClass:"row mh-100"},[a("div",{staticClass:"col-md-6",attrs:{id:"media-library-list"}},s._l(s.files,function(t){return a("div",{key:t.id,staticClass:"media-item-wrapper"},[a("a",{attrs:{href:"javascript:void(0)"},on:{click:function(e){return s.selectFile(t),!1}}},[a("i",{class:s.getIconClass(t.location),attrs:{"aria-hidden":"true"}}),s._v(" "),a("small",{staticClass:"media-item-title"},[s._v(s._s(t.location.split("/").pop()))])])])}),0),s._v(" "),a("div",{staticClass:"col-md-6",attrs:{id:"media-library-details"}},[a("h4",{staticClass:"text-center"},[s._v("Media Details")]),s._v(" "),a("div",{attrs:{id:"media-item"}},[a("div",{staticClass:"text-center",attrs:{id:"media-icon"}},[this.selectedFile?a("div",["far fa-file-image"===s.getIconClass(this.selectedFile.location)?a("div",[a("img",{staticStyle:{"max-width":"100%","max-height":"100%","object-fit":"contain"},attrs:{src:s.buildSelectedFileUrl()}})]):a("div",[a("i",{class:s.getIconClass(this.selectedFile.location)+" fa-4x",attrs:{"aria-hidden":"true"}})])]):s._e()]),s._v(" "),a("br"),s._v(" "),this.selectedFile?a("div",{staticClass:"text-center",attrs:{id:"media-filename"}},[a("a",{attrs:{href:s.buildSelectedFileUrl(),target:"_blank"}},[s._v("\n "+s._s(this.selectedFile.location.split("/").pop())+"\n ")])]):s._e(),s._v(" "),a("br"),s._v(" "),a("div",{staticClass:"form-group"},[this.selectedFile?a("div",[s._v("\n Link:\n "),a("input",{staticClass:"form-control",attrs:{type:"text",id:"media-link",readonly:""},domProps:{value:s.buildSelectedFileUrl()}})]):a("div",[s._v("\n Link:\n "),a("input",{staticClass:"form-control",attrs:{type:"text",id:"media-link",readonly:""}})])]),s._v(" "),a("div",{staticClass:"form-group text-center"},[a("div",{staticClass:"row"},[a("div",{staticClass:"col-md-6"},[a("button",{staticClass:"btn btn-success w-100",attrs:{id:"media-insert","data-toggle":"tooltip","data-placement":"top",title:"Insert link into editor"},on:{click:s.insertSelectedFile}},[s._v("\n Insert\n ")])]),s._v(" "),a("div",{staticClass:"col-md-3"},[a("button",{staticClass:"btn btn-primary w-100",attrs:{id:"media-download","data-toggle":"tooltip","data-placement":"top",title:"Download file"},on:{click:s.downloadSelectedFile}},[a("i",{staticClass:"fas fa-download"})])]),s._v(" "),a("div",{staticClass:"col-md-3"},[a("button",{staticClass:"btn btn-danger w-100",attrs:{id:"media-delete","data-toggle":"tooltip","data-placement":"top",title:"Delete file"},on:{click:s.deleteSelectedFile}},[a("i",{staticClass:"far fa-trash-alt"})])])])])])])])])]),s._v(" "),s._m(1)]),s._v(" "),a("div",{staticClass:"modal-footer"},[a("div",{staticClass:"float-right"},[a("button",{staticClass:"btn btn-primary media-upload-button",attrs:{type:"submit"},on:{click:s.uploadChosenFiles}},[s._v("\n Upload\n ")])])])])])])}var i=[function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"modal-header"},[s("div",{staticClass:"container"},[s("div",{staticClass:"row"},[s("div",{staticClass:"col-md-12"},[s("h3",{staticClass:"text-center"},[e._v("Media Library")])])])]),e._v(" "),s("button",{staticClass:"close",attrs:{type:"button","data-dismiss":"modal","aria-label":"Close"}},[s("span",{attrs:{"aria-hidden":"true"}},[e._v("×")])])])},function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("form",{attrs:{id:"media-library-upload",enctype:"multipart/form-data"}},[s("div",{staticClass:"form-group"},[s("label",{attrs:{for:"media-files"}},[e._v("\n Upload Files\n ")]),e._v(" "),s("input",{staticClass:"form-control-file",attrs:{type:"file",name:"file",id:"media-files",multiple:""}}),e._v(" "),s("sub",{staticClass:"help-block"},[e._v("\n Attach multiple files using Control+Click or Cmd+Click.\n ")])]),e._v(" "),s("input",{attrs:{type:"hidden",value:"page",name:"type"}})])}];a._withStripped=!0,s.d(t,"a",function(){return a}),s.d(t,"b",function(){return i})},"./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&":function(e,t,s){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=o(s("./CTFd/themes/core/assets/js/CTFd.js")),i=o(s("./CTFd/themes/core/assets/js/helpers.js")),n=o(s("./node_modules/moment/moment.js"));function o(e){return e&&e.__esModule?e:{default:e}}var l={props:{type:String,id:Number},data:function(){return{comment:"",comments:[],urlRoot:a.default.config.urlRoot}},methods:{toLocalTime:function(e){return(0,n.default)(e).local().format("MMMM Do, h:mm:ss A")},getArgs:function(){var e={};return e["".concat(this.$props.type,"_id")]=this.$props.id,e},loadComments:function(){var t=this,e=this.getArgs();i.default.comments.get_comments(e).then(function(e){return t.comments=e.data,t.comments})},submitComment:function(){var e=this,t=this.comment.trim();0>>\n ")])])]),s._v(" "),a("div",{staticClass:"col-md-12"},[a("div",{staticClass:"text-center"},[a("small",{staticClass:"text-muted"},[s._v("Page "+s._s(s.page)+" of "+s._s(s.total)+" comments")])])])]):s._e(),s._v(" "),a("div",{staticClass:"comments"},[a("transition-group",{attrs:{name:"comment-card"}},s._l(s.comments,function(t){return a("div",{key:t.id,staticClass:"comment-card card mb-2"},[a("div",{staticClass:"card-body pl-0 pb-0 pt-2 pr-2"},[a("button",{staticClass:"close float-right",attrs:{type:"button","aria-label":"Close"},on:{click:function(e){return s.deleteComment(t.id)}}},[a("span",{attrs:{"aria-hidden":"true"}},[s._v("×")])])]),s._v(" "),a("div",{staticClass:"card-body"},[a("div",{staticClass:"card-text",domProps:{innerHTML:s._s(t.html)}}),s._v(" "),a("small",{staticClass:"text-muted float-left"},[a("span",[a("a",{attrs:{href:s.urlRoot+"/admin/users/"+t.author_id}},[s._v(s._s(t.author.name))])])]),s._v(" "),a("small",{staticClass:"text-muted float-right"},[a("span",{staticClass:"float-right"},[s._v(s._s(s.toLocalTime(t.date)))])])])])}),0)],1),s._v(" "),1>>\n ")])])]),s._v(" "),a("div",{staticClass:"col-md-12"},[a("div",{staticClass:"text-center"},[a("small",{staticClass:"text-muted"},[s._v("Page "+s._s(s.page)+" of "+s._s(s.total)+" comments")])])])]):s._e()])}var i=[];a._withStripped=!0,s.d(t,"a",function(){return a}),s.d(t,"b",function(){return i})},"./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue":function(e,t,s){s.r(t);var a=s("./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=template&id=50f8d42a&"),i=s("./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=script&lang=js&");for(var n in i)"default"!==n&&function(e){s.d(t,e,function(){return i[e]})}(n);var o=s("./node_modules/vue-loader/lib/runtime/componentNormalizer.js"),l=Object(o.a)(i.default,a.a,a.b,!1,null,null,null);l.options.__file="CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue",t.default=l.exports},"./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=script&lang=js&":function(e,t,s){s.r(t);var a=s("./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=script&lang=js&"),i=s.n(a);for(var n in a)"default"!==n&&function(e){s.d(t,e,function(){return a[e]})}(n);t.default=i.a},"./CTFd/themes/admin/assets/js/components/files/MediaLibrary.vue?vue&type=template&id=50f8d42a&":function(e,t,s){function a(){var s=this,e=s.$createElement,a=s._self._c||e;return a("div",{staticClass:"modal fade",attrs:{id:"media-modal",tabindex:"-1"}},[a("div",{staticClass:"modal-dialog modal-lg"},[a("div",{staticClass:"modal-content"},[s._m(0),s._v(" "),a("div",{staticClass:"modal-body"},[a("div",{staticClass:"modal-header"},[a("div",{staticClass:"container"},[a("div",{staticClass:"row mh-100"},[a("div",{staticClass:"col-md-6",attrs:{id:"media-library-list"}},s._l(s.files,function(t){return a("div",{key:t.id,staticClass:"media-item-wrapper"},[a("a",{attrs:{href:"javascript:void(0)"},on:{click:function(e){return s.selectFile(t),!1}}},[a("i",{class:s.getIconClass(t.location),attrs:{"aria-hidden":"true"}}),s._v(" "),a("small",{staticClass:"media-item-title"},[s._v(s._s(t.location.split("/").pop()))])])])}),0),s._v(" "),a("div",{staticClass:"col-md-6",attrs:{id:"media-library-details"}},[a("h4",{staticClass:"text-center"},[s._v("Media Details")]),s._v(" "),a("div",{attrs:{id:"media-item"}},[a("div",{staticClass:"text-center",attrs:{id:"media-icon"}},[this.selectedFile?a("div",["far fa-file-image"===s.getIconClass(this.selectedFile.location)?a("div",[a("img",{staticStyle:{"max-width":"100%","max-height":"100%","object-fit":"contain"},attrs:{src:s.buildSelectedFileUrl()}})]):a("div",[a("i",{class:s.getIconClass(this.selectedFile.location)+" fa-4x",attrs:{"aria-hidden":"true"}})])]):s._e()]),s._v(" "),a("br"),s._v(" "),this.selectedFile?a("div",{staticClass:"text-center",attrs:{id:"media-filename"}},[a("a",{attrs:{href:s.buildSelectedFileUrl(),target:"_blank"}},[s._v("\n "+s._s(this.selectedFile.location.split("/").pop())+"\n ")])]):s._e(),s._v(" "),a("br"),s._v(" "),a("div",{staticClass:"form-group"},[this.selectedFile?a("div",[s._v("\n Link:\n "),a("input",{staticClass:"form-control",attrs:{type:"text",id:"media-link",readonly:""},domProps:{value:s.buildSelectedFileUrl()}})]):a("div",[s._v("\n Link:\n "),a("input",{staticClass:"form-control",attrs:{type:"text",id:"media-link",readonly:""}})])]),s._v(" "),a("div",{staticClass:"form-group text-center"},[a("div",{staticClass:"row"},[a("div",{staticClass:"col-md-6"},[a("button",{staticClass:"btn btn-success w-100",attrs:{id:"media-insert","data-toggle":"tooltip","data-placement":"top",title:"Insert link into editor"},on:{click:s.insertSelectedFile}},[s._v("\n Insert\n ")])]),s._v(" "),a("div",{staticClass:"col-md-3"},[a("button",{staticClass:"btn btn-primary w-100",attrs:{id:"media-download","data-toggle":"tooltip","data-placement":"top",title:"Download file"},on:{click:s.downloadSelectedFile}},[a("i",{staticClass:"fas fa-download"})])]),s._v(" "),a("div",{staticClass:"col-md-3"},[a("button",{staticClass:"btn btn-danger w-100",attrs:{id:"media-delete","data-toggle":"tooltip","data-placement":"top",title:"Delete file"},on:{click:s.deleteSelectedFile}},[a("i",{staticClass:"far fa-trash-alt"})])])])])])])])])]),s._v(" "),s._m(1)]),s._v(" "),a("div",{staticClass:"modal-footer"},[a("div",{staticClass:"float-right"},[a("button",{staticClass:"btn btn-primary media-upload-button",attrs:{type:"submit"},on:{click:s.uploadChosenFiles}},[s._v("\n Upload\n ")])])])])])])}var i=[function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"modal-header"},[s("div",{staticClass:"container"},[s("div",{staticClass:"row"},[s("div",{staticClass:"col-md-12"},[s("h3",{staticClass:"text-center"},[e._v("Media Library")])])])]),e._v(" "),s("button",{staticClass:"close",attrs:{type:"button","data-dismiss":"modal","aria-label":"Close"}},[s("span",{attrs:{"aria-hidden":"true"}},[e._v("×")])])])},function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("form",{attrs:{id:"media-library-upload",enctype:"multipart/form-data"}},[s("div",{staticClass:"form-group"},[s("label",{attrs:{for:"media-files"}},[e._v("\n Upload Files\n ")]),e._v(" "),s("input",{staticClass:"form-control-file",attrs:{type:"file",name:"file",id:"media-files",multiple:""}}),e._v(" "),s("sub",{staticClass:"help-block"},[e._v("\n Attach multiple files using Control+Click or Cmd+Click.\n ")])]),e._v(" "),s("input",{attrs:{type:"hidden",value:"page",name:"type"}})])}];a._withStripped=!0,s.d(t,"a",function(){return a}),s.d(t,"b",function(){return i})},"./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&":function(e,t,s){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var a=o(s("./CTFd/themes/core/assets/js/CTFd.js")),i=o(s("./CTFd/themes/core/assets/js/helpers.js")),n=o(s("./node_modules/moment/moment.js"));function o(e){return e&&e.__esModule?e:{default:e}}var l={props:{type:String,id:Number},data:function(){return{page:1,pages:null,next:null,prev:null,total:null,comment:"",comments:[],urlRoot:a.default.config.urlRoot}},methods:{toLocalTime:function(e){return(0,n.default)(e).local().format("MMMM Do, h:mm:ss A")},nextPage:function(){this.page++,this.loadComments()},prevPage:function(){this.page--,this.loadComments()},getArgs:function(){var e={};return e["".concat(this.$props.type,"_id")]=this.$props.id,e},loadComments:function(){var t=this,e=this.getArgs();e.page=this.page,e.per_page=10,i.default.comments.get_comments(e).then(function(e){return t.page=e.meta.pagination.page,t.pages=e.meta.pagination.pages,t.next=e.meta.pagination.next,t.prev=e.meta.pagination.prev,t.total=e.meta.pagination.total,t.comments=e.data,t.comments})},submitComment:function(){var e=this,t=this.comment.trim();0