Skip to content

Commit

Permalink
added unread marker
Browse files Browse the repository at this point in the history
  • Loading branch information
asliayk committed Dec 14, 2024
1 parent fafe68c commit 58a30aa
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,12 @@
@for (group of groupedPosts; track postsGroupTrackByFn($index, group)) {
<div class="message-group">
@for (post of group.posts; track postsTrackByFn($index, post)) {
<div class="post-item">
<div class="post-item" style="position: relative">
@if (isFirstUnreadMarker(post, group)) {
<span class="unread-marker">
<span class="unread-text" jhiTranslate="global.generic.new"></span>
</span>
}
<jhi-posting-thread
#postingThread
[lastReadDate]="_activeConversation?.lastReadDate"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,26 @@
jhi-posting-thread {
margin-bottom: 5px;
}

.unread-marker {
position: absolute;
left: 0;
right: 0;
min-height: 0.09rem;
background-color: var(--bs-danger);
z-index: 1;

.unread-text {
position: absolute;
top: -0.6rem;
left: 95%;
transform: translateX(-50%);
background-color: var(--bs-danger);
color: var(--bs-white);
font-size: 0.6rem;
font-weight: bold;
padding: 0.1rem 0.3rem;
border-radius: 0.2rem;
z-index: 2;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD
scrollSubject = new Subject<number>();
canStartSaving = false;
createdNewMessage = false;
unreadCount = 0;

@Output() openThread = new EventEmitter<Post>();

Expand Down Expand Up @@ -145,10 +146,46 @@ export class ConversationMessagesComponent implements OnInit, AfterViewInit, OnD
return;
}
this._activeConversation = conversation;
this.unreadCount = conversation?.unreadMessagesCount || 0;
this.onActiveConversationChange();
});
}

/**
* Determines whether a post is the "first unread message" in the conversation.
*/
isFirstUnreadMarker(post: Post, group: PostGroup): boolean {
let remainingUnread = this.unreadCount;
let firstUnreadFound = false;

const groupIndex = this.groupedPosts.findIndex((g) => g === group);
if (groupIndex === -1) {
return false;
}

for (let i = this.groupedPosts.length - 1; i >= 0; i--) {
const currentGroup = this.groupedPosts[i];
const groupMessageCount = currentGroup.posts.length;

if (i > groupIndex) {
remainingUnread -= groupMessageCount;
} else if (i === groupIndex) {
const postIndexInGroup = currentGroup.posts.indexOf(post);

if (!firstUnreadFound && postIndexInGroup === groupMessageCount - remainingUnread) {
firstUnreadFound = true;
return true;
}

remainingUnread--;
} else {
return false;
}
}

return false;
}

private subscribeToSearch() {
this.search$
.pipe(
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/app/shared/metis/metis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export class MetisService implements OnDestroy {
const indexToUpdate = this.cachedPosts.findIndex((cachedPost) => cachedPost.id === updatedPost.id);
if (indexToUpdate > -1) {
updatedPost.answers = [...(this.cachedPosts[indexToUpdate].answers ?? [])];
updatedPost.authorRole = this.cachedPosts[indexToUpdate].authorRole;
this.cachedPosts[indexToUpdate] = updatedPost;
this.posts$.next(this.cachedPosts);
this.totalNumberOfPosts$.next(this.cachedTotalNumberOfPosts);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="d-flex justify-content-between">
<div class="d-flex justify-content-between" style="padding-top: 0.25rem">
<div id="header-author-date" class="post-header-author-date d-flex align-items-start gap-2 flex-wrap">
@if (posting.author) {
<span class="d-inline-flex align-items-start gap-2 flex-wrap">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Course } from 'app/entities/course.model';
import { getAsChannelDTO } from 'app/entities/metis/conversation/channel.model';
import { PostCreateEditModalComponent } from 'app/shared/metis/posting-create-edit-modal/post-create-edit-modal/post-create-edit-modal.component';
import dayjs from 'dayjs';
import { User } from '../../../../../../../../main/webapp/app/core/user/user.model';

const examples: ConversationDTO[] = [
generateOneToOneChatDTO({}),
Expand All @@ -28,6 +29,11 @@ const examples: ConversationDTO[] = [
generateExampleChannelDTO({ isAnnouncementChannel: true }),
];

interface PostGroup {
author: User | undefined;
posts: Post[];
}

@Directive({
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[infiniteScroll], [infinite-scroll], [data-infinite-scroll]',
Expand Down Expand Up @@ -268,6 +274,56 @@ examples.forEach((activeConversation) => {
expect(component.groupedPosts[2].posts).toHaveLength(1);
});

it('should correctly identify the first unread marker', () => {
const mockPosts: Post[] = [{ id: 1, content: 'Post 1' } as Post, { id: 2, content: 'Post 2' } as Post, { id: 3, content: 'Post 3' } as Post];

const mockGroups: PostGroup[] = [
{ author: undefined, posts: [mockPosts[0], mockPosts[1]] },
{ author: undefined, posts: [mockPosts[2]] },
];

component.groupedPosts = mockGroups;
component.unreadCount = 2;

const isFirstUnreadForPost1 = component.isFirstUnreadMarker(mockPosts[0], mockGroups[0]);
const isFirstUnreadForPost2 = component.isFirstUnreadMarker(mockPosts[1], mockGroups[0]);
const isFirstUnreadForPost3 = component.isFirstUnreadMarker(mockPosts[2], mockGroups[1]);

expect(isFirstUnreadForPost1).toBeFalse();
expect(isFirstUnreadForPost2).toBeTrue();
expect(isFirstUnreadForPost3).toBeFalse();
});

it('should render the unread marker if isFirstUnreadMarker returns true', () => {
const mockPost: Post = { id: 1, content: 'Post 1' } as Post;
const mockGroup: PostGroup = { author: undefined, posts: [mockPost] };

jest.spyOn(component, 'isFirstUnreadMarker').mockReturnValue(true);

component.groupedPosts = [mockGroup];
component.unreadCount = 1;

fixture.detectChanges();

const unreadMarker = fixture.debugElement.query(By.css('.unread-marker'));
expect(unreadMarker).toBeTruthy();
});

it('should not render the unread marker if isFirstUnreadMarker returns false', () => {
const mockPost: Post = { id: 1, content: 'Post 1' } as Post;
const mockGroup: PostGroup = { author: undefined, posts: [mockPost] };

jest.spyOn(component, 'isFirstUnreadMarker').mockReturnValue(false);

component.groupedPosts = [mockGroup];
component.unreadCount = 1;

fixture.detectChanges();

const unreadMarker = fixture.debugElement.query(By.css('.unread-marker'));
expect(unreadMarker).toBeFalsy();
});

if (getAsChannelDTO(activeConversation)?.isAnnouncementChannel) {
it('should display the "new announcement" button when the conversation is an announcement channel', fakeAsync(() => {
const announcementButton = fixture.debugElement.query(By.css('.btn.btn-md.btn-primary'));
Expand Down

0 comments on commit 58a30aa

Please sign in to comment.