From 03e5d61798e6b8df3b4d74b5f3bb1a96fe8199bb Mon Sep 17 00:00:00 2001 From: Abijeet Date: Tue, 16 May 2017 00:40:14 +0530 Subject: [PATCH] #47 Implements the reply and edit functionality for comments. --- app/Comment.php | 22 +++--- app/Http/Controllers/CommentController.php | 23 +++--- app/Repos/CommentRepo.php | 12 +-- resources/assets/js/controllers.js | 61 ++++++++++----- resources/assets/js/directives.js | 77 +++++++++++++++---- resources/views/comments/add.blade.php | 13 ---- .../views/comments/comment-reply.blade.php | 23 +++--- resources/views/comments/comments.blade.php | 4 +- resources/views/comments/list-item.blade.php | 5 +- routes/web.php | 4 +- 10 files changed, 149 insertions(+), 95 deletions(-) delete mode 100644 resources/views/comments/add.blade.php diff --git a/app/Comment.php b/app/Comment.php index 74fcc3fdc..e7df32015 100644 --- a/app/Comment.php +++ b/app/Comment.php @@ -5,8 +5,8 @@ use Illuminate\Support\Facades\DB; class Comment extends Ownable { - protected $fillable = ['text', 'html']; - + protected $fillable = ['text', 'html', 'parent_id']; + /** * Get the entity that this comment belongs to * @return \Illuminate\Database\Eloquent\Relations\MorphTo @@ -15,7 +15,7 @@ class Comment extends Ownable { return $this->morphTo('entity'); } - + /** * Get the page that this comment is in. * @return \Illuminate\Database\Eloquent\Relations\BelongsTo @@ -24,32 +24,32 @@ class Comment extends Ownable { return $this->belongsTo(Page::class); } - + /** * Get the owner of this comment. * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function user() + public function user() { return $this->belongsTo(User::class); } - - public function getCommentsByPage($pageId, $commentId, $pageNum = 0, $limit = 0) { - + + public function getCommentsByPage($pageId, $commentId, $pageNum = 0, $limit = 0) { + $query = static::newQuery(); $query->join('users AS u', 'comments.created_by', '=', 'u.id'); $query->leftJoin('users AS u1', 'comments.updated_by', '=', 'u1.id'); $query->leftJoin('images AS i', 'i.id', '=', 'u.image_id'); $query->selectRaw('comments.id, text, html, comments.created_by, comments.updated_by, comments.created_at, comments.updated_at, ' . 'u.name AS created_by_name, u1.name AS updated_by_name, ' - . '(SELECT count(c.id) FROM bookstack.comments c WHERE c.parent_id = comments.id AND page_id = ?) AS cnt_sub_comments, i.url AS avatar ', + . '(SELECT count(c.id) FROM bookstack.comments c WHERE c.parent_id = comments.id AND page_id = ?) AS cnt_sub_comments, i.url AS avatar ', [$pageId]); - + if (empty($commentId)) { $query->whereRaw('page_id = ? AND parent_id IS NULL', [$pageId]); } else { $query->whereRaw('page_id = ? AND parent_id = ?', [$pageId, $commentId]); - } + } $query->orderBy('created_at'); return $query; } diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php index 8e7b1512a..e1729bbee 100644 --- a/app/Http/Controllers/CommentController.php +++ b/app/Http/Controllers/CommentController.php @@ -2,18 +2,19 @@ use BookStack\Repos\CommentRepo; use BookStack\Repos\EntityRepo; +use BookStack\Comment; use Illuminate\Http\Request; -use Views; // delete -checkOwnablePermission \ class CommentController extends Controller { protected $entityRepo; - public function __construct(EntityRepo $entityRepo, CommentRepo $commentRepo) + public function __construct(EntityRepo $entityRepo, CommentRepo $commentRepo, Comment $comment) { $this->entityRepo = $entityRepo; $this->commentRepo = $commentRepo; + $this->comment = $comment; parent::__construct(); } @@ -43,10 +44,10 @@ class CommentController extends Controller // create a new comment. $this->checkPermission('comment-create-all'); $comment = $this->commentRepo->create($page, $request->only(['text', 'html', 'parent_id'])); - $respMsg = trans('entities.comment_created'); + $respMsg = trans('entities.comment_created'); } else { // update existing comment - // get comment by ID and check if this user has permission to update. + // get comment by ID and check if this user has permission to update. $comment = $this->comment->findOrFail($commentId); $this->checkOwnablePermission('comment-update', $comment); $this->commentRepo->update($comment, $request->all()); @@ -59,7 +60,7 @@ class CommentController extends Controller ]); } - + public function destroy($id) { $comment = $this->comment->findOrFail($id); $this->checkOwnablePermission('comment-delete', $comment); @@ -67,13 +68,13 @@ class CommentController extends Controller // } - public function getComments($pageId, $commentId = null) { + public function getCommentThread($pageId, $commentId = null) { try { $page = $this->entityRepo->getById('page', $pageId, true); } catch (ModelNotFoundException $e) { return response('Not found', 404); } - + if($page->draft) { // cannot add comments to drafts. return response()->json([ @@ -81,15 +82,15 @@ class CommentController extends Controller 'message' => trans('errors.no_comments_for_draft'), ], 400); } - + $this->checkOwnablePermission('page-view', $page); - + $comments = $this->commentRepo->getCommentsForPage($pageId, $commentId); if (empty($commentId)) { // requesting for parent level comments, send the total count as well. $totalComments = $this->commentRepo->getCommentCount($pageId); - return response()->json(array('success' => true, 'comments'=> $comments, 'total' => $totalComments)); + return response()->json(['success' => true, 'comments'=> $comments, 'total' => $totalComments]); } - return response()->json(array('success' => true, 'comments'=> $comments)); + return response()->json(['success' => true, 'comments'=> $comments]); } } diff --git a/app/Repos/CommentRepo.php b/app/Repos/CommentRepo.php index ba34617ed..10b36eb16 100644 --- a/app/Repos/CommentRepo.php +++ b/app/Repos/CommentRepo.php @@ -10,7 +10,7 @@ use BookStack\Page; class CommentRepo { /** * - * @var Comment $comment + * @var Comment $comment */ protected $comment; @@ -25,7 +25,7 @@ class CommentRepo { $comment->fill($data); // new comment $comment->page_id = $page->id; - $comment->created_by = $userId; + $comment->created_by = $userId; $comment->save(); return $comment; } @@ -37,13 +37,13 @@ class CommentRepo { $comment->save(); return $comment; } - - public function getCommentsForPage($pageId, $commentId, $count = 20) { + + public function getCommentsForPage($pageId, $commentId, $count = 20) { // requesting parent comments $query = $this->comment->getCommentsByPage($pageId, $commentId); - return $query->paginate($count); + return $query->paginate($count); } - + public function getCommentCount($pageId) { return $this->comment->where('page_id', '=', $pageId)->count(); } diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js index 732467368..9d5478690 100644 --- a/resources/assets/js/controllers.js +++ b/resources/assets/js/controllers.js @@ -683,29 +683,49 @@ module.exports = function (ngApp, events) { }]); // CommentCrudController - ngApp.controller('CommentAddController', ['$scope', '$http', function ($scope, $http) { + ngApp.controller('CommentReplyController', ['$scope', '$http', function ($scope, $http) { const MarkdownIt = require("markdown-it"); const md = new MarkdownIt({html: true}); let vm = this; $scope.errors = {}; vm.saveComment = function () { - let pageId = $scope.comment.pageId; - let comment = $scope.comment.newComment; - let commentHTML = md.render($scope.comment.newComment); - - $http.post(window.baseUrl(`/ajax/page/${pageId}/comment/`), { + let pageId = $scope.comment.pageId || $scope.pageId; + let comment = $scope.comment.text; + let commentHTML = md.render($scope.comment.text); + let serviceUrl = `/ajax/page/${pageId}/comment/`; + let httpMethod = 'post'; + let errorOp = 'add'; + let reqObj = { text: comment, html: commentHTML - }).then(resp => { - $scope.comment.newComment = ''; + }; + + if ($scope.isEdit === true) { + // this will be set when editing the comment. + serviceUrl = `/ajax/page/${pageId}/comment/${$scope.comment.id}`; + httpMethod = 'put'; + errorOp = 'update'; + } else if ($scope.isReply === true) { + // if its reply, get the parent comment id + reqObj.parent_id = $scope.parentId; + } + $http[httpMethod](window.baseUrl(serviceUrl), reqObj).then(resp => { if (!resp.data || resp.data.status !== 'success') { return events.emit('error', trans('error')); } + if ($scope.isEdit) { + $scope.comment.html = commentHTML; + $scope.$emit('evt.comment-success', $scope.comment.id); + } else { + $scope.comment.text = ''; + $scope.$emit('evt.comment-success', null, true); + } events.emit('success', trans(resp.data.message)); - }, checkError('add')); - - }; - + + }, checkError(errorOp)); + + }; + function checkError(errorGroupName) { $scope.errors[errorGroupName] = {}; return function(response) { @@ -725,19 +745,19 @@ module.exports = function (ngApp, events) { ngApp.controller('CommentListController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) { let vm = this; $scope.errors = {}; - $scope.defaultAvatar = defaultAvatar; + $scope.defaultAvatar = defaultAvatar; vm.totalCommentsStr = 'Loading...'; $scope.editorChange = function (content) { console.log(content); } - + $timeout(function() { $http.get(window.baseUrl(`/ajax/page/${$scope.pageId}/comments/`)).then(resp => { if (!resp.data || resp.data.success !== true) { // TODO : Handle error return; } - vm.comments = resp.data.comments.data; + vm.comments = resp.data.comments.data; vm.totalComments = resp.data.total; // TODO : Fetch message from translate. if (vm.totalComments === 0) { @@ -748,20 +768,19 @@ module.exports = function (ngApp, events) { vm.totalCommentsStr = vm.totalComments + ' Comments' } }, checkError('app')); - }); - + }); + vm.loadSubComments = function(event, comment) { event.preventDefault(); $http.get(window.baseUrl(`/ajax/page/${$scope.pageId}/comments/${comment.id}/sub-comments`)).then(resp => { - console.log(resp); if (!resp.data || resp.data.success !== true) { return; } - comment.is_loaded = true; - comment.comments = resp.data.comments.data; + comment.is_loaded = true; + comment.comments = resp.data.comments.data; }, checkError('app')); }; - + function checkError(errorGroupName) { $scope.errors[errorGroupName] = {}; return function(response) { diff --git a/resources/assets/js/directives.js b/resources/assets/js/directives.js index dded45dd7..6c556acc9 100644 --- a/resources/assets/js/directives.js +++ b/resources/assets/js/directives.js @@ -818,29 +818,62 @@ module.exports = function (ngApp, events) { } }; }]); - - ngApp.directive('commentReply', ['$timeout', function ($timeout) { + + ngApp.directive('commentReply', [function () { return { restrict: 'E', templateUrl: 'comment-reply.html', scope: { - + pageId: '=', + parentId: '=' }, - link: function (scope, element, attr) { - + link: function (scope, element) { + scope.isReply = true; + scope.$on('evt.comment-success', function (event) { + // no need for the event to do anything more. + event.stopPropagation(); + event.preventDefault(); + element.remove(); + scope.$destroy(); + }); } } - }]); - ngApp.directive('commentReplyLink', ['$document', '$compile', function ($document, $compile) { - return { + ngApp.directive('commentEdit', [function () { + return { + restrict: 'E', + templateUrl: 'comment-reply.html', + scope: { + comment: '=', + }, + link: function (scope, element) { + scope.isEdit = true; + scope.$on('evt.comment-success', function (event, commentId) { + // no need for the event to do anything more. + event.stopPropagation(); + event.preventDefault(); + if (commentId === scope.comment.id && !scope.isNew) { + element.remove(); + scope.$destroy(); + } + }); + } + } + }]); + + + ngApp.directive('commentReplyLink', ['$document', '$compile', '$http', function ($document, $compile, $http) { + return { + scope: { + comment: '=' + }, link: function (scope, element, attr) { element.on('$destroy', function () { element.off('click'); - scope.$destroy(); + scope.$destroy(); }); - + element.on('click', function () { var $container = element.parents('.comment-box').first(); if (!$container.length) { @@ -848,21 +881,31 @@ module.exports = function (ngApp, events) { return; } if (attr.noCommentReplyDupe) { - removeDupe(); + removeDupe(); } - var compiledHTML = $compile('')(scope); - $container.append(compiledHTML); + + compileHtml($container, scope, attr.isReply === 'true'); }); } }; - - + + function compileHtml($container, scope, isReply) { + let lnkFunc = null; + if (isReply) { + lnkFunc = $compile(''); + } else { + lnkFunc = $compile(''); + } + var compiledHTML = lnkFunc(scope); + $container.append(compiledHTML); + } + function removeDupe() { - let $existingElement = $document.find('comment-reply'); + let $existingElement = $document.find('.comments-list comment-reply'); if (!$existingElement.length) { return; } - + $existingElement.remove(); } }]); diff --git a/resources/views/comments/add.blade.php b/resources/views/comments/add.blade.php deleted file mode 100644 index 7655675ae..000000000 --- a/resources/views/comments/add.blade.php +++ /dev/null @@ -1,13 +0,0 @@ -
-
- - - -
-
- -@if($errors->has('markdown')) -
{{ $errors->first('markdown') }}
-@endif \ No newline at end of file diff --git a/resources/views/comments/comment-reply.blade.php b/resources/views/comments/comment-reply.blade.php index d5ceb55c6..74a13edff 100644 --- a/resources/views/comments/comment-reply.blade.php +++ b/resources/views/comments/comment-reply.blade.php @@ -1,10 +1,13 @@ - -
-
- -
- - -
\ No newline at end of file +
+
+ + + +
+
+ +@if($errors->has('markdown')) +
{{ $errors->first('markdown') }}
+@endif \ No newline at end of file diff --git a/resources/views/comments/comments.blade.php b/resources/views/comments/comments.blade.php index b3669faa9..8c4cb9860 100644 --- a/resources/views/comments/comments.blade.php +++ b/resources/views/comments/comments.blade.php @@ -2,7 +2,7 @@ @include('comments/list-item')

