mirror of
				https://github.com/BookStackApp/BookStack.git
				synced 2025-11-03 02:13:16 +03:00 
			
		
		
		
	Made some changes to the comment system
Changed to be rendered server side along with page content. Changed deletion to fully delete comments from the database. Added 'local_id' to comments for referencing. Updated reply system to be non-nested (Incomplete) Made database comment format entity-agnostic to be more future proof. Updated designs of comment sections.
This commit is contained in:
		@@ -1,12 +1,10 @@
 | 
				
			|||||||
<?php
 | 
					<?php namespace BookStack;
 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace BookStack;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Comment extends Ownable
 | 
					class Comment extends Ownable
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public $sub_comments = [];
 | 
					 | 
				
			||||||
    protected $fillable = ['text', 'html', 'parent_id'];
 | 
					    protected $fillable = ['text', 'html', 'parent_id'];
 | 
				
			||||||
    protected $appends = ['created', 'updated', 'sub_comments'];
 | 
					    protected $appends = ['created', 'updated'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the entity that this comment belongs to
 | 
					     * Get the entity that this comment belongs to
 | 
				
			||||||
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
 | 
					     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
 | 
				
			||||||
@@ -17,80 +15,29 @@ class Comment extends Ownable
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the page that this comment is in.
 | 
					     * Check if a comment has been updated since creation.
 | 
				
			||||||
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
 | 
					     * @return bool
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function page()
 | 
					    public function isUpdated()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->belongsTo(Page::class);
 | 
					        return $this->updated_at->timestamp > $this->created_at->timestamp;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the owner of this comment.
 | 
					     * Get created date as a relative diff.
 | 
				
			||||||
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
 | 
					     * @return mixed
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function user()
 | 
					    public function getCreatedAttribute()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->belongsTo(User::class);
 | 
					        return $this->created_at->diffForHumans();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /*
 | 
					    /**
 | 
				
			||||||
     * Not being used, but left here because might be used in the future for performance reasons.
 | 
					     * Get updated date as a relative diff.
 | 
				
			||||||
 | 
					     * @return mixed
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getPageComments($pageId) {
 | 
					    public function getUpdatedAttribute()
 | 
				
			||||||
        $query = static::newQuery();
 | 
					    {
 | 
				
			||||||
        $query->join('users AS u', 'comments.created_by', '=', 'u.id');
 | 
					        return $this->updated_at->diffForHumans();
 | 
				
			||||||
        $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, comments.parent_id, '
 | 
					 | 
				
			||||||
                . 'u.name AS created_by_name, u1.name AS updated_by_name, '
 | 
					 | 
				
			||||||
                . 'i.url AS avatar ');
 | 
					 | 
				
			||||||
        $query->whereRaw('page_id = ?', [$pageId]);
 | 
					 | 
				
			||||||
        $query->orderBy('created_at');
 | 
					 | 
				
			||||||
        return $query->get();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function getAllPageComments($pageId) {
 | 
					 | 
				
			||||||
        return self::where('page_id', '=', $pageId)->with(['createdBy' => function($query) {
 | 
					 | 
				
			||||||
            $query->select('id', 'name', 'image_id');
 | 
					 | 
				
			||||||
        }, 'updatedBy' => function($query) {
 | 
					 | 
				
			||||||
            $query->select('id', 'name');
 | 
					 | 
				
			||||||
        }, 'createdBy.avatar' => function ($query) {
 | 
					 | 
				
			||||||
            $query->select('id', 'path', 'url');
 | 
					 | 
				
			||||||
        }])->get();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function getCommentById($commentId) {
 | 
					 | 
				
			||||||
        return self::where('id', '=', $commentId)->with(['createdBy' => function($query) {
 | 
					 | 
				
			||||||
            $query->select('id', 'name', 'image_id');
 | 
					 | 
				
			||||||
        }, 'updatedBy' => function($query) {
 | 
					 | 
				
			||||||
            $query->select('id', 'name');
 | 
					 | 
				
			||||||
        }, 'createdBy.avatar' => function ($query) {
 | 
					 | 
				
			||||||
            $query->select('id', 'path', 'url');
 | 
					 | 
				
			||||||
        }])->first();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function getCreatedAttribute() {
 | 
					 | 
				
			||||||
        $created = [
 | 
					 | 
				
			||||||
            'day_time_str' => $this->created_at->toDayDateTimeString(),
 | 
					 | 
				
			||||||
            'diff' => $this->created_at->diffForHumans()
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
        return $created;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function getUpdatedAttribute() {
 | 
					 | 
				
			||||||
        if (empty($this->updated_at)) {
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        $updated = [
 | 
					 | 
				
			||||||
            'day_time_str' => $this->updated_at->toDayDateTimeString(),
 | 
					 | 
				
			||||||
            'diff' => $this->updated_at->diffForHumans()
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
        return $updated;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function getSubCommentsAttribute() {
 | 
					 | 
				
			||||||
        return $this->sub_comments;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
<?php namespace BookStack;
 | 
					<?php namespace BookStack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Relations\MorphMany;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Entity extends Ownable
 | 
					class Entity extends Ownable
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -65,6 +67,16 @@ class Entity extends Ownable
 | 
				
			|||||||
        return $this->morphMany(Tag::class, 'entity')->orderBy('order', 'asc');
 | 
					        return $this->morphMany(Tag::class, 'entity')->orderBy('order', 'asc');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the comments for an entity
 | 
				
			||||||
 | 
					     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function comments()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->morphMany(Comment::class, 'entity')->orderBy('created_at', 'asc');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the related search terms.
 | 
					     * Get the related search terms.
 | 
				
			||||||
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
 | 
					     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,22 +2,34 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use BookStack\Repos\CommentRepo;
 | 
					use BookStack\Repos\CommentRepo;
 | 
				
			||||||
use BookStack\Repos\EntityRepo;
 | 
					use BookStack\Repos\EntityRepo;
 | 
				
			||||||
use BookStack\Comment;
 | 
					use Illuminate\Database\Eloquent\ModelNotFoundException;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CommentController extends Controller
 | 
					class CommentController extends Controller
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    protected $entityRepo;
 | 
					    protected $entityRepo;
 | 
				
			||||||
 | 
					    protected $commentRepo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function __construct(EntityRepo $entityRepo, CommentRepo $commentRepo, Comment $comment)
 | 
					    /**
 | 
				
			||||||
 | 
					     * CommentController constructor.
 | 
				
			||||||
 | 
					     * @param EntityRepo $entityRepo
 | 
				
			||||||
 | 
					     * @param CommentRepo $commentRepo
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(EntityRepo $entityRepo, CommentRepo $commentRepo)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->entityRepo = $entityRepo;
 | 
					        $this->entityRepo = $entityRepo;
 | 
				
			||||||
        $this->commentRepo = $commentRepo;
 | 
					        $this->commentRepo = $commentRepo;
 | 
				
			||||||
        $this->comment = $comment;
 | 
					 | 
				
			||||||
        parent::__construct();
 | 
					        parent::__construct();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function save(Request $request, $pageId, $commentId = null)
 | 
					    /**
 | 
				
			||||||
 | 
					     * Save a new comment for a Page
 | 
				
			||||||
 | 
					     * @param Request $request
 | 
				
			||||||
 | 
					     * @param integer $pageId
 | 
				
			||||||
 | 
					     * @param null|integer $commentId
 | 
				
			||||||
 | 
					     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function savePageComment(Request $request, $pageId, $commentId = null)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->validate($request, [
 | 
					        $this->validate($request, [
 | 
				
			||||||
            'text' => 'required|string',
 | 
					            'text' => 'required|string',
 | 
				
			||||||
@@ -30,70 +42,50 @@ class CommentController extends Controller
 | 
				
			|||||||
            return response('Not found', 404);
 | 
					            return response('Not found', 404);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if($page->draft) {
 | 
					 | 
				
			||||||
            // cannot add comments to drafts.
 | 
					 | 
				
			||||||
            return response()->json([
 | 
					 | 
				
			||||||
                'status' => 'error',
 | 
					 | 
				
			||||||
                'message' => trans('errors.cannot_add_comment_to_draft'),
 | 
					 | 
				
			||||||
            ], 400);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->checkOwnablePermission('page-view', $page);
 | 
					        $this->checkOwnablePermission('page-view', $page);
 | 
				
			||||||
        if (empty($commentId)) {
 | 
					
 | 
				
			||||||
            // create a new comment.
 | 
					        // Prevent adding comments to draft pages
 | 
				
			||||||
            $this->checkPermission('comment-create-all');
 | 
					        if ($page->draft) {
 | 
				
			||||||
            $comment = $this->commentRepo->create($page, $request->only(['text', 'html', 'parent_id']));
 | 
					            return $this->jsonError(trans('errors.cannot_add_comment_to_draft'), 400);
 | 
				
			||||||
            $respMsg = trans('entities.comment_created');
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            // update existing comment
 | 
					 | 
				
			||||||
            // 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());
 | 
					 | 
				
			||||||
            $respMsg = trans('entities.comment_updated');
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $comment = $this->commentRepo->getCommentById($comment->id);
 | 
					        // Create a new comment.
 | 
				
			||||||
 | 
					        $this->checkPermission('comment-create-all');
 | 
				
			||||||
        return response()->json([
 | 
					        $comment = $this->commentRepo->create($page, $request->all());
 | 
				
			||||||
            'status'    => 'success',
 | 
					        return view('comments/comment', ['comment' => $comment]);
 | 
				
			||||||
            'message'   => $respMsg,
 | 
					 | 
				
			||||||
            'comment'   => $comment
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function destroy($id) {
 | 
					    /**
 | 
				
			||||||
        $comment = $this->comment->findOrFail($id);
 | 
					     * Update an existing comment.
 | 
				
			||||||
 | 
					     * @param Request $request
 | 
				
			||||||
 | 
					     * @param integer $commentId
 | 
				
			||||||
 | 
					     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function update(Request $request, $commentId)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->validate($request, [
 | 
				
			||||||
 | 
					            'text' => 'required|string',
 | 
				
			||||||
 | 
					            'html' => 'required|string',
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $comment = $this->commentRepo->getById($commentId);
 | 
				
			||||||
 | 
					        $this->checkOwnablePermission('page-view', $comment->entity);
 | 
				
			||||||
 | 
					        $this->checkOwnablePermission('comment-update', $comment);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $comment = $this->commentRepo->update($comment, $request->all());
 | 
				
			||||||
 | 
					        return view('comments/comment', ['comment' => $comment]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Delete a comment from the system.
 | 
				
			||||||
 | 
					     * @param integer $id
 | 
				
			||||||
 | 
					     * @return \Illuminate\Http\JsonResponse
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function destroy($id)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $comment = $this->commentRepo->getById($id);
 | 
				
			||||||
        $this->checkOwnablePermission('comment-delete', $comment);
 | 
					        $this->checkOwnablePermission('comment-delete', $comment);
 | 
				
			||||||
        $this->commentRepo->delete($comment);
 | 
					        $this->commentRepo->delete($comment);
 | 
				
			||||||
        $updatedComment = $this->commentRepo->getCommentById($comment->id);
 | 
					        return response()->json(['message' => trans('entities.comment_deleted')]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        return response()->json([
 | 
					 | 
				
			||||||
            'status' => 'success',
 | 
					 | 
				
			||||||
            'message' => trans('entities.comment_deleted'),
 | 
					 | 
				
			||||||
            'comment' => $updatedComment
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function getPageComments($pageId) {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            $page = $this->entityRepo->getById('page', $pageId, true);
 | 
					 | 
				
			||||||
        } catch (ModelNotFoundException $e) {
 | 
					 | 
				
			||||||
            return response('Not found', 404);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->checkOwnablePermission('page-view', $page);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $comments = $this->commentRepo->getPageComments($pageId);
 | 
					 | 
				
			||||||
        return response()->json(['status' => 'success', 'comments'=> $comments['comments'],
 | 
					 | 
				
			||||||
            'total' => $comments['total'], 'permissions' => [
 | 
					 | 
				
			||||||
                'comment_create' => $this->currentUser->can('comment-create-all'),
 | 
					 | 
				
			||||||
                'comment_update_own' => $this->currentUser->can('comment-update-own'),
 | 
					 | 
				
			||||||
                'comment_update_all' => $this->currentUser->can('comment-update-all'),
 | 
					 | 
				
			||||||
                'comment_delete_all' => $this->currentUser->can('comment-delete-all'),
 | 
					 | 
				
			||||||
                'comment_delete_own' => $this->currentUser->can('comment-delete-own'),
 | 
					 | 
				
			||||||
            ], 'user_id' => $this->currentUser->id]);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -161,6 +161,7 @@ class PageController extends Controller
 | 
				
			|||||||
        $pageContent = $this->entityRepo->renderPage($page);
 | 
					        $pageContent = $this->entityRepo->renderPage($page);
 | 
				
			||||||
        $sidebarTree = $this->entityRepo->getBookChildren($page->book);
 | 
					        $sidebarTree = $this->entityRepo->getBookChildren($page->book);
 | 
				
			||||||
        $pageNav = $this->entityRepo->getPageNav($pageContent);
 | 
					        $pageNav = $this->entityRepo->getPageNav($pageContent);
 | 
				
			||||||
 | 
					        $page->load(['comments.createdBy']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Views::add($page);
 | 
					        Views::add($page);
 | 
				
			||||||
        $this->setPageTitle($page->getShortName());
 | 
					        $this->setPageTitle($page->getShortName());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,10 +66,6 @@ class Page extends Entity
 | 
				
			|||||||
        return $this->hasMany(Attachment::class, 'uploaded_to')->orderBy('order', 'asc');
 | 
					        return $this->hasMany(Attachment::class, 'uploaded_to')->orderBy('order', 'asc');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function comments() {
 | 
					 | 
				
			||||||
        return $this->hasMany(Comment::class, 'page_id')->orderBy('created_on', 'asc');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the url for this page.
 | 
					     * Get the url for this page.
 | 
				
			||||||
     * @param string|bool $path
 | 
					     * @param string|bool $path
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,105 +1,87 @@
 | 
				
			|||||||
<?php namespace BookStack\Repos;
 | 
					<?php namespace BookStack\Repos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use BookStack\Comment;
 | 
					use BookStack\Comment;
 | 
				
			||||||
use BookStack\Page;
 | 
					use BookStack\Entity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Class TagRepo
 | 
					 * Class CommentRepo
 | 
				
			||||||
 * @package BookStack\Repos
 | 
					 * @package BookStack\Repos
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class CommentRepo {
 | 
					class CommentRepo {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @var Comment $comment
 | 
					     * @var Comment $comment
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $comment;
 | 
					    protected $comment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * CommentRepo constructor.
 | 
				
			||||||
 | 
					     * @param Comment $comment
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    public function __construct(Comment $comment)
 | 
					    public function __construct(Comment $comment)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->comment = $comment;
 | 
					        $this->comment = $comment;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function create (Page $page, $data = []) {
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a comment by ID.
 | 
				
			||||||
 | 
					     * @param $id
 | 
				
			||||||
 | 
					     * @return Comment|\Illuminate\Database\Eloquent\Model
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function getById($id)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->comment->newQuery()->findOrFail($id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Create a new comment on an entity.
 | 
				
			||||||
 | 
					     * @param Entity $entity
 | 
				
			||||||
 | 
					     * @param array $data
 | 
				
			||||||
 | 
					     * @return Comment
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function create (Entity $entity, $data = [])
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        $userId = user()->id;
 | 
					        $userId = user()->id;
 | 
				
			||||||
        $comment = $this->comment->newInstance();
 | 
					        $comment = $this->comment->newInstance($data);
 | 
				
			||||||
        $comment->fill($data);
 | 
					 | 
				
			||||||
        // new comment
 | 
					 | 
				
			||||||
        $comment->page_id = $page->id;
 | 
					 | 
				
			||||||
        $comment->created_by = $userId;
 | 
					        $comment->created_by = $userId;
 | 
				
			||||||
        $comment->updated_at = null;
 | 
					 | 
				
			||||||
        $comment->save();
 | 
					 | 
				
			||||||
        return $comment;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public function update($comment, $input, $activeOnly = true) {
 | 
					 | 
				
			||||||
        $userId = user()->id;
 | 
					 | 
				
			||||||
        $comment->updated_by = $userId;
 | 
					        $comment->updated_by = $userId;
 | 
				
			||||||
        $comment->fill($input);
 | 
					        $comment->local_id = $this->getNextLocalId($entity);
 | 
				
			||||||
 | 
					        $entity->comments()->save($comment);
 | 
				
			||||||
        // only update active comments by default.
 | 
					 | 
				
			||||||
        $whereClause = ['active' => 1];
 | 
					 | 
				
			||||||
        if (!$activeOnly) {
 | 
					 | 
				
			||||||
            $whereClause = [];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        $comment->update($whereClause);
 | 
					 | 
				
			||||||
        return $comment;
 | 
					        return $comment;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function delete($comment) {
 | 
					    /**
 | 
				
			||||||
        $comment->text = trans('entities.comment_deleted');
 | 
					     * Update an existing comment.
 | 
				
			||||||
        $comment->html = trans('entities.comment_deleted');
 | 
					     * @param Comment $comment
 | 
				
			||||||
        $comment->active = false;
 | 
					     * @param array $input
 | 
				
			||||||
        $userId = user()->id;
 | 
					     * @return mixed
 | 
				
			||||||
        $comment->updated_by = $userId;
 | 
					     */
 | 
				
			||||||
        $comment->save();
 | 
					    public function update($comment, $input)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $comment->updated_by = user()->id;
 | 
				
			||||||
 | 
					        $comment->update($input);
 | 
				
			||||||
        return $comment;
 | 
					        return $comment;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getPageComments($pageId) {
 | 
					    /**
 | 
				
			||||||
        $comments = $this->comment->getAllPageComments($pageId);
 | 
					     * Delete a comment from the system.
 | 
				
			||||||
        $index = [];
 | 
					     * @param Comment $comment
 | 
				
			||||||
        $totalComments = count($comments);
 | 
					     * @return mixed
 | 
				
			||||||
        $finalCommentList = [];
 | 
					     */
 | 
				
			||||||
 | 
					    public function delete($comment)
 | 
				
			||||||
        // normalizing the response.
 | 
					    {
 | 
				
			||||||
        for ($i = 0; $i < count($comments); ++$i) {
 | 
					        return $comment->delete();
 | 
				
			||||||
            $comment = $this->normalizeComment($comments[$i]);
 | 
					 | 
				
			||||||
            $parentId = $comment->parent_id;
 | 
					 | 
				
			||||||
            if (empty($parentId)) {
 | 
					 | 
				
			||||||
                $finalCommentList[] = $comment;
 | 
					 | 
				
			||||||
                $index[$comment->id] = $comment;
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (empty($index[$parentId])) {
 | 
					 | 
				
			||||||
                // weird condition should not happen.
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (empty($index[$parentId]->sub_comments)) {
 | 
					 | 
				
			||||||
                $index[$parentId]->sub_comments = [];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            array_push($index[$parentId]->sub_comments, $comment);
 | 
					 | 
				
			||||||
            $index[$comment->id] = $comment;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return [
 | 
					 | 
				
			||||||
            'comments' => $finalCommentList,
 | 
					 | 
				
			||||||
            'total' => $totalComments
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getCommentById($commentId) {
 | 
					    /**
 | 
				
			||||||
        return $this->normalizeComment($this->comment->getCommentById($commentId));
 | 
					     * Get the next local ID relative to the linked entity.
 | 
				
			||||||
    }
 | 
					     * @param Entity $entity
 | 
				
			||||||
 | 
					     * @return int
 | 
				
			||||||
    private function normalizeComment($comment) {
 | 
					     */
 | 
				
			||||||
        if (empty($comment)) {
 | 
					    protected function getNextLocalId(Entity $entity)
 | 
				
			||||||
            return;
 | 
					    {
 | 
				
			||||||
        }
 | 
					        $comments = $entity->comments()->orderBy('local_id', 'desc')->first();
 | 
				
			||||||
        $comment->createdBy->avatar_url = $comment->createdBy->getAvatar(50);
 | 
					        if ($comments === null) return 1;
 | 
				
			||||||
        $comment->createdBy->profile_url = $comment->createdBy->getProfileUrl();
 | 
					        return $comments->local_id + 1;
 | 
				
			||||||
        if (!empty($comment->updatedBy)) {
 | 
					 | 
				
			||||||
            $comment->updatedBy->profile_url = $comment->updatedBy->getProfileUrl();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return $comment;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -33,6 +33,7 @@ class TagRepo
 | 
				
			|||||||
     * @param $entityType
 | 
					     * @param $entityType
 | 
				
			||||||
     * @param $entityId
 | 
					     * @param $entityId
 | 
				
			||||||
     * @param string $action
 | 
					     * @param string $action
 | 
				
			||||||
 | 
					     * @return \Illuminate\Database\Eloquent\Model|null|static
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getEntity($entityType, $entityId, $action = 'view')
 | 
					    public function getEntity($entityType, $entityId, $action = 'view')
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,17 +15,19 @@ class CreateCommentsTable extends Migration
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        Schema::create('comments', function (Blueprint $table) {
 | 
					        Schema::create('comments', function (Blueprint $table) {
 | 
				
			||||||
            $table->increments('id')->unsigned();
 | 
					            $table->increments('id')->unsigned();
 | 
				
			||||||
            $table->integer('page_id')->unsigned();
 | 
					            $table->integer('entity_id')->unsigned();
 | 
				
			||||||
 | 
					            $table->string('entity_type');
 | 
				
			||||||
            $table->longText('text')->nullable();
 | 
					            $table->longText('text')->nullable();
 | 
				
			||||||
            $table->longText('html')->nullable();
 | 
					            $table->longText('html')->nullable();
 | 
				
			||||||
            $table->integer('parent_id')->unsigned()->nullable();
 | 
					            $table->integer('parent_id')->unsigned()->nullable();
 | 
				
			||||||
 | 
					            $table->integer('local_id')->unsigned()->nullable();
 | 
				
			||||||
            $table->integer('created_by')->unsigned();
 | 
					            $table->integer('created_by')->unsigned();
 | 
				
			||||||
            $table->integer('updated_by')->unsigned()->nullable();
 | 
					            $table->integer('updated_by')->unsigned()->nullable();
 | 
				
			||||||
            $table->boolean('active')->default(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            $table->index(['page_id']);
 | 
					 | 
				
			||||||
            $table->timestamps();
 | 
					            $table->timestamps();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $table->index(['entity_id', 'entity_type']);
 | 
				
			||||||
 | 
					            $table->index(['local_id']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Assign new comment permissions to admin role
 | 
					            // Assign new comment permissions to admin role
 | 
				
			||||||
            $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
 | 
					            $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
 | 
				
			||||||
            // Create & attach new entity permissions
 | 
					            // Create & attach new entity permissions
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ let componentMapping = {
 | 
				
			|||||||
    'entity-selector': require('./entity-selector'),
 | 
					    'entity-selector': require('./entity-selector'),
 | 
				
			||||||
    'sidebar': require('./sidebar'),
 | 
					    'sidebar': require('./sidebar'),
 | 
				
			||||||
    'page-picker': require('./page-picker'),
 | 
					    'page-picker': require('./page-picker'),
 | 
				
			||||||
 | 
					    'page-comments': require('./page-comments'),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.components = {};
 | 
					window.components = {};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										137
									
								
								resources/assets/js/components/page-comments.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								resources/assets/js/components/page-comments.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
				
			|||||||
 | 
					const MarkdownIt = require("markdown-it");
 | 
				
			||||||
 | 
					const md = new MarkdownIt({ html: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PageComments {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(elem) {
 | 
				
			||||||
 | 
					        this.elem = elem;
 | 
				
			||||||
 | 
					        this.pageId = Number(elem.getAttribute('page-id'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.formContainer = elem.querySelector('[comment-form-container]');
 | 
				
			||||||
 | 
					        this.form = this.formContainer.querySelector('form');
 | 
				
			||||||
 | 
					        this.formInput = this.form.querySelector('textarea');
 | 
				
			||||||
 | 
					        this.container = elem.querySelector('[comment-container]');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TODO - Handle elem usage when no permissions
 | 
				
			||||||
 | 
					        this.form.addEventListener('submit', this.saveComment.bind(this));
 | 
				
			||||||
 | 
					        this.elem.addEventListener('click', this.handleAction.bind(this));
 | 
				
			||||||
 | 
					        this.elem.addEventListener('submit', this.updateComment.bind(this));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.editingComment = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    handleAction(event) {
 | 
				
			||||||
 | 
					        let actionElem = event.target.closest('[action]');
 | 
				
			||||||
 | 
					        if (actionElem === null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let action = actionElem.getAttribute('action');
 | 
				
			||||||
 | 
					        if (action === 'edit') this.editComment(actionElem.closest('[comment]'));
 | 
				
			||||||
 | 
					        if (action === 'closeUpdateForm') this.closeUpdateForm();
 | 
				
			||||||
 | 
					        if (action === 'delete') this.deleteComment(actionElem.closest('[comment]'));
 | 
				
			||||||
 | 
					        if (action === 'addComment') this.showForm();
 | 
				
			||||||
 | 
					        if (action === 'hideForm') this.hideForm();
 | 
				
			||||||
 | 
					        if (action === 'reply') this.setReply();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    closeUpdateForm() {
 | 
				
			||||||
 | 
					        if (!this.editingComment) return;
 | 
				
			||||||
 | 
					        this.editingComment.querySelector('[comment-content]').style.display = 'block';
 | 
				
			||||||
 | 
					        this.editingComment.querySelector('[comment-edit-container]').style.display = 'none';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    editComment(commentElem) {
 | 
				
			||||||
 | 
					        this.hideForm();
 | 
				
			||||||
 | 
					        if (this.editingComment) this.closeUpdateForm();
 | 
				
			||||||
 | 
					        commentElem.querySelector('[comment-content]').style.display = 'none';
 | 
				
			||||||
 | 
					        commentElem.querySelector('[comment-edit-container]').style.display = 'block';
 | 
				
			||||||
 | 
					        this.editingComment = commentElem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateComment(event) {
 | 
				
			||||||
 | 
					        let form = event.target;
 | 
				
			||||||
 | 
					        event.preventDefault();
 | 
				
			||||||
 | 
					        let text = form.querySelector('textarea').value;
 | 
				
			||||||
 | 
					        let reqData = {
 | 
				
			||||||
 | 
					            text: text,
 | 
				
			||||||
 | 
					            html: md.render(text),
 | 
				
			||||||
 | 
					            // parent_id: this.parent_id TODO - Handle replies
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        // TODO - Loading indicator
 | 
				
			||||||
 | 
					        let commentId = this.editingComment.getAttribute('comment');
 | 
				
			||||||
 | 
					        window.$http.put(window.baseUrl(`/ajax/comment/${commentId}`), reqData).then(resp => {
 | 
				
			||||||
 | 
					            let newComment = document.createElement('div');
 | 
				
			||||||
 | 
					            newComment.innerHTML = resp.data;
 | 
				
			||||||
 | 
					            this.editingComment.innerHTML = newComment.children[0].innerHTML;
 | 
				
			||||||
 | 
					            window.$events.emit('success', window.trans('entities.comment_updated_success'));
 | 
				
			||||||
 | 
					            this.closeUpdateForm();
 | 
				
			||||||
 | 
					            this.editingComment = null;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deleteComment(commentElem) {
 | 
				
			||||||
 | 
					        let id = commentElem.getAttribute('comment');
 | 
				
			||||||
 | 
					        // TODO - Loading indicator
 | 
				
			||||||
 | 
					        // TODO - Confirm dropdown
 | 
				
			||||||
 | 
					        window.$http.delete(window.baseUrl(`/ajax/comment/${id}`)).then(resp => {
 | 
				
			||||||
 | 
					            commentElem.parentNode.removeChild(commentElem);
 | 
				
			||||||
 | 
					            window.$events.emit('success', window.trans('entities.comment_deleted_success'));
 | 
				
			||||||
 | 
					            this.updateCount();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    saveComment(event) {
 | 
				
			||||||
 | 
					        event.preventDefault();
 | 
				
			||||||
 | 
					        event.stopPropagation();
 | 
				
			||||||
 | 
					        let text = this.formInput.value;
 | 
				
			||||||
 | 
					        let reqData = {
 | 
				
			||||||
 | 
					            text: text,
 | 
				
			||||||
 | 
					            html: md.render(text),
 | 
				
			||||||
 | 
					            // parent_id: this.parent_id TODO - Handle replies
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        // TODO - Loading indicator
 | 
				
			||||||
 | 
					        window.$http.post(window.baseUrl(`/ajax/page/${this.pageId}/comment`), reqData).then(resp => {
 | 
				
			||||||
 | 
					            let newComment = document.createElement('div');
 | 
				
			||||||
 | 
					            newComment.innerHTML = resp.data;
 | 
				
			||||||
 | 
					            this.container.appendChild(newComment.children[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            window.$events.emit('success', window.trans('entities.comment_created_success'));
 | 
				
			||||||
 | 
					            this.resetForm();
 | 
				
			||||||
 | 
					            this.updateCount();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateCount() {
 | 
				
			||||||
 | 
					        let count = this.container.children.length;
 | 
				
			||||||
 | 
					        this.elem.querySelector('[comments-title]').textContent = window.trans_choice('entities.comment_count', count, {count});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resetForm() {
 | 
				
			||||||
 | 
					        this.formInput.value = '';
 | 
				
			||||||
 | 
					        this.formContainer.appendChild(this.form);
 | 
				
			||||||
 | 
					        this.hideForm();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    showForm() {
 | 
				
			||||||
 | 
					        this.formContainer.style.display = 'block';
 | 
				
			||||||
 | 
					        this.formContainer.parentNode.style.display = 'block';
 | 
				
			||||||
 | 
					        this.elem.querySelector('[comment-add-button]').style.display = 'none';
 | 
				
			||||||
 | 
					        this.formInput.focus(); // TODO - Scroll to input on focus
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hideForm() {
 | 
				
			||||||
 | 
					        this.formContainer.style.display = 'none';
 | 
				
			||||||
 | 
					        this.formContainer.parentNode.style.display = 'none';
 | 
				
			||||||
 | 
					        this.elem.querySelector('[comment-add-button]').style.display = 'block';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setReply() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.showForm();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO - Go to comment if url param set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = PageComments;
 | 
				
			||||||
@@ -73,6 +73,7 @@ let ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize'
 | 
				
			|||||||
const Translations = require("./translations");
 | 
					const Translations = require("./translations");
 | 
				
			||||||
let translator = new Translations(window.translations);
 | 
					let translator = new Translations(window.translations);
 | 
				
			||||||
window.trans = translator.get.bind(translator);
 | 
					window.trans = translator.get.bind(translator);
 | 
				
			||||||
 | 
					window.trans_choice = translator.getPlural.bind(translator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require("./vues/vues");
 | 
					require("./vues/vues");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,9 +20,64 @@ class Translator {
 | 
				
			|||||||
     * @returns {*}
 | 
					     * @returns {*}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    get(key, replacements) {
 | 
					    get(key, replacements) {
 | 
				
			||||||
 | 
					        let text = this.getTransText(key);
 | 
				
			||||||
 | 
					        return this.performReplacements(text, replacements);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get pluralised text, Dependant on the given count.
 | 
				
			||||||
 | 
					     * Same format at laravel's 'trans_choice' helper.
 | 
				
			||||||
 | 
					     * @param key
 | 
				
			||||||
 | 
					     * @param count
 | 
				
			||||||
 | 
					     * @param replacements
 | 
				
			||||||
 | 
					     * @returns {*}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getPlural(key, count, replacements) {
 | 
				
			||||||
 | 
					        let text = this.getTransText(key);
 | 
				
			||||||
 | 
					        let splitText = text.split('|');
 | 
				
			||||||
 | 
					        let result = null;
 | 
				
			||||||
 | 
					        let exactCountRegex = /^{([0-9]+)}/;
 | 
				
			||||||
 | 
					        let rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0, len = splitText.length; i < len; i++) {
 | 
				
			||||||
 | 
					            let t = splitText[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Parse exact matches
 | 
				
			||||||
 | 
					            let exactMatches = t.match(exactCountRegex);
 | 
				
			||||||
 | 
					            console.log(exactMatches);
 | 
				
			||||||
 | 
					            if (exactMatches !== null && Number(exactMatches[1]) === count) {
 | 
				
			||||||
 | 
					                result = t.replace(exactCountRegex, '').trim();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Parse range matches
 | 
				
			||||||
 | 
					            let rangeMatches = t.match(rangeRegex);
 | 
				
			||||||
 | 
					            if (rangeMatches !== null) {
 | 
				
			||||||
 | 
					                let rangeStart = Number(rangeMatches[1]);
 | 
				
			||||||
 | 
					                if (rangeStart <= count && (rangeMatches[2] === '*' || Number(rangeMatches[2]) >= count)) {
 | 
				
			||||||
 | 
					                    result = t.replace(rangeRegex, '').trim();
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (result === null && splitText.length > 1) {
 | 
				
			||||||
 | 
					            result = (count === 1) ? splitText[0] : splitText[1];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (result === null) result = splitText[0];
 | 
				
			||||||
 | 
					        return this.performReplacements(result, replacements);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetched translation text from the store for the given key.
 | 
				
			||||||
 | 
					     * @param key
 | 
				
			||||||
 | 
					     * @returns {String|Object}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getTransText(key) {
 | 
				
			||||||
        let splitKey = key.split('.');
 | 
					        let splitKey = key.split('.');
 | 
				
			||||||
        let value = splitKey.reduce((a, b) => {
 | 
					        let value = splitKey.reduce((a, b) => {
 | 
				
			||||||
            return a != undefined ? a[b] : a;
 | 
					            return a !== undefined ? a[b] : a;
 | 
				
			||||||
        }, this.store);
 | 
					        }, this.store);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (value === undefined) {
 | 
					        if (value === undefined) {
 | 
				
			||||||
@@ -30,16 +85,25 @@ class Translator {
 | 
				
			|||||||
            value = key;
 | 
					            value = key;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (replacements === undefined) return value;
 | 
					        return value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let replaceMatches = value.match(/:([\S]+)/g);
 | 
					    /**
 | 
				
			||||||
        if (replaceMatches === null) return value;
 | 
					     * Perform replacements on a string.
 | 
				
			||||||
 | 
					     * @param {String} string
 | 
				
			||||||
 | 
					     * @param {Object} replacements
 | 
				
			||||||
 | 
					     * @returns {*}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    performReplacements(string, replacements) {
 | 
				
			||||||
 | 
					        if (!replacements) return string;
 | 
				
			||||||
 | 
					        let replaceMatches = string.match(/:([\S]+)/g);
 | 
				
			||||||
 | 
					        if (replaceMatches === null) return string;
 | 
				
			||||||
        replaceMatches.forEach(match => {
 | 
					        replaceMatches.forEach(match => {
 | 
				
			||||||
            let key = match.substring(1);
 | 
					            let key = match.substring(1);
 | 
				
			||||||
            if (typeof replacements[key] === 'undefined') return;
 | 
					            if (typeof replacements[key] === 'undefined') return;
 | 
				
			||||||
            value = value.replace(match, replacements[key]);
 | 
					            string = string.replace(match, replacements[key]);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        return value;
 | 
					        return string;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,113 +0,0 @@
 | 
				
			|||||||
const MarkdownIt = require("markdown-it");
 | 
					 | 
				
			||||||
const md = new MarkdownIt({ html: true });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var template = `
 | 
					 | 
				
			||||||
<div class="comment-editor" v-cloak>
 | 
					 | 
				
			||||||
<form novalidate>
 | 
					 | 
				
			||||||
    <textarea name="markdown" rows="3" v-model="comment.text" :placeholder="trans('entities.comment_placeholder')"></textarea>
 | 
					 | 
				
			||||||
    <input type="hidden" v-model="comment.pageId" name="comment.pageId" :value="pageId">
 | 
					 | 
				
			||||||
    <button type="button" v-if="isReply || isEdit" class="button muted" v-on:click="closeBox">{{ trans('entities.comment_cancel') }}</button>
 | 
					 | 
				
			||||||
    <button type="submit" class="button pos" v-on:click.prevent="saveComment">{{ trans('entities.comment_save') }}</button>
 | 
					 | 
				
			||||||
</form>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = {
 | 
					 | 
				
			||||||
    pageId: {},
 | 
					 | 
				
			||||||
    commentObj: {},
 | 
					 | 
				
			||||||
    isReply: {
 | 
					 | 
				
			||||||
        default: false,
 | 
					 | 
				
			||||||
        type: Boolean
 | 
					 | 
				
			||||||
    }, isEdit: {
 | 
					 | 
				
			||||||
        default: false,
 | 
					 | 
				
			||||||
        type: Boolean
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function data() {
 | 
					 | 
				
			||||||
    let comment = {
 | 
					 | 
				
			||||||
        text: ''
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (this.isReply) {
 | 
					 | 
				
			||||||
        comment.page_id = this.commentObj.page_id;
 | 
					 | 
				
			||||||
        comment.id = this.commentObj.id;
 | 
					 | 
				
			||||||
    } else if (this.isEdit) {
 | 
					 | 
				
			||||||
        comment = this.commentObj;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        comment: comment,
 | 
					 | 
				
			||||||
        trans: trans
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const methods = {
 | 
					 | 
				
			||||||
    saveComment: function (event) {
 | 
					 | 
				
			||||||
        let pageId = this.comment.page_id || this.pageId;
 | 
					 | 
				
			||||||
        let commentText = this.comment.text;
 | 
					 | 
				
			||||||
        if (!commentText) {
 | 
					 | 
				
			||||||
            return this.$events.emit('error', trans('errors.empty_comment'))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        let commentHTML = md.render(commentText);
 | 
					 | 
				
			||||||
        let serviceUrl = `/ajax/page/${pageId}/comment/`;
 | 
					 | 
				
			||||||
        let httpMethod = 'post';
 | 
					 | 
				
			||||||
        let reqObj = {
 | 
					 | 
				
			||||||
            text: commentText,
 | 
					 | 
				
			||||||
            html: commentHTML
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (this.isEdit === true) {
 | 
					 | 
				
			||||||
            // this will be set when editing the comment.
 | 
					 | 
				
			||||||
            serviceUrl = `/ajax/page/${pageId}/comment/${this.comment.id}`;
 | 
					 | 
				
			||||||
            httpMethod = 'put';
 | 
					 | 
				
			||||||
        } else if (this.isReply === true) {
 | 
					 | 
				
			||||||
            // if its reply, get the parent comment id
 | 
					 | 
				
			||||||
            reqObj.parent_id = this.comment.id;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        $http[httpMethod](window.baseUrl(serviceUrl), reqObj).then(resp => {
 | 
					 | 
				
			||||||
            if (!isCommentOpSuccess(resp)) {
 | 
					 | 
				
			||||||
                this.$events.emit('error', getErrorMsg(resp));
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            // hide the comments first, and then retrigger the refresh
 | 
					 | 
				
			||||||
            if (this.isEdit) {
 | 
					 | 
				
			||||||
                this.$emit('comment-edited', event, resp.data.comment);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                this.comment.text = '';
 | 
					 | 
				
			||||||
                this.$emit('comment-added', event);
 | 
					 | 
				
			||||||
                if (this.isReply === true) {
 | 
					 | 
				
			||||||
                    this.$emit('comment-replied', event, resp.data.comment);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    this.$parent.$emit('new-comment', event, resp.data.comment);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            this.$events.emit('success', resp.data.message);
 | 
					 | 
				
			||||||
        }).catch(err => {
 | 
					 | 
				
			||||||
            this.$events.emit('error', trans('errors.comment_add'))
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    closeBox: function (event) {
 | 
					 | 
				
			||||||
        this.$emit('editor-removed', event);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const computed = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function isCommentOpSuccess(resp) {
 | 
					 | 
				
			||||||
    if (resp && resp.data && resp.data.status === 'success') {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getErrorMsg(response) {
 | 
					 | 
				
			||||||
    if (response.data) {
 | 
					 | 
				
			||||||
        return response.data.message;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        return trans('errors.comment_add');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = { name: 'comment-reply', template, data, props, methods, computed };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -1,174 +0,0 @@
 | 
				
			|||||||
const commentReply = require('./comment-reply');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const template = `
 | 
					 | 
				
			||||||
<div class="comment-box">
 | 
					 | 
				
			||||||
  <div class='page-comment' :id="commentId">
 | 
					 | 
				
			||||||
  <div class="user-image">
 | 
					 | 
				
			||||||
      <img :src="comment.created_by.avatar_url" alt="user avatar">
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
  <div class="comment-container">
 | 
					 | 
				
			||||||
      <div class="comment-header">
 | 
					 | 
				
			||||||
          <a :href="comment.created_by.profile_url">{{comment.created_by.name}}</a>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div v-html="comment.html" v-if="comment.active" class="comment-body" v-bind:class="{ 'comment-inactive' : !comment.active }">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div v-if="!comment.active" class="comment-body comment-inactive">
 | 
					 | 
				
			||||||
          {{ trans('entities.comment_deleted') }}
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div class="comment-actions">
 | 
					 | 
				
			||||||
          <ul>
 | 
					 | 
				
			||||||
              <li v-if="(level < 4 && canComment)">
 | 
					 | 
				
			||||||
                <a href="#" comment="comment" v-on:click.prevent="replyComment">{{ trans('entities.comment_reply') }}</a>
 | 
					 | 
				
			||||||
              </li>
 | 
					 | 
				
			||||||
              <li v-if="canEditOrDelete('update')">
 | 
					 | 
				
			||||||
                <a href="#" comment="comment" v-on:click.prevent="editComment">{{ trans('entities.comment_edit') }}</a>
 | 
					 | 
				
			||||||
              </li>
 | 
					 | 
				
			||||||
              <li v-if="canEditOrDelete('delete')">
 | 
					 | 
				
			||||||
                <a href="#" comment="comment" v-on:click.prevent="deleteComment">{{ trans('entities.comment_delete') }}</a>
 | 
					 | 
				
			||||||
              </li>
 | 
					 | 
				
			||||||
              <li>{{ trans('entities.comment_create') }}
 | 
					 | 
				
			||||||
                <a :title="comment.created.day_time_str" :href="commentHref">{{comment.created.diff}}</a>
 | 
					 | 
				
			||||||
              </li>
 | 
					 | 
				
			||||||
              <li v-if="comment.updated">
 | 
					 | 
				
			||||||
                <span :title="comment.updated.day_time_str">{{trans('entities.comment_updated_text', { updateDiff: comment.updated.diff }) }}
 | 
					 | 
				
			||||||
                      <a :href="comment.updated_by.profile_url">{{comment.updated_by.name}}</a>
 | 
					 | 
				
			||||||
                </span>
 | 
					 | 
				
			||||||
              </li>
 | 
					 | 
				
			||||||
          </ul>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div v-if="showEditor">
 | 
					 | 
				
			||||||
        <comment-reply :page-id="comment.page_id" :comment-obj="comment"
 | 
					 | 
				
			||||||
          v-on:editor-removed.stop.prevent="hideComment"
 | 
					 | 
				
			||||||
          v-on:comment-replied.stop="commentReplied(...arguments)"
 | 
					 | 
				
			||||||
          v-on:comment-edited.stop="commentEdited(...arguments)"
 | 
					 | 
				
			||||||
          v-on:comment-added.stop="commentAdded"
 | 
					 | 
				
			||||||
           :is-reply="isReply" :is-edit="isEdit">
 | 
					 | 
				
			||||||
        </comment-reply>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <comment v-for="(comment, index) in comments" :initial-comment="comment" :index="index"
 | 
					 | 
				
			||||||
        :level="nextLevel" :key="comment.id" :permissions="permissions" :current-user-id="currentUserId"
 | 
					 | 
				
			||||||
        v-on:comment-added.stop="commentAdded"></comment>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = ['initialComment', 'index', 'level', 'permissions', 'currentUserId'];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function data() {
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        trans: trans,
 | 
					 | 
				
			||||||
        comments: [],
 | 
					 | 
				
			||||||
        showEditor: false,
 | 
					 | 
				
			||||||
        comment: this.initialComment,
 | 
					 | 
				
			||||||
        nextLevel: this.level + 1
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const methods = {
 | 
					 | 
				
			||||||
    deleteComment: function () {
 | 
					 | 
				
			||||||
        var resp = window.confirm(trans('entities.comment_delete_confirm'));
 | 
					 | 
				
			||||||
        if (!resp) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        this.$http.delete(window.baseUrl(`/ajax/comment/${this.comment.id}`)).then(resp => {
 | 
					 | 
				
			||||||
            if (!isCommentOpSuccess(resp)) {
 | 
					 | 
				
			||||||
                this.$events.emit('error', trans('error.comment_delete'));
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            this.$events.emit('success', trans('entities.comment_deleted'));
 | 
					 | 
				
			||||||
            this.comment = resp.data.comment;
 | 
					 | 
				
			||||||
        }).catch(err => {
 | 
					 | 
				
			||||||
            this.$events.emit('error', trans('error.comment_delete'));
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    replyComment: function () {
 | 
					 | 
				
			||||||
        this.toggleEditor(false);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    editComment: function () {
 | 
					 | 
				
			||||||
        this.toggleEditor(true);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    hideComment: function () {
 | 
					 | 
				
			||||||
        this.showEditor = false;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    toggleEditor: function (isEdit) {
 | 
					 | 
				
			||||||
        this.showEditor = false;
 | 
					 | 
				
			||||||
        this.isEdit = isEdit;
 | 
					 | 
				
			||||||
        this.isReply = !isEdit;
 | 
					 | 
				
			||||||
        this.showEditor = true;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    commentReplied: function (event, comment) {
 | 
					 | 
				
			||||||
        this.comments.push(comment);
 | 
					 | 
				
			||||||
        this.showEditor = false;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    commentEdited: function (event, comment) {
 | 
					 | 
				
			||||||
        this.comment = comment;
 | 
					 | 
				
			||||||
        this.showEditor = false;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    commentAdded: function (event, comment) {
 | 
					 | 
				
			||||||
        // this is to handle non-parent child relationship
 | 
					 | 
				
			||||||
        // we want to make it go up.
 | 
					 | 
				
			||||||
        this.$emit('comment-added', event);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    canEditOrDelete: function (prop) {
 | 
					 | 
				
			||||||
        if (!this.comment.active) {
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!this.permissions) {
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let propAll = 'comment_' + prop + '_all';
 | 
					 | 
				
			||||||
        let propOwn = 'comment_' + prop + '_own';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (this.permissions[propAll]) {
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (this.permissions[propOwn] && this.comment.created_by.id === this.currentUserId) {
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    canComment: function () {
 | 
					 | 
				
			||||||
        if (!this.permissions) {
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return this.permissions.comment_create === true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const computed = {
 | 
					 | 
				
			||||||
    commentId: function () {
 | 
					 | 
				
			||||||
        return `comment-${this.comment.page_id}-${this.comment.id}`;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    commentHref: function () {
 | 
					 | 
				
			||||||
        return `#?cm=${this.commentId}`;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function mounted() {
 | 
					 | 
				
			||||||
    if (this.comment.sub_comments && this.comment.sub_comments.length) {
 | 
					 | 
				
			||||||
        // set this so that we can render the next set of sub comments.
 | 
					 | 
				
			||||||
        this.comments = this.comment.sub_comments;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function isCommentOpSuccess(resp) {
 | 
					 | 
				
			||||||
    if (resp && resp.data && resp.data.status === 'success') {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
    name: 'comment',
 | 
					 | 
				
			||||||
    template, data, props, methods, computed, mounted, components: {
 | 
					 | 
				
			||||||
        commentReply
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -1,117 +0,0 @@
 | 
				
			|||||||
const comment = require('./components/comments/comment');
 | 
					 | 
				
			||||||
const commentReply = require('./components/comments/comment-reply');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let data = {
 | 
					 | 
				
			||||||
    totalCommentsStr: trans('entities.comments_loading'),
 | 
					 | 
				
			||||||
    comments: [],
 | 
					 | 
				
			||||||
    permissions: null,
 | 
					 | 
				
			||||||
    currentUserId: null,
 | 
					 | 
				
			||||||
    trans: trans,
 | 
					 | 
				
			||||||
    commentCount: 0
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let methods = {
 | 
					 | 
				
			||||||
    commentAdded: function () {
 | 
					 | 
				
			||||||
        ++this.totalComments;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let computed = {
 | 
					 | 
				
			||||||
    totalComments: {
 | 
					 | 
				
			||||||
        get: function () {
 | 
					 | 
				
			||||||
            return this.commentCount;
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        set: function (value) {
 | 
					 | 
				
			||||||
            this.commentCount = value;
 | 
					 | 
				
			||||||
            if (value === 0) {
 | 
					 | 
				
			||||||
                this.totalCommentsStr = trans('entities.no_comments');
 | 
					 | 
				
			||||||
            } else if (value === 1) {
 | 
					 | 
				
			||||||
                this.totalCommentsStr = trans('entities.one_comment');
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                this.totalCommentsStr = trans('entities.x_comments', {
 | 
					 | 
				
			||||||
                    numComments: value
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    canComment: function () {
 | 
					 | 
				
			||||||
        if (!this.permissions) {
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return this.permissions.comment_create === true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function mounted() {
 | 
					 | 
				
			||||||
    this.pageId = Number(this.$el.getAttribute('page-id'));
 | 
					 | 
				
			||||||
    let linkedCommentId = getUrlParameter('cm');
 | 
					 | 
				
			||||||
    this.$http.get(window.baseUrl(`/ajax/page/${this.pageId}/comments/`)).then(resp => {
 | 
					 | 
				
			||||||
        if (!isCommentOpSuccess(resp)) {
 | 
					 | 
				
			||||||
            // just show that no comments are available.
 | 
					 | 
				
			||||||
            vm.totalComments = 0;
 | 
					 | 
				
			||||||
            this.$events.emit('error', getErrorMsg(resp));
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        this.comments = resp.data.comments;
 | 
					 | 
				
			||||||
        this.totalComments = +resp.data.total;
 | 
					 | 
				
			||||||
        this.permissions = resp.data.permissions;
 | 
					 | 
				
			||||||
        this.currentUserId = resp.data.user_id;
 | 
					 | 
				
			||||||
        if (!linkedCommentId) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // adding a setTimeout to give the comment list some time to render
 | 
					 | 
				
			||||||
        // before focusing the comment.
 | 
					 | 
				
			||||||
        setTimeout(function() {
 | 
					 | 
				
			||||||
            focusLinkedComment(linkedCommentId);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }).catch(err => {
 | 
					 | 
				
			||||||
        this.$events.emit('error', trans('errors.comment_list'));
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function isCommentOpSuccess(resp) {
 | 
					 | 
				
			||||||
    if (resp && resp.data && resp.data.status === 'success') {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getErrorMsg(response) {
 | 
					 | 
				
			||||||
    if (response.data) {
 | 
					 | 
				
			||||||
        return response.data.message;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        return trans('errors.comment_add');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function created() {
 | 
					 | 
				
			||||||
    this.$on('new-comment', function (event, comment) {
 | 
					 | 
				
			||||||
        this.comments.push(comment);
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function beforeDestroy() {
 | 
					 | 
				
			||||||
    this.$off('new-comment');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getUrlParameter(name) {
 | 
					 | 
				
			||||||
    name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
 | 
					 | 
				
			||||||
    var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
 | 
					 | 
				
			||||||
    var results = regex.exec(location.hash);
 | 
					 | 
				
			||||||
    return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function focusLinkedComment(linkedCommentId) {
 | 
					 | 
				
			||||||
    let comment = document.getElementById(linkedCommentId);
 | 
					 | 
				
			||||||
    if (comment && comment.length !== 0) {
 | 
					 | 
				
			||||||
        window.setupPageShow.goToText(linkedCommentId);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
    data, methods, mounted, computed, components: {
 | 
					 | 
				
			||||||
        comment, commentReply
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    created, beforeDestroy
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -11,7 +11,6 @@ let vueMapping = {
 | 
				
			|||||||
    'image-manager': require('./image-manager'),
 | 
					    'image-manager': require('./image-manager'),
 | 
				
			||||||
    'tag-manager': require('./tag-manager'),
 | 
					    'tag-manager': require('./tag-manager'),
 | 
				
			||||||
    'attachment-manager': require('./attachment-manager'),
 | 
					    'attachment-manager': require('./attachment-manager'),
 | 
				
			||||||
    'page-comments': require('./page-comments')
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.vues = {};
 | 
					window.vues = {};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,82 +1,33 @@
 | 
				
			|||||||
.comments-list {
 | 
					.comment-box {
 | 
				
			||||||
    .comment-box {
 | 
					    border: 1px solid #DDD;
 | 
				
			||||||
        border-bottom: 1px solid $comment-border;
 | 
					    margin-bottom: $-s;
 | 
				
			||||||
 | 
					    border-radius: 3px;
 | 
				
			||||||
 | 
					    .content {
 | 
				
			||||||
 | 
					        padding: $-s;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    .content p {
 | 
				
			||||||
    .comment-box:last-child {
 | 
					 | 
				
			||||||
        border-bottom: 0px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.page-comment {
 | 
					 | 
				
			||||||
    .comment-container {
 | 
					 | 
				
			||||||
        margin-left: 42px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .comment-actions {
 | 
					 | 
				
			||||||
        font-size: 0.8em;
 | 
					 | 
				
			||||||
        padding-bottom: 2px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ul {
 | 
					 | 
				
			||||||
            padding-left: 0px;
 | 
					 | 
				
			||||||
            margin-bottom: 2px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        li {
 | 
					 | 
				
			||||||
            float: left;
 | 
					 | 
				
			||||||
            list-style-type: none;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        li:after {
 | 
					 | 
				
			||||||
            content: '•';
 | 
					 | 
				
			||||||
            color: #707070;
 | 
					 | 
				
			||||||
            padding: 0 5px;
 | 
					 | 
				
			||||||
            font-size: 1em;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        li:last-child:after {
 | 
					 | 
				
			||||||
            content: none;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .comment-actions {
 | 
					 | 
				
			||||||
        border-bottom: 1px solid #DDD;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .comment-actions:last-child {
 | 
					 | 
				
			||||||
        border-bottom: 0px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .comment-header {
 | 
					 | 
				
			||||||
        font-size: 1.25em;
 | 
					 | 
				
			||||||
        margin-top: 0.6em;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .comment-body p {
 | 
					 | 
				
			||||||
        margin-bottom: 1em;
 | 
					        margin-bottom: 1em;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    .comment-inactive {
 | 
					 | 
				
			||||||
        font-style: italic;
 | 
					 | 
				
			||||||
        font-size: 0.85em;
 | 
					 | 
				
			||||||
        padding-top: 5px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .user-image {
 | 
					 | 
				
			||||||
        float: left;
 | 
					 | 
				
			||||||
        margin-right: 10px;
 | 
					 | 
				
			||||||
        width: 32px;
 | 
					 | 
				
			||||||
        img {
 | 
					 | 
				
			||||||
            width: 100%;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.comment-editor {
 | 
					.comment-box .header {
 | 
				
			||||||
    margin-top: 2em;
 | 
					    padding: $-xs $-s;
 | 
				
			||||||
 | 
					    background-color: #f8f8f8;
 | 
				
			||||||
    textarea {
 | 
					    border-bottom: 1px solid #DDD;
 | 
				
			||||||
        display: block;
 | 
					    img, a, span {
 | 
				
			||||||
        width: 100%;
 | 
					        display: inline-block;
 | 
				
			||||||
        max-width: 100%;
 | 
					        vertical-align: top;
 | 
				
			||||||
        min-height: 120px;
 | 
					    }
 | 
				
			||||||
 | 
					    a, span {
 | 
				
			||||||
 | 
					        padding: $-xxs 0 $-xxs 0;
 | 
				
			||||||
 | 
					        line-height: 1.6;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    a { color: #666; }
 | 
				
			||||||
 | 
					    span {
 | 
				
			||||||
 | 
					        color: #888;
 | 
				
			||||||
 | 
					        padding-left: $-xxs;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .text-muted {
 | 
				
			||||||
 | 
					        color: #999;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,7 +59,4 @@ $text-light: #EEE;
 | 
				
			|||||||
// Shadows
 | 
					// Shadows
 | 
				
			||||||
$bs-light: 0 0 4px 1px #CCC;
 | 
					$bs-light: 0 0 4px 1px #CCC;
 | 
				
			||||||
$bs-med: 0 1px 3px 1px rgba(76, 76, 76, 0.26);
 | 
					$bs-med: 0 1px 3px 1px rgba(76, 76, 76, 0.26);
 | 
				
			||||||
$bs-hover: 0 2px 2px 1px rgba(0,0,0,.13);
 | 
					$bs-hover: 0 2px 2px 1px rgba(0,0,0,.13);
 | 
				
			||||||
 | 
					 | 
				
			||||||
// comments
 | 
					 | 
				
			||||||
$comment-border: #DDD;
 | 
					 | 
				
			||||||
@@ -29,6 +29,7 @@ return [
 | 
				
			|||||||
    'edit' => 'Edit',
 | 
					    'edit' => 'Edit',
 | 
				
			||||||
    'sort' => 'Sort',
 | 
					    'sort' => 'Sort',
 | 
				
			||||||
    'move' => 'Move',
 | 
					    'move' => 'Move',
 | 
				
			||||||
 | 
					    'reply' => 'Reply',
 | 
				
			||||||
    'delete' => 'Delete',
 | 
					    'delete' => 'Delete',
 | 
				
			||||||
    'search' => 'Search',
 | 
					    'search' => 'Search',
 | 
				
			||||||
    'search_clear' => 'Clear Search',
 | 
					    'search_clear' => 'Clear Search',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -242,20 +242,15 @@ return [
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    'comment' => 'Comment',
 | 
					    'comment' => 'Comment',
 | 
				
			||||||
    'comments' => 'Comments',
 | 
					    'comments' => 'Comments',
 | 
				
			||||||
    'comment_placeholder' => 'Enter your comments here, markdown supported...',
 | 
					    'comment_placeholder' => 'Leave a comment here',
 | 
				
			||||||
    'no_comments' => 'No Comments',
 | 
					    'comment_count' => '{0} No Comments|{1} 1 Comment|[2,*] :count Comments',
 | 
				
			||||||
    'x_comments' => ':numComments Comments',
 | 
					 | 
				
			||||||
    'one_comment' => '1 Comment',
 | 
					 | 
				
			||||||
    'comments_loading' => 'Loading...',
 | 
					 | 
				
			||||||
    'comment_save' => 'Save Comment',
 | 
					    'comment_save' => 'Save Comment',
 | 
				
			||||||
    'comment_reply' => 'Reply',
 | 
					    'comment_new' => 'New Comment',
 | 
				
			||||||
    'comment_edit' => 'Edit',
 | 
					    'comment_created' => 'commented :createDiff',
 | 
				
			||||||
    'comment_delete' => 'Delete',
 | 
					    'comment_updated' => 'Updated :updateDiff by :username',
 | 
				
			||||||
    'comment_cancel' => 'Cancel',
 | 
					    'comment_deleted_success' => 'Comment deleted',
 | 
				
			||||||
    'comment_created' => 'Comment added',
 | 
					    'comment_created_success' => 'Comment added',
 | 
				
			||||||
    'comment_updated' => 'Comment updated',
 | 
					    'comment_updated_success' => 'Comment updated',
 | 
				
			||||||
    'comment_deleted' => 'Comment deleted',
 | 
					 | 
				
			||||||
    'comment_updated_text' => 'Updated :updateDiff by',
 | 
					 | 
				
			||||||
    'comment_delete_confirm' => 'This will remove the contents of the comment. Are you sure you want to delete this comment?',
 | 
					    'comment_delete_confirm' => 'This will remove the contents of the comment. Are you sure you want to delete this comment?',
 | 
				
			||||||
    'comment_create' => 'Created'
 | 
					    'comment_create' => 'Created'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										50
									
								
								resources/views/comments/comment.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								resources/views/comments/comment.blade.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					<div class="comment-box" comment="{{ $comment->id }}" id="comment{{$comment->local_id}}">
 | 
				
			||||||
 | 
					    <div class="header">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="float right actions">
 | 
				
			||||||
 | 
					            @if(userCan('comment-update', $comment))
 | 
				
			||||||
 | 
					                <button type="button" class="text-button" action="edit" title="{{ trans('common.edit') }}"><i class="zmdi zmdi-edit"></i></button>
 | 
				
			||||||
 | 
					            @endif
 | 
				
			||||||
 | 
					            @if(userCan('comment-create-all'))
 | 
				
			||||||
 | 
					                <button type="button" class="text-button" action="reply" title="{{ trans('common.reply') }}"><i class="zmdi zmdi-mail-reply-all"></i></button>
 | 
				
			||||||
 | 
					            @endif
 | 
				
			||||||
 | 
					            @if(userCan('comment-delete', $comment))
 | 
				
			||||||
 | 
					                <button type="button" class="text-button" action="delete" title="{{ trans('common.delete') }}"><i class="zmdi zmdi-delete"></i></button>
 | 
				
			||||||
 | 
					            @endif
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <a href="#comment{{$comment->local_id}}" class="text-muted">#{{$comment->local_id}}</a>
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					        <img width="50" src="{{ $comment->createdBy->getAvatar(50) }}" class="avatar" alt="{{ $comment->createdBy->name }}">
 | 
				
			||||||
 | 
					         
 | 
				
			||||||
 | 
					        <a href="{{ $comment->createdBy->getProfileUrl() }}">{{ $comment->createdBy->name }}</a>
 | 
				
			||||||
 | 
					        {{--TODO - Account for deleted user--}}
 | 
				
			||||||
 | 
					        <span title="{{ $comment->created_at }}">
 | 
				
			||||||
 | 
					            {{ trans('entities.comment_created', ['createDiff' => $comment->created]) }}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					        @if($comment->isUpdated())
 | 
				
			||||||
 | 
					            <span title="{{ $comment->updated_at }}">
 | 
				
			||||||
 | 
					                • 
 | 
				
			||||||
 | 
					               {{ trans('entities.comment_updated', ['updateDiff' => $comment->updated, 'username' => $comment->updatedBy->name]) }}
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					        @endif
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div comment-content class="content">
 | 
				
			||||||
 | 
					        {!! $comment->html  !!}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @if(userCan('comment-update', $comment))
 | 
				
			||||||
 | 
					        <div comment-edit-container style="display: none;" class="content">
 | 
				
			||||||
 | 
					            <form novalidate>
 | 
				
			||||||
 | 
					                <div class="form-group">
 | 
				
			||||||
 | 
					                    <textarea name="markdown" rows="3" v-model="comment.text" placeholder="{{ trans('entities.comment_placeholder') }}">{{ $comment->text }}</textarea>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="form-group text-right">
 | 
				
			||||||
 | 
					                    <button type="button" class="button outline" action="closeUpdateForm">{{ trans('common.cancel') }}</button>
 | 
				
			||||||
 | 
					                    <button type="submit" class="button pos">{{ trans('entities.comment_save') }}</button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    @endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@@ -1,11 +1,33 @@
 | 
				
			|||||||
<div id="page-comments" page-id="<?= $page->id ?>" class="comments-list" v-cloak>
 | 
					<div page-comments page-id="{{ $page->id }}" ng-non-bindable class="comments-list">
 | 
				
			||||||
  <h3>@{{totalCommentsStr}}</h3>
 | 
					  <h3 comments-title>{{ trans_choice('entities.comment_count', count($page->comments), ['count' => count($page->comments)]) }}</h3>
 | 
				
			||||||
  <hr>
 | 
					
 | 
				
			||||||
  <comment v-for="(comment, index) in comments" :initial-comment="comment" :index="index" :level=1
 | 
					    <div class="comment-container" comment-container>
 | 
				
			||||||
     v-on:comment-added.stop="commentAdded"
 | 
					        @foreach($page->comments as $comment)
 | 
				
			||||||
     :current-user-id="currentUserId" :key="comment.id" :permissions="permissions"></comment>
 | 
					            @include('comments.comment', ['comment' => $comment])
 | 
				
			||||||
  <div v-if="canComment">
 | 
					        @endforeach
 | 
				
			||||||
     <comment-reply v-on:comment-added.stop="commentAdded" :page-id="<?= $page->id ?>">
 | 
					    </div>
 | 
				
			||||||
     </comment-reply>
 | 
					
 | 
				
			||||||
  </div>
 | 
					
 | 
				
			||||||
 | 
					    @if(userCan('comment-create-all'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="comment-box" comment-box style="display:none;">
 | 
				
			||||||
 | 
					            <div class="header"><i class="zmdi zmdi-comment"></i> {{ trans('entities.comment_new') }}</div>
 | 
				
			||||||
 | 
					            <div class="content" comment-form-container>
 | 
				
			||||||
 | 
					                <form novalidate>
 | 
				
			||||||
 | 
					                    <div class="form-group">
 | 
				
			||||||
 | 
					                        <textarea name="markdown" rows="3" v-model="comment.text" placeholder="{{ trans('entities.comment_placeholder') }}"></textarea>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="form-group text-right">
 | 
				
			||||||
 | 
					                        <button type="button" class="button outline" action="hideForm">{{ trans('common.cancel') }}</button>
 | 
				
			||||||
 | 
					                        <button type="submit" class="button pos">{{ trans('entities.comment_save') }}</button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="form-group" comment-add-button>
 | 
				
			||||||
 | 
					            <button type="button" action="addComment" class="button outline">Add Comment</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    @endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
@@ -147,8 +147,9 @@
 | 
				
			|||||||
        @include('pages/page-display')
 | 
					        @include('pages/page-display')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="container small">
 | 
					    <div class="container small">
 | 
				
			||||||
        @include('comments/comments', ['pageId' => $page->id])
 | 
					        @include('comments/comments', ['page' => $page])
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@stop
 | 
					@stop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -120,10 +120,9 @@ Route::group(['middleware' => 'auth'], function () {
 | 
				
			|||||||
    Route::get('/ajax/search/entities', 'SearchController@searchEntitiesAjax');
 | 
					    Route::get('/ajax/search/entities', 'SearchController@searchEntitiesAjax');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Comments
 | 
					    // Comments
 | 
				
			||||||
    Route::post('/ajax/page/{pageId}/comment/', 'CommentController@save');
 | 
					    Route::post('/ajax/page/{pageId}/comment', 'CommentController@savePageComment');
 | 
				
			||||||
    Route::put('/ajax/page/{pageId}/comment/{commentId}', 'CommentController@save');
 | 
					    Route::put('/ajax/comment/{id}', 'CommentController@update');
 | 
				
			||||||
    Route::delete('/ajax/comment/{id}', 'CommentController@destroy');
 | 
					    Route::delete('/ajax/comment/{id}', 'CommentController@destroy');
 | 
				
			||||||
    Route::get('/ajax/page/{pageId}/comments/', 'CommentController@getPageComments');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Links
 | 
					    // Links
 | 
				
			||||||
    Route::get('/link/{id}', 'PageController@redirectFromLink');
 | 
					    Route::get('/link/{id}', 'PageController@redirectFromLink');
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user