Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Communication: Add pinned messages filter #10173

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,21 @@
[ngClass]="{ 'content-height-dev': !isProduction || isTestServer, 'is-answer-thread-open': !!postInThread }"
>
@if (activeConversation) {
<jhi-conversation-header (collapseSearch)="toggleChannelSearch()" (onUpdateSidebar)="prepareSidebarData()" />
<jhi-conversation-header
(collapseSearch)="toggleChannelSearch()"
(onUpdateSidebar)="prepareSidebarData()"
(togglePinnedMessage)="togglePinnedView()"
[pinnedMessageCount]="pinnedCount"
/>
<jhi-conversation-messages
[contentHeightDev]="!isProduction || isTestServer"
(openThread)="openThread($event)"
[course]="course"
[searchbarCollapsed]="channelSearchCollapsed"
[focusPostId]="focusPostId"
[openThreadOnFocus]="openThreadOnFocus"
[showOnlyPinned]="showOnlyPinned"
(pinnedCount)="onPinnedCountChanged($event)"
/>
} @else {
@if (selectedSavedPostStatus === null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
focusPostId: number | undefined = undefined;
openThreadOnFocus = false;
selectedSavedPostStatus: null | SavedPostStatus = null;
showOnlyPinned = false;
pinnedCount: number = 0;

readonly CHANNEL_TYPE_ICON = CHANNEL_TYPE_ICON;
readonly DEFAULT_COLLAPSE_STATE = DEFAULT_COLLAPSE_STATE;
Expand Down Expand Up @@ -203,6 +205,14 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
});
}

togglePinnedView(): void {
this.showOnlyPinned = !this.showOnlyPinned;
}

onPinnedCountChanged(newCount: number): void {
this.pinnedCount = newCount;
}

private setupMetis() {
this.metisService.setPageType(PageType.OVERVIEW);
this.metisService.setCourse(this.course!);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class ChannelFormComponent implements OnInit, OnChanges, OnDestroy {
return this.form.get('isAnnouncementChannel');
}

get isisCourseWideChannelControl() {
get isCourseWideChannelControl() {
return this.form.get('isCourseWideChannel');
}

Expand Down Expand Up @@ -107,8 +107,8 @@ export class ChannelFormComponent implements OnInit, OnChanges, OnDestroy {
});
}