@{{vm.totalCommentsStr}}

@@ -13,4 +13,4 @@
-@include('comments/add', ['pageId' => $pageId]) \ No newline at end of file +@include('comments/comment-reply', ['pageId' => $pageId]) \ No newline at end of file diff --git a/resources/views/comments/list-item.blade.php b/resources/views/comments/list-item.blade.php index aecc0c26b..290fe4a8b 100644 --- a/resources/views/comments/list-item.blade.php +++ b/resources/views/comments/list-item.blade.php @@ -6,12 +6,13 @@
@{{ ::comment.created_by_name }}
-
+
diff --git a/routes/web.php b/routes/web.php index 2ac212e62..076bc8110 100644 --- a/routes/web.php +++ b/routes/web.php @@ -123,8 +123,8 @@ Route::group(['middleware' => 'auth'], function () { Route::post('/ajax/page/{pageId}/comment/', 'CommentController@save'); Route::put('/ajax/page/{pageId}/comment/{commentId}', 'CommentController@save'); Route::delete('/ajax/comment/{id}', 'CommentController@destroy'); - Route::get('/ajax/page/{pageId}/comments/{commentId}/sub-comments', 'CommentController@getComments'); - Route::get('/ajax/page/{pageId}/comments/', 'CommentController@getComments'); + Route::get('/ajax/page/{pageId}/comments/{commentId}/sub-comments', 'CommentController@getCommentThread'); + Route::get('/ajax/page/{pageId}/comments/', 'CommentController@getCommentThread'); // Links Route::get('/link/{id}', 'PageController@redirectFromLink');