Skip to content

Commit

Permalink
add pinned messages filter
Browse files Browse the repository at this point in the history
  • Loading branch information
asliayk committed Jan 20, 2025
1 parent acb1220 commit 3f374ea
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 20 deletions.
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 @@ -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();
}
}

applyFilter(): 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.applyFilter();

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

0 comments on commit 3f374ea

Please sign in to comment.