if (this.isisCourseWideChannelControl) {
this.isisCourseWideChannelControl.valueChanges.pipe(takeUntil(this.ngUnsubscribe)).subscribe((value) => {
if (this.isCourseWideChannelControl) {
this.isCourseWideChannelControl.valueChanges.pipe(takeUntil(this.ngUnsubscribe)).subscribe((value) => {
this.isCourseWideChannelChanged.emit(value);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,29 @@ <h4 class="pointer d-inline-block rounded py-2 info mb-0" (click)="openConversat
<div class="d-flex flex-wrap gap-2 py-2 px-3">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group me-1" role="group">
@if (pinnedMessageCount() > 0) {
<div class="pinned-messages-button">
<button type="button" (click)="togglePinnedMessages()" class="btn btn-sm btn-outline-secondary">
@if (showPinnedMessages) {
<jhi-emoji class="fs-x-small" emoji="x" />
} @else {
<jhi-emoji emoji="pushpin" />
}
<span>
{{
showPinnedMessages
? ('artemisApp.metis.showing' | artemisTranslate) +
(pinnedMessageCount() === 1
? ('artemisApp.metis.singlePinned' | artemisTranslate)
: ('artemisApp.metis.multiplePinned' | artemisTranslate: { number: pinnedMessageCount() }))
: pinnedMessageCount() === 1
? ('artemisApp.metis.singlePinned' | artemisTranslate)
: ('artemisApp.metis.multiplePinned' | artemisTranslate: { number: pinnedMessageCount() })
}}
</span>
</button>
</div>
}
<button type="button" class="btn btn-outline-secondary btn-sm search" (click)="toggleSearchBar()">
<fa-icon [icon]="faSearch" />
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.pinned-messages-button button {
border-radius: 1.25rem;
padding: 0.3rem 0.6rem;
display: flex;
align-items: center;
gap: 0.3rem;
margin-right: 1rem;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output, inject } from '@angular/core';
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output, inject, input, output } from '@angular/core';
import { faChevronLeft, faPeopleGroup, faSearch, faUserGroup, faUserPlus } from '@fortawesome/free-solid-svg-icons';
import { ConversationDTO } from 'app/entities/metis/conversation/conversation.model';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
Expand All @@ -25,18 +25,22 @@ import { ChannelIconComponent } from '../../other/channel-icon/channel-icon.comp
import { ProfilePictureComponent } from 'app/shared/profile-picture/profile-picture.component';
import { TranslateDirective } from 'app/shared/language/translate.directive';
import { RouterLink } from '@angular/router';
import { EmojiComponent } from 'app/shared/metis/emoji/emoji.component';
import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';

@Component({
selector: 'jhi-conversation-header',
templateUrl: './conversation-header.component.html',
styleUrls: ['./conversation-header.component.scss'],
imports: [FaIconComponent, ChannelIconComponent, ProfilePictureComponent, TranslateDirective, RouterLink],
imports: [FaIconComponent, ChannelIconComponent, ProfilePictureComponent, TranslateDirective, RouterLink, EmojiComponent, ArtemisTranslatePipe],
})
export class ConversationHeaderComponent implements OnInit, OnDestroy {
private modalService = inject(NgbModal);
metisConversationService = inject(MetisConversationService);
conversationService = inject(ConversationService);
private metisService = inject(MetisService);
pinnedMessageCount = input<number>(0);
togglePinnedMessage = output<void>();

private ngUnsubscribe = new Subject<void>();

Expand All @@ -59,8 +63,10 @@ export class ConversationHeaderComponent implements OnInit, OnDestroy {
faSearch = faSearch;
faChevronLeft = faChevronLeft;
readonly faPeopleGroup = faPeopleGroup;
showPinnedMessages: boolean = false;

private courseSidebarService: CourseSidebarService = inject(CourseSidebarService);
private cdr = inject(ChangeDetectorRef);

getAsGroupChat = getAsGroupChatDTO;
getAsOneToOneChat = getAsOneToOneChatDTO;
Expand All @@ -72,6 +78,12 @@ export class ConversationHeaderComponent implements OnInit, OnDestroy {
this.subscribeToActiveConversation();
}

togglePinnedMessages(): void {
this.togglePinnedMessage.emit();
this.showPinnedMessages = !this.showPinnedMessages;
this.cdr.detectChanges();
}

getOtherUser() {
const conversation = getAsOneToOneChatDTO(this.activeConversation);
if (conversation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import {
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
QueryList,
SimpleChanges,
ViewChild,
ViewChildren,
ViewEncapsulation,
effect,
inject,
input,
output,
} from '@angular/core';
import { faCircleNotch, faEnvelope, faSearch, faTimes } from '@fortawesome/free-solid-svg-icons';
import { Conversation, ConversationDTO } from 'app/entities/metis/conversation/conversation.model';
Expand Down Expand Up @@ -64,7 +67,7 @@ interface PostGroup {
ArtemisTranslatePipe,
],
})
export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnDestroy {
export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges {
metisService = inject(MetisService);
metisConversationService = inject(MetisConversationService);
cdr = inject(ChangeDetectorRef);
Expand All @@ -89,6 +92,8 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD
@Input() course?: Course;
@Input() searchbarCollapsed = false;
@Input() contentHeightDev = false;
showOnlyPinned = input<boolean>(false);
pinnedCount = output<number>();

readonly focusPostId = input<number | undefined>(undefined);
readonly openThreadOnFocus = input<boolean>(false);
Expand All @@ -108,6 +113,7 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD
elementsAtScrollPosition: PostingThreadComponent[];
newPost?: Post;
posts: Post[] = [];
allPosts: Post[] = [];
groupedPosts: PostGroup[] = [];
totalNumberOfPosts = 0;
page = 1;
Expand All @@ -132,13 +138,28 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD
});
}

ngOnChanges(changes: SimpleChanges): void {
if (changes['showOnlyPinned'] && !changes['showOnlyPinned'].firstChange) {
this.setPosts();
}
}

applyPinnedMessageFilter(): void {
if (this.showOnlyPinned()) {
this.posts = this.allPosts.filter((post) => post.displayPriority === DisplayPriority.PINNED);
} else {
this.posts = [...this.allPosts];
}
const pinnedCount = this.allPosts.filter((post) => post.displayPriority === DisplayPriority.PINNED).length;
this.pinnedCount.emit(pinnedCount);
}

ngOnInit(): void {
this.subscribeToSearch();
this.subscribeToMetis();
this.subscribeToActiveConversation();
this.setupScrollDebounce();
this.isMobile = this.layoutService.isBreakpointActive(CustomBreakpointNames.extraSmall);

this.layoutService
.subscribeToLayoutChanges()
.pipe(takeUntil(this.ngUnsubscribe))
Expand Down Expand Up @@ -231,7 +252,8 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD

private subscribeToMetis() {
this.metisService.posts.pipe(takeUntil(this.ngUnsubscribe)).subscribe((posts: Post[]) => {
this.setPosts(posts);
this.allPosts = posts;
this.setPosts();
this.isFetchingPosts = false;
});
this.metisService.totalNumberOfPosts.pipe(takeUntil(this.ngUnsubscribe)).subscribe((totalNumberOfPosts: number) => {
Expand All @@ -258,11 +280,7 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD
return;
}

// Separate pinned posts into their own group
const pinnedPosts = this.posts.filter((post) => post.displayPriority === DisplayPriority.PINNED);
const unpinnedPosts = this.posts.filter((post) => post.displayPriority !== DisplayPriority.PINNED);

const sortedPosts = unpinnedPosts.sort((a, b) => {
const sortedPosts = this.posts.sort((a, b) => {
const aDate = (a as any).creationDateDayjs;
const bDate = (b as any).creationDateDayjs;
return aDate?.valueOf() - bDate?.valueOf();
Expand Down Expand Up @@ -299,21 +317,18 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD

groups.push(currentGroup);

// Only add pinned group if pinned posts exist
if (pinnedPosts.length > 0) {
groups.unshift({ author: undefined, posts: pinnedPosts });
}

this.groupedPosts = groups;
this.cdr.detectChanges();
}

setPosts(posts: Post[]): void {
setPosts(): void {
if (this.content) {
this.previousScrollDistanceFromTop = this.content.nativeElement.scrollHeight - this.content.nativeElement.scrollTop;
}

this.posts = posts
this.applyPinnedMessageFilter();

this.posts = this.posts
.slice()
.reverse()
.map((post) => {
Expand Down
3 changes: 3 additions & 0 deletions src/main/webapp/i18n/de/metis.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"showMultipleAnswers": "Zeige {{ number }} Antworten",
"multipleAnswers": "{{ number }} Antworten",
"showSingleAnswer": "Zeige 1 Antwort",
"showing": "Anzeigen ",
"singlePinned": "1 angeheftete Nachricht",
"multiplePinned": "{{ number }} angeheftete Nachrichten",
"singleAnswer": "1 Antwort",
"collapseAnswers": "Antworten einklappen",
"savePosting": "Speichern",
Expand Down
3 changes: 3 additions & 0 deletions src/main/webapp/i18n/en/metis.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"showMultipleAnswers": "Show {{ number }} replies",
"multipleAnswers": "{{ number }} Replies",
"showSingleAnswer": "Show 1 reply",
"showing": "Showing ",
"singlePinned": "1 pinned message",
"multiplePinned": "{{ number }} pinned messages",
"singleAnswer": "1 Reply",
"collapseAnswers": "Hide replies",
"savePosting": "Save",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,23 @@ examples.forEach((activeConversation) => {
expect(component.selectedSavedPostStatus).toBeNull();
expect(metisConversationService.setActiveConversation).not.toHaveBeenCalled();
});

it('should toggle the value of showOnlyPinned', () => {
expect(component.showOnlyPinned).toBe(false);

component.togglePinnedView();
expect(component.showOnlyPinned).toBe(true);

component.togglePinnedView();
expect(component.showOnlyPinned).toBe(false);
});

it('should update pinnedCount when onPinnedCountChanged is called', () => {
const newPinnedCount = 5;

component.onPinnedCountChanged(newPinnedCount);
expect(component.pinnedCount).toBe(newPinnedCount);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,26 @@ examples.forEach((activeConversation) => {
expect(component.otherUser).toEqual(oneToOneChat.members[1]);
});

it('should toggle pinned messages visibility', fakeAsync(() => {
const togglePinnedMessagesSpy = jest.spyOn(component, 'togglePinnedMessages');

expect(component.showPinnedMessages).toBe(false);

component.togglePinnedMessages();
expect(togglePinnedMessagesSpy).toHaveBeenCalled();
expect(component.showPinnedMessages).toBe(true);

component.togglePinnedMessages();
expect(component.showPinnedMessages).toBe(false);
}));

it('should emit togglePinnedMessage event', fakeAsync(() => {
const togglePinnedMessageSpy = jest.spyOn(component.togglePinnedMessage, 'emit');

component.togglePinnedMessages();
expect(togglePinnedMessageSpy).toHaveBeenCalled();
}));

if (activeConversation instanceof ChannelDTO && activeConversation.subType !== ChannelSubType.GENERAL) {
it(
'should navigate to ' + activeConversation.subType,
Expand Down
Loading
Loading