Comments: Started archive display, created mode for tree node
This commit is contained in:
		
							parent
							
								
									8bdf948743
								
							
						
					
					
						commit
						099f6104d0
					
				| 
						 | 
				
			
			@ -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();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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' }}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -27,14 +27,25 @@
 | 
			
		|||
    @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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue