]> BookStack Code Mirror - bookstack/commitdiff
Comments: Started archive display, created mode for tree node
authorDan Brown <redacted>
Mon, 28 Apr 2025 19:09:18 +0000 (20:09 +0100)
committerDan Brown <redacted>
Mon, 28 Apr 2025 19:09:18 +0000 (20:09 +0100)
app/Activity/CommentRepo.php
app/Activity/Controllers/CommentController.php
app/Activity/Tools/CommentTree.php
app/Activity/Tools/CommentTreeNode.php [new file with mode: 0644]
lang/en/entities.php
resources/js/components/page-comment.ts
resources/js/components/page-comments.ts
resources/views/comments/comment-branch.blade.php
resources/views/comments/comment.blade.php
resources/views/comments/comments.blade.php

index 866368ee64963a0f5a78ffd1d29931885d58c898..bf162f68ae60dfd7f76aa8d376b8911e0cef7cb5 100644 (file)
@@ -4,6 +4,8 @@ namespace BookStack\Activity;
 
 use BookStack\Activity\Models\Comment;
 use BookStack\Entities\Models\Entity;
+use BookStack\Exceptions\NotifyException;
+use BookStack\Exceptions\PrettyException;
 use BookStack\Facades\Activity as ActivityService;
 use BookStack\Util\HtmlDescriptionFilter;
 
