mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-04-12 07:58:07 +00:00
Comments: Updated to show as nested threads
Initial functional implementation, a lot of tweaking and adapting to be done.
This commit is contained in:
parent
88785aa71b
commit
4b9f6beb37
8 changed files with 143 additions and 35 deletions
app
resources
|
@ -7,27 +7,14 @@ use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Facades\Activity as ActivityService;
|
use BookStack\Facades\Activity as ActivityService;
|
||||||
use League\CommonMark\CommonMarkConverter;
|
use League\CommonMark\CommonMarkConverter;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class CommentRepo.
|
|
||||||
*/
|
|
||||||
class CommentRepo
|
class CommentRepo
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var Comment
|
|
||||||
*/
|
|
||||||
protected $comment;
|
|
||||||
|
|
||||||
public function __construct(Comment $comment)
|
|
||||||
{
|
|
||||||
$this->comment = $comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a comment by ID.
|
* Get a comment by ID.
|
||||||
*/
|
*/
|
||||||
public function getById(int $id): Comment
|
public function getById(int $id): Comment
|
||||||
{
|
{
|
||||||
return $this->comment->newQuery()->findOrFail($id);
|
return Comment::query()->findOrFail($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +23,7 @@ class CommentRepo
|
||||||
public function create(Entity $entity, string $text, ?int $parent_id): Comment
|
public function create(Entity $entity, string $text, ?int $parent_id): Comment
|
||||||
{
|
{
|
||||||
$userId = user()->id;
|
$userId = user()->id;
|
||||||
$comment = $this->comment->newInstance();
|
$comment = new Comment();
|
||||||
|
|
||||||
$comment->text = $text;
|
$comment->text = $text;
|
||||||
$comment->html = $this->commentToHtml($text);
|
$comment->html = $this->commentToHtml($text);
|
||||||
|
@ -83,7 +70,7 @@ class CommentRepo
|
||||||
'allow_unsafe_links' => false,
|
'allow_unsafe_links' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $converter->convertToHtml($commentText);
|
return $converter->convert($commentText);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,9 +78,8 @@ class CommentRepo
|
||||||
*/
|
*/
|
||||||
protected function getNextLocalId(Entity $entity): int
|
protected function getNextLocalId(Entity $entity): int
|
||||||
{
|
{
|
||||||
/** @var Comment $comment */
|
$currentMaxId = $entity->comments()->max('local_id');
|
||||||
$comment = $entity->comments(false)->orderBy('local_id', 'desc')->first();
|
|
||||||
|
|
||||||
return ($comment->local_id ?? 0) + 1;
|
return $currentMaxId + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
102
app/Activity/Tools/CommentTree.php
Normal file
102
app/Activity/Tools/CommentTree.php
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Activity\Tools;
|
||||||
|
|
||||||
|
use BookStack\Activity\Models\Comment;
|
||||||
|
use BookStack\Entities\Models\Page;
|
||||||
|
|
||||||
|
class CommentTree
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The built nested tree structure array.
|
||||||
|
* @var array{comment: Comment, depth: int, children: array}[]
|
||||||
|
*/
|
||||||
|
protected array $tree;
|
||||||
|
protected array $comments;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected Page $page
|
||||||
|
) {
|
||||||
|
$this->comments = $this->loadComments();
|
||||||
|
$this->tree = $this->createTree($this->comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enabled(): bool
|
||||||
|
{
|
||||||
|
return !setting('app-disable-comments');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function empty(): bool
|
||||||
|
{
|
||||||
|
return count($this->tree) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return count($this->comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(): array
|
||||||
|
{
|
||||||
|
return $this->tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Comment[] $comments
|
||||||
|
*/
|
||||||
|
protected function createTree(array $comments): array
|
||||||
|
{
|
||||||
|
$byId = [];
|
||||||
|
foreach ($comments as $comment) {
|
||||||
|
$byId[$comment->local_id] = $comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
$childMap = [];
|
||||||
|
foreach ($comments as $comment) {
|
||||||
|
$parent = $comment->parent_id;
|
||||||
|
if (is_null($parent) || !isset($byId[$parent])) {
|
||||||
|
$parent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($childMap[$parent])) {
|
||||||
|
$childMap[$parent] = [];
|
||||||
|
}
|
||||||
|
$childMap[$parent][] = $comment->local_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tree = [];
|
||||||
|
foreach ($childMap[0] as $childId) {
|
||||||
|
$tree[] = $this->createTreeForId($childId, 0, $byId, $childMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createTreeForId(int $id, int $depth, array &$byId, array &$childMap): array
|
||||||
|
{
|
||||||
|
$childIds = $childMap[$id] ?? [];
|
||||||
|
$children = [];
|
||||||
|
|
||||||
|
foreach ($childIds as $childId) {
|
||||||
|
$children[] = $this->createTreeForId($childId, $depth + 1, $byId, $childMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'comment' => $byId[$id],
|
||||||
|
'depth' => $depth,
|
||||||
|
'children' => $children,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadComments(): array
|
||||||
|
{
|
||||||
|
if (!$this->enabled()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->page->comments()
|
||||||
|
->with('createdBy')
|
||||||
|
->get()
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
namespace BookStack\Entities\Controllers;
|
namespace BookStack\Entities\Controllers;
|
||||||
|
|
||||||
use BookStack\Activity\Models\View;
|
use BookStack\Activity\Models\View;
|
||||||
|
use BookStack\Activity\Tools\CommentTree;
|
||||||
use BookStack\Entities\Models\Page;
|
use BookStack\Entities\Models\Page;
|
||||||
use BookStack\Entities\Repos\PageRepo;
|
use BookStack\Entities\Repos\PageRepo;
|
||||||
use BookStack\Entities\Tools\BookContents;
|
use BookStack\Entities\Tools\BookContents;
|
||||||
|
@ -140,15 +141,10 @@ class PageController extends Controller
|
||||||
|
|
||||||
$pageContent = (new PageContent($page));
|
$pageContent = (new PageContent($page));
|
||||||
$page->html = $pageContent->render();
|
$page->html = $pageContent->render();
|
||||||
$sidebarTree = (new BookContents($page->book))->getTree();
|
|
||||||
$pageNav = $pageContent->getNavigation($page->html);
|
$pageNav = $pageContent->getNavigation($page->html);
|
||||||
|
|
||||||
// Check if page comments are enabled
|
$sidebarTree = (new BookContents($page->book))->getTree();
|
||||||
$commentsEnabled = !setting('app-disable-comments');
|
$commentTree = (new CommentTree($page));
|
||||||
if ($commentsEnabled) {
|
|
||||||
$page->load(['comments.createdBy']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nextPreviousLocator = new NextPreviousContentLocator($page, $sidebarTree);
|
$nextPreviousLocator = new NextPreviousContentLocator($page, $sidebarTree);
|
||||||
|
|
||||||
View::incrementFor($page);
|
View::incrementFor($page);
|
||||||
|
@ -159,7 +155,7 @@ class PageController extends Controller
|
||||||
'book' => $page->book,
|
'book' => $page->book,
|
||||||
'current' => $page,
|
'current' => $page,
|
||||||
'sidebarTree' => $sidebarTree,
|
'sidebarTree' => $sidebarTree,
|
||||||
'commentsEnabled' => $commentsEnabled,
|
'commentTree' => $commentTree,
|
||||||
'pageNav' => $pageNav,
|
'pageNav' => $pageNav,
|
||||||
'next' => $nextPreviousLocator->getNext(),
|
'next' => $nextPreviousLocator->getNext(),
|
||||||
'previous' => $nextPreviousLocator->getPrevious(),
|
'previous' => $nextPreviousLocator->getPrevious(),
|
||||||
|
|
|
@ -704,6 +704,13 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment-thread-indicator {
|
||||||
|
border-inline-start: 3px dotted #DDD;
|
||||||
|
@include lightDark(border-color, #DDD, #444);
|
||||||
|
margin-inline-start: $-xs;
|
||||||
|
width: $-l;
|
||||||
|
}
|
||||||
|
|
||||||
#tag-manager .drag-card {
|
#tag-manager .drag-card {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
17
resources/views/comments/comment-branch.blade.php
Normal file
17
resources/views/comments/comment-branch.blade.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<div>
|
||||||
|
<div class="mb-m">
|
||||||
|
@include('comments.comment', ['comment' => $branch['comment']])
|
||||||
|
</div>
|
||||||
|
@if(count($branch['children']) > 0)
|
||||||
|
<div class="flex-container-row">
|
||||||
|
<div class="pb-m">
|
||||||
|
<div class="comment-thread-indicator fill-height"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
@foreach($branch['children'] as $childBranch)
|
||||||
|
@include('comments.comment-branch', ['branch' => $childBranch])
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="comment-box mb-m" comment="{{ $comment->id }}" local-id="{{$comment->local_id}}" parent-id="{{$comment->parent_id}}" id="comment{{$comment->local_id}}">
|
<div class="comment-box" comment="{{ $comment->id }}" local-id="{{$comment->local_id}}" parent-id="{{$comment->parent_id}}" id="comment{{$comment->local_id}}">
|
||||||
<div class="header p-s">
|
<div class="header p-s">
|
||||||
<div class="grid half left-focus no-gap v-center">
|
<div class="grid half left-focus no-gap v-center">
|
||||||
<div class="meta text-muted text-small">
|
<div class="meta text-muted text-small">
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
aria-label="{{ trans('entities.comments') }}">
|
aria-label="{{ trans('entities.comments') }}">
|
||||||
|
|
||||||
<div refs="page-comments@commentCountBar" class="grid half left-focus v-center no-row-gap">
|
<div refs="page-comments@commentCountBar" class="grid half left-focus v-center no-row-gap">
|
||||||
<h5 comments-title>{{ trans_choice('entities.comment_count', count($page->comments), ['count' => count($page->comments)]) }}</h5>
|
<h5 comments-title>{{ trans_choice('entities.comment_count', $commentTree->count(), ['count' => $commentTree->count()]) }}</h5>
|
||||||
@if (count($page->comments) === 0 && userCan('comment-create-all'))
|
@if ($commentTree->empty() && userCan('comment-create-all'))
|
||||||
<div class="text-m-right" refs="page-comments@addButtonContainer">
|
<div class="text-m-right" refs="page-comments@addButtonContainer">
|
||||||
<button type="button" action="addComment"
|
<button type="button" action="addComment"
|
||||||
class="button outline">{{ trans('entities.comment_add') }}</button>
|
class="button outline">{{ trans('entities.comment_add') }}</button>
|
||||||
|
@ -18,15 +18,15 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div refs="page-comments@commentContainer" class="comment-container">
|
<div refs="page-comments@commentContainer" class="comment-container">
|
||||||
@foreach($page->comments as $comment)
|
@foreach($commentTree->get() as $branch)
|
||||||
@include('comments.comment', ['comment' => $comment])
|
@include('comments.comment-branch', ['branch' => $branch])
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(userCan('comment-create-all'))
|
@if(userCan('comment-create-all'))
|
||||||
@include('comments.create')
|
@include('comments.create')
|
||||||
|
|
||||||
@if (count($page->comments) > 0)
|
@if (!$commentTree->empty())
|
||||||
<div refs="page-comments@addButtonContainer" class="text-right">
|
<div refs="page-comments@addButtonContainer" class="text-right">
|
||||||
<button type="button" action="addComment"
|
<button type="button" action="addComment"
|
||||||
class="button outline">{{ trans('entities.comment_add') }}</button>
|
class="button outline">{{ trans('entities.comment_add') }}</button>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
@include('entities.sibling-navigation', ['next' => $next, 'previous' => $previous])
|
@include('entities.sibling-navigation', ['next' => $next, 'previous' => $previous])
|
||||||
|
|
||||||
@if ($commentsEnabled)
|
@if ($commentTree->enabled())
|
||||||
@if(($previous || $next))
|
@if(($previous || $next))
|
||||||
<div class="px-xl">
|
<div class="px-xl">
|
||||||
<hr class="darker">
|
<hr class="darker">
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="px-xl comments-container mb-l print-hidden">
|
<div class="px-xl comments-container mb-l print-hidden">
|
||||||
@include('comments.comments', ['page' => $page])
|
@include('comments.comments', ['commentTree' => $commentTree, 'page' => $page])
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
Loading…
Add table
Reference in a new issue