public function empty(): bool
{
- return count($this->tree) === 0;
+ return count($this->getActive()) === 0;
}
public function count(): int
return array_filter($this->tree, fn (CommentTreeNode $node) => !$node->comment->archived);
}
+ public function activeThreadCount(): int
+ {
+ return count($this->getActive());
+ }
+
public function getArchived(): array
{
return array_filter($this->tree, fn (CommentTreeNode $node) => $node->comment->archived);
}
+ public function archivedThreadCount(): int
+ {
+ return count($this->getArchived());
+ }
+
public function getCommentNodeForId(int $commentId): ?CommentTreeNode
{
foreach ($this->tree as $node) {
'comment' => 'Comment',
'comments' => 'Comments',
'comment_add' => 'Add Comment',
- 'comment_archived' => ':count Archived Comment|:count Archived Comments',
+ 'comment_none' => 'No comments to display',
'comment_placeholder' => 'Leave a comment here',
- 'comment_count' => '{0} No Comments|{1} 1 Comment|[2,*] :count Comments',
+ 'comment_thread_count' => ':count Comment Thread|:count Comment Threads',
+ 'comment_archived_count' => ':count Archived',
'comment_save' => 'Save Comment',
'comment_new' => 'New Comment',
'comment_created' => 'commented :createDiff',
const action = isArchived ? 'unarchive' : '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.$emit(action, {new_thread_dom: htmlToDom(response.data as string)});
this.container.closest('.comment-branch')?.remove();
}
import {Component} from './component';
import {getLoading, htmlToDom} from '../services/dom.ts';
import {buildForInput} from '../wysiwyg-tinymce/config';
+import {Tabs} from "./tabs";
export interface CommentReplyEvent extends Event {
detail: {
private pageId: number;
private container: HTMLElement;
private commentCountBar: HTMLElement;
- private commentsTitle: HTMLElement;
+ private activeTab: HTMLElement;
+ private archivedTab: HTMLElement;
private addButtonContainer: HTMLElement;
private archiveContainer: HTMLElement;
private replyToRow: HTMLElement;
private wysiwygEditor: any = null;
private createdText: string;
private countText: string;
+ private archivedCountText: string;
private parentId: number | null = null;
private contentReference: string = '';
private formReplyText: string = '';
// Element references
this.container = this.$refs.commentContainer;
this.commentCountBar = this.$refs.commentCountBar;
- this.commentsTitle = this.$refs.commentsTitle;
+ this.activeTab = this.$refs.activeTab;
+ this.archivedTab = this.$refs.archivedTab;
this.addButtonContainer = this.$refs.addButtonContainer;
this.archiveContainer = this.$refs.archiveContainer;
this.replyToRow = this.$refs.replyToRow;
// Translations
this.createdText = this.$opts.createdText;
this.countText = this.$opts.countText;
+ this.archivedCountText = this.$opts.archivedCountText;
this.formReplyText = this.formReplyLink?.textContent || '';
this.elem.addEventListener('page-comment-archive', (event: ArchiveEvent) => {
this.archiveContainer.append(event.detail.new_thread_dom);
+ setTimeout(() => this.updateCount(), 1);
});
this.elem.addEventListener('page-comment-unarchive', (event: ArchiveEvent) => {
- this.container.append(event.detail.new_thread_dom)
+ this.container.append(event.detail.new_thread_dom);
+ setTimeout(() => this.updateCount(), 1);
});
if (this.form) {
}
protected updateCount(): void {
- const count = this.getCommentCount();
- this.commentsTitle.textContent = window.$trans.choice(this.countText, count);
+ const activeCount = this.getActiveThreadCount();
+ this.activeTab.textContent = window.$trans.choice(this.countText, activeCount);
+ const archivedCount = this.getArchivedThreadCount();
+ this.archivedTab.textContent = window.$trans.choice(this.archivedCountText, archivedCount);
}
protected resetForm(): void {
this.addButtonContainer.toggleAttribute('hidden', true);
this.formContainer.scrollIntoView({behavior: 'smooth', block: 'nearest'});
this.loadEditor();
+
+ // Ensure the active comments tab is displaying
+ const tabs = window.$components.firstOnElement(this.elem, 'tabs');
+ if (tabs instanceof Tabs) {
+ tabs.show('comment-tab-panel-active');
+ }
}
protected hideForm(): void {
this.resetForm();
this.formContainer.toggleAttribute('hidden', true);
- if (this.getCommentCount() > 0) {
+ if (this.getActiveThreadCount() > 0) {
this.elem.append(this.addButtonContainer);
} else {
this.commentCountBar.append(this.addButtonContainer);
}
}
- protected getCommentCount(): number {
- return this.container.querySelectorAll('[component="page-comment"]').length;
+ protected getActiveThreadCount(): number {
+ return this.container.querySelectorAll(':scope > .comment-branch:not([hidden])').length;
+ }
+
+ protected getArchivedThreadCount(): number {
+ return this.archiveContainer.querySelectorAll(':scope > .comment-branch').length;
}
protected setReply(commentLocalId, commentElement): void {
*/
export class Tabs extends Component {
+ protected container: HTMLElement;
+ protected tabList: HTMLElement;
+ protected tabs: HTMLElement[];
+ protected panels: HTMLElement[];
+
+ protected activeUnder: number;
+ protected active: null|boolean = null;
+
setup() {
this.container = this.$el;
- this.tabList = this.container.querySelector('[role="tablist"]');
+ this.tabList = this.container.querySelector('[role="tablist"]') as HTMLElement;
this.tabs = Array.from(this.tabList.querySelectorAll('[role="tab"]'));
this.panels = Array.from(this.container.querySelectorAll(':scope > [role="tabpanel"], :scope > * > [role="tabpanel"]'));
this.activeUnder = this.$opts.activeUnder ? Number(this.$opts.activeUnder) : 10000;
- this.active = null;
this.container.addEventListener('click', event => {
- const tab = event.target.closest('[role="tab"]');
- if (tab && this.tabs.includes(tab)) {
- this.show(tab.getAttribute('aria-controls'));
+ const tab = (event.target as HTMLElement).closest('[role="tab"]');
+ if (tab instanceof HTMLElement && this.tabs.includes(tab)) {
+ this.show(tab.getAttribute('aria-controls') || '');
}
});
this.updateActiveState();
}
- show(sectionId) {
+ public show(sectionId: string): void {
for (const panel of this.panels) {
panel.toggleAttribute('hidden', panel.id !== sectionId);
}
this.$emit('change', {showing: sectionId});
}
- updateActiveState() {
+ protected updateActiveState(): void {
const active = window.innerWidth < this.activeUnder;
if (active === this.active) {
return;
this.active = active;
}
- activate() {
+ protected activate(): void {
const panelToShow = this.panels.find(p => !p.hasAttribute('hidden')) || this.panels[0];
this.show(panelToShow.id);
this.tabList.toggleAttribute('hidden', false);
}
- deactivate() {
+ protected deactivate(): void {
for (const panel of this.panels) {
panel.removeAttribute('hidden');
}
display: block;
}
+.comment-container .empty-state {
+ display: none;
+}
+.comment-container:not(:has([component="page-comment"])) .empty-state {
+ display: block;
+}
+
.comment-container-compact .comment-box {
margin-bottom: vars.$xs;
.meta {
-<section component="page-comments"
+<section components="page-comments tabs"
option:page-comments:page-id="{{ $page->id }}"
option:page-comments:created-text="{{ trans('entities.comment_created_success') }}"
- option:page-comments:count-text="{{ trans('entities.comment_count') }}"
+ option:page-comments:count-text="{{ trans('entities.comment_thread_count') }}"
+ option:page-comments:archived-count-text="{{ trans('entities.comment_archived_count') }}"
option:page-comments:wysiwyg-language="{{ $locale->htmlLang() }}"
option:page-comments:wysiwyg-text-direction="{{ $locale->htmlDirection() }}"
- class="comments-list"
+ class="comments-list tab-container"
aria-label="{{ trans('entities.comments') }}">
- <div refs="page-comments@comment-count-bar" class="grid half left-focus v-center no-row-gap">
- <h5 refs="page-comments@comments-title">{{ trans_choice('entities.comment_count', $commentTree->count(), ['count' => $commentTree->count()]) }}</h5>
+ <div refs="page-comments@comment-count-bar" class="flex-container-row items-center">
+ <div role="tablist" class="flex">
+ <button type="button"
+ role="tab"
+ id="comment-tab-active"
+ aria-controls="comment-tab-panel-active"
+ refs="page-comments@active-tab"
+ aria-selected="true">{{ trans_choice('entities.comment_thread_count', $commentTree->activeThreadCount()) }}</button>
+ <button type="button"
+ role="tab"
+ id="comment-tab-archived"
+ aria-controls="comment-tab-panel-archived"
+ refs="page-comments@archived-tab"
+ aria-selected="false">{{ trans_choice('entities.comment_archived_count', count($commentTree->getArchived())) }}</button>
+ </div>
@if ($commentTree->empty() && userCan('comment-create-all'))
- <div class="text-m-right" refs="page-comments@add-button-container">
+ <div class="ml-m" refs="page-comments@add-button-container">
<button type="button"
refs="page-comments@add-comment-button"
- class="button outline">{{ trans('entities.comment_add') }}</button>
+ class="button outline mb-m">{{ trans('entities.comment_add') }}</button>
</div>
@endif
</div>
- <div refs="page-comments@comment-container" class="comment-container">
- @foreach($commentTree->getActive() as $branch)
- @include('comments.comment-branch', ['branch' => $branch, 'readOnly' => false])
- @endforeach
- </div>
+ <div id="comment-tab-panel-active"
+ tabindex="0"
+ role="tabpanel"
+ aria-labelledby="comment-tab-active"
+ class="comment-container">
+ <div refs="page-comments@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="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>
+ <p class="text-center text-muted italic empty-state">{{ trans('entities.comment_none') }}</p>
- <button type="button"
- refs="page-comments@add-comment-button"
- class="button outline ml-auto">{{ trans('entities.comment_add') }}</button>
- </div>
+ @if(userCan('comment-create-all'))
+ @include('comments.create')
+ @if (!$commentTree->empty())
+ <div refs="page-comments@addButtonContainer" class="flex-container-row">
+ <button type="button"
+ refs="page-comments@add-comment-button"
+ class="button outline ml-auto">{{ trans('entities.comment_add') }}</button>
+ </div>
+ @endif
@endif
- @endif
+ </div>
- <div refs="page-comments@archive-container" class="comment-container">
+ <div refs="page-comments@archive-container"
+ id="comment-tab-panel-archived"
+ tabindex="0"
+ role="tabpanel"
+ aria-labelledby="comment-tab-archived"
+ hidden="hidden"
+ class="comment-container">
@foreach($commentTree->getArchived() as $branch)
@include('comments.comment-branch', ['branch' => $branch, 'readOnly' => false])
@endforeach
+ <p class="text-center text-muted italic empty-state">{{ trans('entities.comment_none') }}</p>
</div>
@if(userCan('comment-create-all') || $commentTree->canUpdateAny())
@include('entities.sibling-navigation', ['next' => $next, 'previous' => $previous])
@if ($commentTree->enabled())
- @if(($previous || $next))
- <div class="px-xl print-hidden">
- <hr class="darker">
- </div>
- @endif
-
<div class="comments-container mb-l print-hidden">
@include('comments.comments', ['commentTree' => $commentTree, 'page' => $page])
<div class="clearfix"></div>