]> BookStack Code Mirror - bookstack/commitdiff
#47 Implements the reply and edit functionality for comments.
authorAbijeet <redacted>
Mon, 15 May 2017 19:10:14 +0000 (00:40 +0530)
committerAbijeet <redacted>
Mon, 15 May 2017 19:10:14 +0000 (00:40 +0530)
app/Comment.php
app/Http/Controllers/CommentController.php
app/Repos/CommentRepo.php
resources/assets/js/controllers.js
resources/assets/js/directives.js
resources/views/comments/add.blade.php [deleted file]
resources/views/comments/comment-reply.blade.php
resources/views/comments/comments.blade.php
resources/views/comments/list-item.blade.php
routes/web.php

index 74fcc3fdcc6c14fdd2c332c9060ce54865d38226..e7df3201514a503d35a2f87186a93dd713631c79 100644 (file)
@@ -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;
     }
index 8e7b1512aa07230a5672fac993605a088c45eacb..e1729bbeeb262283c58ad1ddb3d50f1f32557f74 100644 (file)
@@ -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]);
     }
 }
index ba34617ed8fe0a53dcd3a21a4c0e6d49ec55268c..10b36eb16c19dfeb8a772d1ef72aa466e63a9ab1 100644 (file)
@@ -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();
     }
index 7324673686c4d10faf0989c365bce31cc00a3701..9d5478690cdf1ab236e3aca6dec31d965dfcc121 100644 (file)
@@ -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) {
index dded45dd720c621ab6653f4b80a303f78ebaa0bc..6c556acc90c9f2143c79577b5b6edcef37b73fcc 100644 (file)
@@ -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('<comment-reply></comment-reply>')(scope);
-                    $container.append(compiledHTML);
+
+                    compileHtml($container, scope, attr.isReply === 'true');
                 });
             }
         };
-        
-        
+
+        function compileHtml($container, scope, isReply) {
+            let lnkFunc = null;
+            if (isReply) {
+                lnkFunc = $compile('<comment-reply page-id="comment.pageId" parent-id="comment.id"></comment-reply>');
+            } else {
+                lnkFunc = $compile('<comment-edit comment="comment"></comment-add>');
+            }
+            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 (file)
index 7655675..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="comment-editor" ng-controller="CommentAddController as vm" ng-cloak>
-    <form novalidate>
-        <textarea name="markdown" rows="3" ng-model="comment.newComment" placeholder="{{ trans('entities.comment_placeholder') }}"
-                  @if($errors->has('markdown')) class="neg" @endif>@if(isset($model) ||
-                  old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
-        <input type="hidden" ng-model="pageId" name="comment.pageId" value="{{$pageId}}" ng-init="comment.pageId = {{$pageId }}">
-        <button type="submit" class="button pos" ng-click="vm.saveComment()">Save</button>
-    </form>
-</div>
-
-@if($errors->has('markdown'))
-    <div class="text-neg text-small">{{ $errors->first('markdown') }}</div>
-@endif
\ No newline at end of file
index d5ceb55c66317978ef69e8b0375f7b6a72b6ec33..74a13edff4fab8e3c5538a05a39cc5e23c807287 100644 (file)
@@ -1,10 +1,13 @@
-<!-- TODO :: needs to be merged with add.blade.php -->
-<form novalidate>
-        <div simple-markdown-input smd-model="comment.newComment" smd-get-content="getCommentHTML" smd-clear="clearInput">
-            <textarea name="markdown" rows="3"
-                      @if($errors->has('markdown')) class="neg" @endif>@if(isset($model) ||
-                      old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
-        </div>
-        <input type="hidden" ng-model="pageId" name="comment.pageId" value="{{$pageId}}" ng-init="comment.pageId = {{$pageId }}">
-        <button type="submit" class="button pos" ng-click="vm.saveComment()">Save</button>
-</form>
\ No newline at end of file
+<div class="comment-editor" ng-controller="CommentReplyController as vm" ng-cloak>
+    <form novalidate>
+        <textarea name="markdown" rows="3" ng-model="comment.text" placeholder="{{ trans('entities.comment_placeholder') }}"
+                  @if($errors->has('markdown')) class="neg" @endif>@if(isset($model) ||
+                  old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
+        <input type="hidden" ng-model="comment.pageId" name="comment.pageId" value="{{$pageId}}" ng-init="comment.pageId = {{$pageId }}">
+        <button type="submit" class="button pos" ng-click="vm.saveComment(isReply)">Save</button>
+    </form>
+</div>
+
+@if($errors->has('markdown'))
+    <div class="text-neg text-small">{{ $errors->first('markdown') }}</div>
+@endif
\ No newline at end of file
index b3669faa9bdb65b128d1dbe3d2ebc09c8e3834a7..8c4cb9860a897ef5e175b0de77e56b74e6857842 100644 (file)
@@ -2,7 +2,7 @@
     @include('comments/list-item')
 </script>
 <script type="text/ng-template" id="comment-reply.html">
-    @include('comments/comment-reply')
+    @include('comments/comment-reply', ['pageId' => $pageId])
 </script>
 <div ng-controller="CommentListController as vm" ng-init="pageId = <?= $page->id ?>" class="comments-list" ng-cloak>   
 <h3>@{{vm.totalCommentsStr}}</h3>
@@ -13,4 +13,4 @@
         </div>
     </div>
 </div>
-@include('comments/add', ['pageId' => $pageId])
\ No newline at end of file
+@include('comments/comment-reply', ['pageId' => $pageId])
\ No newline at end of file
index aecc0c26be1b2b0a04754803e27aa36612eeeb00..290fe4a8b03730357c48046543187bad2f4f7b9c 100644 (file)
@@ -6,12 +6,13 @@
         <div class="comment-header">
             @{{ ::comment.created_by_name }}
         </div>
-        <div ng-bind-html="::comment.html" class="comment-body">
+        <div ng-bind-html="comment.html" class="comment-body">
 
         </div>
         <div class="comment-actions">
             <ul>
-                <li><a href="#" comment-reply-link no-comment-reply-dupe="true">Reply</a></li>
+                <li><a href="#" comment-reply-link no-comment-reply-dupe="true" comment="comment" is-reply="true">Reply</a></li>
+                <li><a href="#" comment-reply-link no-comment-reply-dupe="true" comment="comment">Edit</a></li>
                 <li><a href="#">@{{::comment.created_at}}</a></li>            
             </ul>                
         </div>
index 2ac212e620975c58c1152415b8a662141d64e9dd..076bc8110c17af89b11ba004756e8a9c47ff0d3f 100644 (file)
@@ -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');