Comments: Updated to show as nested threads

Initial functional implementation, a lot of tweaking and adapting to be
done.
This commit is contained in:
Dan Brown 2023-06-07 13:24:49 +01:00
parent 88785aa71b
commit 4b9f6beb37
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
8 changed files with 143 additions and 35 deletions

View File

@ -7,27 +7,14 @@ use BookStack\Entities\Models\Entity;
use BookStack\Facades\Activity as ActivityService;
use League\CommonMark\CommonMarkConverter;
/**
* Class CommentRepo.
*/
class CommentRepo
{
/**
* @var Comment
*/
protected $comment;
public function __construct(Comment $comment)
{
$this->comment = $comment;
}
/**
* Get a comment by ID.
*/
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
{
$userId = user()->id;
$comment = $this->comment->newInstance();
$comment = new Comment();
$comment->text = $text;
$comment->html = $this->commentToHtml($text);
@ -83,7 +70,7 @@ class CommentRepo
'allow_unsafe_links' => false,
]);
return $converter->convertToHtml($commentText);
return $converter->convert($commentText);
}
/**
@ -91,9 +78,8 @@ class CommentRepo
*/
protected function getNextLocalId(Entity $entity): int
{
/** @var Comment $comment */
$comment = $entity->comments(false)->orderBy('local_id', 'desc')->first();
$currentMaxId = $entity->comments()->max('local_id');
return ($comment->local_id ?? 0) + 1;
return $currentMaxId + 1;
}
}

View 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();
}
}

View File

@ -3,6 +3,7 @@
namespace BookStack\Entities\Controllers;
use BookStack\Activity\Models\View;
use BookStack\Activity\Tools\CommentTree;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Repos\PageRepo;
use BookStack\Entities\Tools\BookContents;
@ -140,15 +141,10 @@ class PageController extends Controller
$pageContent = (new PageContent($page));
$page->html = $pageContent->render();
$sidebarTree = (new BookContents($page->book))->getTree();
$pageNav = $pageContent->getNavigation($page->html);
// Check if page comments are enabled
$commentsEnabled = !setting('app-disable-comments');
if ($commentsEnabled) {
$page->load(['comments.createdBy']);
}
$sidebarTree = (new BookContents($page->book))->getTree();
$commentTree = (new CommentTree($page));
$nextPreviousLocator = new NextPreviousContentLocator($page, $sidebarTree);
View::incrementFor($page);
@ -159,7 +155,7 @@ class PageController extends Controller
'book' => $page->book,
'current' => $page,
'sidebarTree' => $sidebarTree,
'commentsEnabled' => $commentsEnabled,
'commentTree' => $commentTree,
'pageNav' => $pageNav,
'next' => $nextPreviousLocator->getNext(),
'previous' => $nextPreviousLocator->getPrevious(),

View File

@ -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 {
max-width: 500px;
}

View 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>

View File

@ -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="grid half left-focus no-gap v-center">
<div class="meta text-muted text-small">

View File

@ -8,8 +8,8 @@
aria-label="{{ trans('entities.comments') }}">
<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>
@if (count($page->comments) === 0 && userCan('comment-create-all'))
<h5 comments-title>{{ trans_choice('entities.comment_count', $commentTree->count(), ['count' => $commentTree->count()]) }}</h5>
@if ($commentTree->empty() && userCan('comment-create-all'))
<div class="text-m-right" refs="page-comments@addButtonContainer">
<button type="button" action="addComment"
class="button outline">{{ trans('entities.comment_add') }}</button>
@ -18,15 +18,15 @@
</div>
<div refs="page-comments@commentContainer" class="comment-container">
@foreach($page->comments as $comment)
@include('comments.comment', ['comment' => $comment])
@foreach($commentTree->get() as $branch)
@include('comments.comment-branch', ['branch' => $branch])
@endforeach
</div>
@if(userCan('comment-create-all'))
@include('comments.create')
@if (count($page->comments) > 0)
@if (!$commentTree->empty())
<div refs="page-comments@addButtonContainer" class="text-right">
<button type="button" action="addComment"
class="button outline">{{ trans('entities.comment_add') }}</button>

View File

@ -27,7 +27,7 @@
@include('entities.sibling-navigation', ['next' => $next, 'previous' => $previous])
@if ($commentsEnabled)
@if ($commentTree->enabled())
@if(($previous || $next))
<div class="px-xl">
<hr class="darker">
@ -35,7 +35,7 @@
@endif
<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>
@endif