@@ -59,6 +61,10 @@ class CommentRepo
      */
     public function archive(Comment $comment): Comment
     {
+        if ($comment->parent_id) {
+            throw new NotifyException('Only top-level comments can be archived.');
+        }
+
         $comment->archived = true;
         $comment->save();
 
@@ -72,6 +78,10 @@ class CommentRepo
      */
     public function unarchive(Comment $comment): Comment
     {
+        if ($comment->parent_id) {
+            throw new NotifyException('Only top-level comments can be un-archived.');
+        }
+
         $comment->archived = false;
         $comment->save();
 
index 7a290ebabc526969ae38232be72982c8af953f30..7f16c17ffced39b3b0c534954dcd8eec9a77cd6a 100644 (file)
@@ -3,6 +3,8 @@
 namespace BookStack\Activity\Controllers;
 
 use BookStack\Activity\CommentRepo;
+use BookStack\Activity\Tools\CommentTree;
+use BookStack\Activity\Tools\CommentTreeNode;
 use BookStack\Entities\Queries\PageQueries;
 use BookStack\Http\Controller;
 use Illuminate\Http\Request;
@@ -45,10 +47,7 @@ class CommentController extends Controller
 
         return view('comments.comment-branch', [
             'readOnly' => false,
-            'branch' => [
-                'comment' => $comment,
-                'children' => [],
-            ]
+            'branch' => new CommentTreeNode($comment, 0, []),
         ]);
     }
 
@@ -81,15 +80,17 @@ class CommentController extends Controller
     public function archive(int $id)
     {
         $comment = $this->commentRepo->getById($id);
+        $this->checkOwnablePermission('page-view', $comment->entity);
         if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) {
             $this->showPermissionError();
         }
 
         $this->commentRepo->archive($comment);
 
-        return view('comments.comment', [
-            'comment' => $comment,
+        $tree = new CommentTree($comment->entity);
+        return view('comments.comment-branch', [
             'readOnly' => false,
+            'branch' => $tree->getCommentNodeForId($id),
         ]);
     }
 
@@ -99,15 +100,17 @@ class CommentController extends Controller
     public function unarchive(int $id)
     {
         $comment = $this->commentRepo->getById($id);
+        $this->checkOwnablePermission('page-view', $comment->entity);
         if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) {
             $this->showPermissionError();
         }
 
         $this->commentRepo->unarchive($comment);
 
-        return view('comments.comment', [
-            'comment' => $comment,
+        $tree = new CommentTree($comment->entity);
+        return view('comments.comment-branch', [
             'readOnly' => false,
+            'branch' => $tree->getCommentNodeForId($id),
         ]);
     }
 
index 16f6804ea4244358568c7a5bdc957953c6c93c2d..13afc92521d452e659c51ae1fb55a1c13ef75d53 100644 (file)
@@ -9,7 +9,7 @@ class CommentTree
 {
     /**
      * The built nested tree structure array.
-     * @var array{comment: Comment, depth: int, children: array}[]
+     * @var CommentTreeNode[]
      */
     protected array $tree;
     protected array $comments;
@@ -36,9 +36,25 @@ class CommentTree
         return count($this->comments);
     }
 
-    public function get(): array
+    public function getActive(): array
     {
-        return $this->tree;
+        return array_filter($this->tree, fn (CommentTreeNode $node) => !$node->comment->archived);
+    }
+
+    public function getArchived(): array
+    {
+        return array_filter($this->tree, fn (CommentTreeNode $node) => $node->comment->archived);
+    }
+
+    public function getCommentNodeForId(int $commentId): ?CommentTreeNode
+    {
+        foreach ($this->tree as $node) {
+            if ($node->comment->id === $commentId) {
+                return $node;
+            }
+        }
+
+        return null;
     }
 
     public function canUpdateAny(): bool
@@ -54,6 +70,7 @@ class CommentTree
 
     /**
      * @param Comment[] $comments
+     * @return CommentTreeNode[]
      */
     protected function createTree(array $comments): array
     {
@@ -77,26 +94,22 @@ class CommentTree
 
         $tree = [];
         foreach ($childMap[0] ?? [] as $childId) {
-            $tree[] = $this->createTreeForId($childId, 0, $byId, $childMap);
+            $tree[] = $this->createTreeNodeForId($childId, 0, $byId, $childMap);
         }
 
         return $tree;
     }
 
-    protected function createTreeForId(int $id, int $depth, array &$byId, array &$childMap): array
+    protected function createTreeNodeForId(int $id, int $depth, array &$byId, array &$childMap): CommentTreeNode
     {
         $childIds = $childMap[$id] ?? [];
         $children = [];
 
         foreach ($childIds as $childId) {
-            $children[] = $this->createTreeForId($childId, $depth + 1, $byId, $childMap);
+            $children[] = $this->createTreeNodeForId($childId, $depth + 1, $byId, $childMap);
         }
 
-        return [
-            'comment' => $byId[$id],
-            'depth' => $depth,
-            'children' => $children,
-        ];
+        return new CommentTreeNode($byId[$id], $depth, $children);
     }
 
     protected function loadComments(): array
diff --git a/app/Activity/Tools/CommentTreeNode.php b/app/Activity/Tools/CommentTreeNode.php
new file mode 100644 (file)
index 0000000..7b280bd
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+namespace BookStack\Activity\Tools;
+
+use BookStack\Activity\Models\Comment;
+
+class CommentTreeNode
+{
+    public Comment $comment;
+    public int $depth;
+
+    /**
+     * @var CommentTreeNode[]
+     */
+    public array $children;
+
+    public function __construct(Comment $comment, int $depth, array $children)
+    {
+        $this->comment = $comment;
+        $this->depth = $depth;
+        $this->children = $children;
+    }
+}
index 141e75b5f3568a0e325a2435d9ce523474b4905b..cda58e65bdf25846665ed4b32ec45567182b84bf 100644 (file)
@@ -392,6 +392,7 @@ return [
     'comment' => 'Comment',
     'comments' => 'Comments',
     'comment_add' => 'Add Comment',
+    'comment_archived' => ':count Archived Comment|:count Archived Comments',
     'comment_placeholder' => 'Leave a comment here',
     'comment_count' => '{0} No Comments|{1} 1 Comment|[2,*] :count Comments',
     'comment_save' => 'Save Comment',
index d2cbd21d1db89810fbd95255b8efd78b6ee3f9fa..82cb95f13de027f6b869f50581e2bd7e7a7a4707 100644 (file)
@@ -137,10 +137,12 @@ export class PageComment extends Component {
     protected async archive(): Promise<void> {
         this.showLoading();
         const isArchived = this.archiveButton.dataset.isArchived === 'true';
+        const action = isArchived ? 'unarchive' : 'archive';
 
-        await window.$http.put(`/comment/${this.commentId}/${isArchived ? 'unarchive' : 'archive'}`);
-        this.$emit('archive');
+        const response = await window.$http.put(`/comment/${this.commentId}/${action}`);
+        this.$emit(action, {new_thread_dom: htmlToDom(response.data as string)});
         window.$events.success(this.archiveText);
+        this.container.closest('.comment-branch')?.remove();
     }
 
     protected showLoading(): HTMLElement {
index 45f8d6a9f6d192494560386899ac1066e366002c..083919b826f34210dabce556bacd4d7bb8e9049d 100644 (file)
@@ -9,6 +9,12 @@ export interface CommentReplyEvent extends Event {
     }
 }
 
+export interface ArchiveEvent extends Event {
+    detail: {
+        new_thread_dom: HTMLElement;
+    }
+}
+
 export class PageComments extends Component {
 
     private elem: HTMLElement;
@@ -17,6 +23,7 @@ export class PageComments extends Component {
     private commentCountBar: HTMLElement;
     private commentsTitle: HTMLElement;
     private addButtonContainer: HTMLElement;
+    private archiveContainer: HTMLElement;
     private replyToRow: HTMLElement;
     private formContainer: HTMLElement;
     private form: HTMLFormElement;
@@ -43,6 +50,7 @@ export class PageComments extends Component {
         this.commentCountBar = this.$refs.commentCountBar;
         this.commentsTitle = this.$refs.commentsTitle;
         this.addButtonContainer = this.$refs.addButtonContainer;
+        this.archiveContainer = this.$refs.archiveContainer;
         this.replyToRow = this.$refs.replyToRow;
         this.formContainer = this.$refs.formContainer;
         this.form = this.$refs.form as HTMLFormElement;
@@ -75,6 +83,14 @@ export class PageComments extends Component {
             this.setReply(event.detail.id, event.detail.element);
         });
 
+        this.elem.addEventListener('page-comment-archive', (event: ArchiveEvent) => {
+            this.archiveContainer.append(event.detail.new_thread_dom);
+        });
+
+        this.elem.addEventListener('page-comment-unarchive', (event: ArchiveEvent) => {
+            this.container.append(event.detail.new_thread_dom)
+        });
+
         if (this.form) {
             this.removeReplyToButton.addEventListener('click', this.removeReplyTo.bind(this));
             this.hideFormButton.addEventListener('click', this.hideForm.bind(this));
index 83fa4b5c595274efcc2a43c99af758d0d21e794e..658c33219c33886a585430084987999b4804f8dc 100644 (file)
@@ -1,13 +1,16 @@
+{{--
+$branch CommentTreeNode
+--}}
 <div class="comment-branch">
     <div>
-        @include('comments.comment', ['comment' => $branch['comment']])
+        @include('comments.comment', ['comment' => $branch->comment])
     </div>
     <div class="flex-container-row">
         <div class="comment-thread-indicator-parent">
             <div class="comment-thread-indicator"></div>
         </div>
         <div class="comment-branch-children flex">
-            @foreach($branch['children'] as $childBranch)
+            @foreach($branch->children as $childBranch)
                 @include('comments.comment-branch', ['branch' => $childBranch])
             @endforeach
         </div>
index 58e057140a0e07f13e84cd5fc41c765a3bc0a06e..fe61bf1a4f6b0e6e4d771962d3175cb5d436abd1 100644 (file)
@@ -38,7 +38,7 @@
                     @if(userCan('comment-create-all'))
                         <button refs="page-comment@reply-button" type="button" class="text-button text-muted hover-underline text-small p-xs">@icon('reply') {{ trans('common.reply') }}</button>
                     @endif
-                    @if(userCan('comment-update', $comment) || userCan('comment-delete', $comment))
+                    @if(!$comment->parent_id && (userCan('comment-update', $comment) || userCan('comment-delete', $comment)))
                         <button refs="page-comment@archive-button"
                                 type="button"
                                 data-is-archived="{{ $comment->archived ? 'true' : 'false' }}"
index 48bf885fff5e6ccba76c75de9c3e558a4ef1d23f..06e96cad689f2c547fc9007e3a2a63437ce11ff9 100644 (file)
@@ -18,8 +18,8 @@
         @endif
     </div>
 
-    <div refs="page-comments@commentContainer" class="comment-container">
-        @foreach($commentTree->get() as $branch)
+    <div refs="page-comments@comment-container" class="comment-container">
+        @foreach($commentTree->getActive() as $branch)
             @include('comments.comment-branch', ['branch' => $branch, 'readOnly' => false])
         @endforeach
     </div>
     @if(userCan('comment-create-all'))
         @include('comments.create')
         @if (!$commentTree->empty())
-            <div refs="page-comments@addButtonContainer" class="text-right">
+            <div refs="page-comments@addButtonContainer" class="flex-container-row">
+
+                <button type="button"
+                        refs="page-comments@show-archived-button"
+                        class="text-button hover-underline">{{ trans_choice('entities.comment_archived', count($commentTree->getArchived())) }}</button>
+
                 <button type="button"
                         refs="page-comments@add-comment-button"
-                        class="button outline">{{ trans('entities.comment_add') }}</button>
+                        class="button outline ml-auto">{{ trans('entities.comment_add') }}</button>
             </div>
         @endif
     @endif
 
+    <div refs="page-comments@archive-container" class="comment-container">
+        @foreach($commentTree->getArchived() as $branch)
+            @include('comments.comment-branch', ['branch' => $branch, 'readOnly' => false])
+        @endforeach
+    </div>
+
     @if(userCan('comment-create-all') || $commentTree->canUpdateAny())
         @push('body-end')
             <script src="{{ versioned_asset('libs/tinymce/tinymce.min.js') }}" nonce="{{ $cspNonce }}" defer></script>
Morty Proxy This is a proxified and sanitized view of the page, visit original site.