Group inbox message notifications by author (#5599)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-05-15 19:38:11 +04:00 committed by GitHub
parent adb971e5ab
commit 249fd6b596
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 221 additions and 23 deletions

View File

@ -13,20 +13,30 @@
// limitations under the License.
-->
<script lang="ts">
import { ButtonIcon, CheckBox, Component, IconMoreV, Label, showPopup, Spinner } from '@hcengineering/ui'
import { ButtonIcon, CheckBox, Component, IconMoreV, Label, showPopup, Spinner, tooltip } from '@hcengineering/ui'
import notification, {
ActivityNotificationViewlet,
DisplayInboxNotification,
DocNotifyContext
DocNotifyContext,
InboxNotification
} from '@hcengineering/notification'
import { getClient } from '@hcengineering/presentation'
import { getDocTitle, getDocIdentifier, Menu } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import { WithLookup } from '@hcengineering/core'
import { Class, Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
import chunter from '@hcengineering/chunter'
import { personAccountByIdStore } from '@hcengineering/contact-resources'
import { Person, PersonAccount } from '@hcengineering/contact'
import MessagesPopup from './MessagePopup.svelte'
import InboxNotificationPresenter from './inbox/InboxNotificationPresenter.svelte'
import NotifyContextIcon from './NotifyContextIcon.svelte'
import { archiveContextNotifications, unarchiveContextNotifications } from '../utils'
import {
archiveContextNotifications,
isActivityNotification,
isMentionNotification,
unarchiveContextNotifications
} from '../utils'
export let value: DocNotifyContext
export let notifications: WithLookup<DisplayInboxNotification>[]
@ -60,6 +70,62 @@
notification.mixin.NotificationContextPresenter
)
let groupedNotifications: Array<InboxNotification[]> = []
$: groupedNotifications = groupNotificationsByUser(notifications, $personAccountByIdStore)
function isTextMessage (_class: Ref<Class<Doc>>): boolean {
return hierarchy.isDerived(_class, chunter.class.ChatMessage)
}
const canGroup = (it: InboxNotification): boolean => {
if (isActivityNotification(it) && isTextMessage(it.attachedToClass)) {
return true
}
return isMentionNotification(it) && isTextMessage(it.mentionedInClass)
}
function groupNotificationsByUser (
notifications: WithLookup<InboxNotification>[],
personAccountById: IdMap<PersonAccount>
): Array<InboxNotification[]> {
const result: Array<InboxNotification[]> = []
let group: InboxNotification[] = []
let person: Ref<Person> | undefined = undefined
for (const it of notifications) {
const account = it.createdBy ?? it.modifiedBy
const curPerson = personAccountById.get(account as Ref<PersonAccount>)?.person
const allowGroup = canGroup(it)
if (!allowGroup || curPerson === undefined) {
if (group.length > 0) {
result.push(group)
group = []
person = undefined
}
result.push([it])
continue
}
if (curPerson === person || person === undefined) {
group.push(it)
} else {
result.push(group)
group = [it]
}
person = curPerson
}
if (group.length > 0) {
result.push(group)
}
return result
}
function showMenu (ev: MouseEvent): void {
ev.stopPropagation()
ev.preventDefault()
@ -99,6 +165,16 @@
await archivingPromise
archivingPromise = undefined
}
function canShowTooltip (group: InboxNotification[]): boolean {
const first = group[0]
return canGroup(first)
}
function getKey (group: InboxNotification[]): string {
return group.map((it) => it._id).join('-')
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
@ -152,16 +228,24 @@
<div class="content">
<div class="notifications">
{#each notifications.slice(0, maxNotifications) as notification}
<div class="notification">
{#each groupedNotifications.slice(0, maxNotifications) as group (getKey(group))}
<div
class="notification"
use:tooltip={canShowTooltip(group)
? {
component: MessagesPopup,
props: { context: value, notifications: group }
}
: undefined}
>
<div class="embeddedMarker" />
<InboxNotificationPresenter
value={notification}
value={group[0]}
{viewlets}
on:click={(e) => {
e.preventDefault()
e.stopPropagation()
dispatch('click', { context: value, notification })
dispatch('click', { context: value, notification: group[0] })
}}
/>
</div>

View File

@ -0,0 +1,110 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Ref, WithLookup } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import activity, { ActivityMessage } from '@hcengineering/activity'
import { Lazy, Spinner } from '@hcengineering/ui'
import { ActivityMessagePresenter, canGroupMessages } from '@hcengineering/activity-resources'
import { ActivityInboxNotification, InboxNotification } from '@hcengineering/notification'
import { isActivityNotification, isMentionNotification } from '../utils'
export let notifications: InboxNotification[]
const client = getClient()
const hierarchy = client.getHierarchy()
let loading = true
let messages: ActivityMessage[] = []
$: void updateMessages(notifications)
async function updateMessages (notifications: InboxNotification[]): Promise<void> {
const result: ActivityMessage[] = []
for (const notification of notifications) {
if (isActivityNotification(notification)) {
const it = notification as WithLookup<ActivityInboxNotification>
if (it.$lookup?.attachedTo) {
result.push(it.$lookup?.attachedTo)
}
}
if (isMentionNotification(notification)) {
const it = notification
if (hierarchy.isDerived(it.mentionedInClass, activity.class.ActivityMessage)) {
const message = await client.findOne<ActivityMessage>(it.mentionedInClass, {
_id: it.mentionedIn as Ref<ActivityMessage>
})
if (message !== undefined) {
result.push(message)
}
}
}
}
messages = result.reverse()
loading = false
}
</script>
<div class="commentPopup-container">
<div class="messages">
{#if loading}
<div class="flex-center">
<Spinner />
</div>
{:else}
{#each messages as message, index}
{@const canGroup = canGroupMessages(message, messages[index - 1])}
<div class="item">
<Lazy>
<ActivityMessagePresenter
value={message}
hideLink
skipLabel
type={canGroup ? 'short' : 'default'}
hoverStyles="filledHover"
/>
</Lazy>
</div>
{/each}
{/if}
</div>
</div>
<style lang="scss">
.commentPopup-container {
overflow: hidden;
display: flex;
flex-direction: column;
padding: 0;
min-width: 20rem;
min-height: 0;
max-height: 20rem;
.messages {
overflow: auto;
flex: 1;
min-width: 0;
min-height: 0;
.item {
max-width: 30rem;
}
}
}
</style>

View File

@ -39,7 +39,7 @@
}
</script>
<BaseMessagePreview {actions} {message}>
<BaseMessagePreview {actions} {message} on:click>
<span class="overflow-label flex-presenter flex-gap-1-5">
<Icon icon={contact.icon.Person} size="small" />
<Label

View File

@ -25,7 +25,7 @@
import { InboxNotificationsClientImpl } from '../../inboxNotificationsClient'
import DocNotifyContextCard from '../DocNotifyContextCard.svelte'
import { archiveContextNotifications, unarchiveContextNotifications } from '../../utils'
import { archiveContextNotifications, notificationsComparator, unarchiveContextNotifications } from '../../utils'
import { InboxData } from '../../types'
export let data: InboxData
@ -51,19 +51,9 @@
$: updateDisplayData(data)
function updateDisplayData (data: InboxData): void {
displayData = Array.from(data.entries()).sort(([, notifications1], [, notifications2]) => {
const createdOn1 = notifications1[0].createdOn ?? 0
const createdOn2 = notifications2[0].createdOn ?? 0
if (createdOn1 > createdOn2) {
return -1
}
if (createdOn1 < createdOn2) {
return 1
}
return 0
})
displayData = Array.from(data.entries()).sort(([, notifications1], [, notifications2]) =>
notificationsComparator(notifications1[0], notifications2[0])
)
}
function onKeydown (key: KeyboardEvent): void {

View File

@ -672,3 +672,17 @@ function arrayBufferToBase64 (buffer: ArrayBuffer | null): string {
return ''
}
}
export function notificationsComparator (notifications1: InboxNotification, notifications2: InboxNotification): number {
const createdOn1 = notifications1.createdOn ?? 0
const createdOn2 = notifications2.createdOn ?? 0
if (createdOn1 > createdOn2) {
return -1
}
if (createdOn1 < createdOn2) {
return 1
}
return 0
}