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);
}
/**
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);
'allow_unsafe_links' => false,
]);
- return $converter->convertToHtml($commentText);
+ return $converter->convert($commentText);
}
/**
*/
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;
}
}
--- /dev/null
+<?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();
+ }
+}
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;
$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);
'book' => $page->book,
'current' => $page,
'sidebarTree' => $sidebarTree,
- 'commentsEnabled' => $commentsEnabled,
+ 'commentTree' => $commentTree,
'pageNav' => $pageNav,
'next' => $nextPreviousLocator->getNext(),
'previous' => $nextPreviousLocator->getPrevious(),
}
}
+.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;
}
--- /dev/null
+<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>
\ No newline at end of file
-<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">
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>
</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>
@include('entities.sibling-navigation', ['next' => $next, 'previous' => $previous])
- @if ($commentsEnabled)
+ @if ($commentTree->enabled())
@if(($previous || $next))
<div class="px-xl">
<hr class="darker">
@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