UBERF-5024: add reactions control to inbox (#4414)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-01-23 19:22:56 +04:00 committed by GitHub
parent 04b3103372
commit 57409feb99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 128 additions and 75 deletions

View File

@ -244,7 +244,7 @@ function groupByTime<T extends ActivityMessage> (messages: T[]): T[][] {
function getDocUpdateMessageKey (message: DocUpdateMessage): string {
const personAccountById = get(personAccountByIdStore)
const person = personAccountById.get(message.modifiedBy as any)?.person ?? message.modifiedBy
const person = personAccountById.get(message.createdBy as any)?.person ?? message.createdBy
if (message.action === 'update') {
return [message._class, message.attachedTo, message.action, person, getAttributeUpdatesKey(message)].join('_')

View File

@ -86,6 +86,7 @@
}
const loc = getLocation()
loc.path[4] = message._id
loc.query = { ...loc.query, thread: message._id }
navigate(loc)
}
</script>

View File

@ -178,8 +178,9 @@
class:opened={isActionMenuOpened || message.isPinned}
>
{#if withActions}
<AddReactionAction object={message} on:open={handleActionMenuOpened} on:close={handleActionMenuClosed} />
{#if withFlatActions}
<AddReactionAction object={message} />
<PinMessageAction object={message} />
<SaveMessageAction object={message} />
@ -231,11 +232,9 @@
}
&.embedded {
padding: 0;
.content {
padding: 0.75rem 0.75rem 0.75rem 0;
}
padding: 0.75rem 0 0 0;
gap: 0.25rem;
border-radius: 0;
}
.actions {
@ -292,7 +291,7 @@
}
.embeddedMarker {
width: 6px;
width: 0.25rem;
border-radius: 0.5rem;
background: var(--secondary-button-default);
}

View File

@ -41,8 +41,10 @@
function handleReply (): void {
const loc = getLocation()
loc.fragment = notification.docNotifyContext
loc.query = { message: notification.attachedTo }
loc.query = { thread: parentMessage?._id ?? message._id }
navigate(loc)
}
</script>

View File

@ -38,6 +38,7 @@ export { default as ActivityDocLink } from './components/ActivityDocLink.svelte'
export { default as ReactionPresenter } from './components/reactions/ReactionPresenter.svelte'
export { default as ActivityMessageNotificationLabel } from './components/activity-message/ActivityMessageNotificationLabel.svelte'
export { default as ActivityMessageHeader } from './components/activity-message/ActivityMessageHeader.svelte'
export { default as AddReactionAction } from './components/reactions/AddReactionAction.svelte'
export default async (): Promise<Resources> => ({
component: {

View File

@ -48,7 +48,7 @@
onDestroy(unsubscribe)
$: isDocChannel = !hierarchy.isDerived(context.attachedToClass, chunter.class.ChunterSpace)
$: isDocChannel = !hierarchy.isDerived(object._class, chunter.class.ChunterSpace)
$: messagesClass = isDocChannel ? activity.class.ActivityMessage : chunter.class.ChatMessage
$: collection = isDocChannel ? 'comments' : 'messages'
</script>

View File

@ -13,13 +13,13 @@
// limitations under the License.
-->
<script lang="ts">
import { Class, Ref } from '@hcengineering/core'
import { DocNotifyContext } from '@hcengineering/notification'
import { Class, Ref, WithLookup } from '@hcengineering/core'
import { ActivityInboxNotification, DocNotifyContext } from '@hcengineering/notification'
import { createQuery, getClient } from '@hcengineering/presentation'
import activity, { ActivityMessage } from '@hcengineering/activity'
import { ChunterSpace } from '@hcengineering/chunter'
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
import { location as locationStore } from '@hcengineering/ui'
import { location as locationStore, Location } from '@hcengineering/ui'
import { isReactionMessage } from '@hcengineering/activity-resources'
import ChannelPresenter from './ChannelView.svelte'
@ -37,10 +37,13 @@
let object: ChunterSpace | undefined = undefined
let threadId: Ref<ActivityMessage> | undefined = undefined
let notification: WithLookup<ActivityInboxNotification> | undefined = undefined
let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
let loc: Location | undefined = undefined
locationStore.subscribe((newLocation) => {
loc = newLocation
selectedMessageId = newLocation.query?.message as Ref<ActivityMessage> | undefined
})
@ -48,11 +51,25 @@
? $activityInboxNotificationsStore.find(({ attachedTo }) => attachedTo === selectedMessageId)
: undefined
$: threadId =
hierarchy.isDerived(context.attachedToClass, activity.class.ActivityMessage) &&
!isReactionMessage(notification?.$lookup?.attachedTo)
? (context.attachedTo as Ref<ActivityMessage>)
: undefined
$: updateThreadId(context, notification, loc)
function updateThreadId (
context: DocNotifyContext,
notification?: WithLookup<ActivityInboxNotification>,
loc?: Location
) {
threadId = loc?.query?.thread as Ref<ActivityMessage> | undefined
if (threadId !== undefined) {
return
}
threadId =
hierarchy.isDerived(context.attachedToClass, activity.class.ActivityMessage) &&
!isReactionMessage(notification?.$lookup?.attachedTo)
? (context.attachedTo as Ref<ActivityMessage>)
: undefined
}
$: objectQuery.query(_class, { _id }, (res) => {
object = res[0]

View File

@ -189,6 +189,7 @@ export async function deleteChatMessage (message: ChatMessage): Promise<void> {
export async function replyToThread (message: ActivityMessage): Promise<void> {
const loc = getLocation()
loc.path[4] = message._id
loc.query = { ...loc.query, thread: message._id }
navigate(loc)
}

View File

@ -15,6 +15,7 @@
<script lang="ts">
import { ActionIcon, Component, IconMoreH, Label, showPopup } from '@hcengineering/ui'
import notification, {
ActivityInboxNotification,
ActivityNotificationViewlet,
DisplayInboxNotification,
DocNotifyContext
@ -23,19 +24,24 @@
import { getDocTitle, getDocIdentifier, Menu } from '@hcengineering/view-resources'
import chunter from '@hcengineering/chunter'
import { createEventDispatcher } from 'svelte'
import { WithLookup } from '@hcengineering/core'
import { AddReactionAction } from '@hcengineering/activity-resources'
import InboxNotificationPresenter from './inbox/InboxNotificationPresenter.svelte'
import NotifyContextIcon from './NotifyContextIcon.svelte'
import NotifyMarker from './NotifyMarker.svelte'
export let value: DocNotifyContext
export let notifications: DisplayInboxNotification[] = []
export let notifications: WithLookup<DisplayInboxNotification>[] = []
export let viewlets: ActivityNotificationViewlet[] = []
const client = getClient()
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
let idTitle: string | undefined
let title: string | undefined
$: visibleNotification = notifications[0]
function showMenu (ev: MouseEvent): void {
@ -50,15 +56,40 @@
chunter.action.OpenChannel
]
},
ev.target as HTMLElement
ev.target as HTMLElement,
handleActionMenuClosed
)
handleActionMenuOpened()
}
const presenterMixin = hierarchy.classHierarchyMixin(
$: presenterMixin = hierarchy.classHierarchyMixin(
value.attachedToClass,
notification.mixin.NotificationContextPresenter
)
$: isCompact = notifications.length === 1
$: message =
visibleNotification._class === notification.class.ActivityInboxNotification
? (visibleNotification as WithLookup<ActivityInboxNotification>).$lookup?.attachedTo
: undefined
let isActionMenuOpened = false
function handleActionMenuOpened (): void {
isActionMenuOpened = true
}
function handleActionMenuClosed (): void {
isActionMenuOpened = false
}
$: getDocIdentifier(client, value.attachedTo, value.attachedToClass).then((res) => {
idTitle = res
})
$: getDocTitle(client, value.attachedTo, value.attachedToClass).then((res) => {
title = res
})
</script>
{#if visibleNotification}
@ -74,13 +105,15 @@
>
{#if isCompact}
<InboxNotificationPresenter value={visibleNotification} {viewlets} showNotify={false} withActions={false} />
<div class="actions compact">
<ActionIcon icon={IconMoreH} size="small" action={showMenu} />
</div>
<div class="notifyMarker compact">
<NotifyMarker count={unreadCount} />
</div>
<div class="actions clear-mins flex flex-gap-2 items-center" class:opened={isActionMenuOpened}>
{#if message}
<AddReactionAction object={message} on:open={handleActionMenuOpened} on:close={handleActionMenuClosed} />
{/if}
<ActionIcon icon={IconMoreH} size="small" action={showMenu} />
</div>
{:else}
<div class="header">
<!-- <CheckBox-->
@ -96,30 +129,23 @@
<Component is={presenterMixin.labelPresenter} props={{ notification: visibleNotification, context: value }} />
{:else}
<div class="labels">
{#await getDocIdentifier(client, value.attachedTo, value.attachedToClass) then title}
{#if title}
{title}
{:else}
<Label label={hierarchy.getClass(value.attachedToClass).label} />
{/if}
{/await}
{#await getDocTitle(client, value.attachedTo, value.attachedToClass) then title}
<div class="title overflow-label" {title}>
{title ?? hierarchy.getClass(value.attachedToClass).label}
</div>
{/await}
{#if idTitle}
{idTitle}
{:else}
<Label label={hierarchy.getClass(value.attachedToClass).label} />
{/if}
<div class="title overflow-label" {title}>
{title ?? hierarchy.getClass(value.attachedToClass).label}
</div>
</div>
{/if}
<div class="actions">
<ActionIcon icon={IconMoreH} size="small" action={showMenu} />
</div>
<div class="notifyMarker">
<NotifyMarker count={unreadCount} />
</div>
</div>
<div class="actions clear-mins flex flex-gap-2 items-center" class:opened={isActionMenuOpened}>
<ActionIcon icon={IconMoreH} size="small" action={showMenu} />
</div>
<div class="notifyMarker">
<NotifyMarker count={unreadCount} />
</div>
<div class="notification">
<InboxNotificationPresenter value={visibleNotification} {viewlets} embedded skipLabel />
</div>
@ -135,7 +161,8 @@
cursor: pointer;
border: 1px solid transparent;
border-radius: 0.5rem;
padding: 0 1rem;
padding: 0.5rem 1rem;
padding-right: 0;
margin: 0.5rem 0;
&.compact {
@ -155,6 +182,22 @@
font-weight: 500;
max-width: 20.5rem;
}
.actions {
position: absolute;
visibility: hidden;
top: 0.75rem;
right: 0.75rem;
color: var(--theme-halfcontent-color);
&.opened {
visibility: visible;
}
}
&:hover > .actions {
visibility: visible;
}
}
.labels {
@ -163,28 +206,17 @@
}
.notification {
margin-top: 1rem;
margin-top: 0.25rem;
margin-left: 4rem;
}
.notifyMarker {
position: absolute;
right: 1.875rem;
left: 0.25rem;
top: 0;
&.compact {
right: 2.875rem;
top: 0.5rem;
}
}
.actions {
position: absolute;
right: 0;
top: 0;
&.compact {
right: 1rem;
left: 0.25rem;
top: 0.5rem;
}
}

View File

@ -40,8 +40,8 @@
const client = getClient()
const messagesQuery = createQuery()
const notificationsClient = InboxNotificationsClientImpl.getClient()
const notificationsStore = notificationsClient.inboxNotifications
const inboxClient = InboxNotificationsClientImpl.getClient()
const notificationsStore = inboxClient.inboxNotifications
let messages: ActivityMessage[] = []
let viewlet: ActivityNotificationViewlet | undefined = undefined
@ -81,6 +81,7 @@
function updateViewlet (viewlets: ActivityNotificationViewlet[], message?: DisplayActivityMessage) {
if (viewlets.length === 0 || message === undefined) {
viewlet = undefined
return
}
@ -91,6 +92,8 @@
return
}
}
viewlet = undefined
}
function handleReply (message?: DisplayActivityMessage): void {
@ -99,7 +102,7 @@
}
const loc = getLocation()
loc.fragment = value.docNotifyContext
loc.query = { message: message._id }
loc.query = { thread: message._id }
navigate(loc)
}

View File

@ -96,6 +96,10 @@
$: filteredNotifications = filterNotifications(selectedTabId, displayNotifications, $notifyContextsStore)
locationStore.subscribe((newLocation) => {
syncLocation(newLocation)
})
async function syncLocation (newLocation: Location) {
const loc = await resolveLocation(newLocation)

View File

@ -49,16 +49,9 @@ import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
* @public
*/
export async function hasMarkAsUnreadAction (doc: DisplayInboxNotification): Promise<boolean> {
const inboxNotificationsClient = InboxNotificationsClientImpl.getClient()
const canRead = await hasMarkAsReadAction(doc)
const combinedIds =
doc._class === notification.class.ActivityInboxNotification
? (doc as DisplayActivityInboxNotification).combinedIds
: [doc._id]
return get(inboxNotificationsClient.inboxNotifications).some(
({ _id, isViewed }) => combinedIds.includes(_id) && isViewed
)
return !canRead
}
export async function hasMarkAsReadAction (doc: DisplayInboxNotification): Promise<boolean> {
@ -353,11 +346,11 @@ async function generateLocation (
loc: {
path: [appComponent, workspace, inboxId],
fragment: contextId,
query: { message: message !== undefined ? (messageId as string) : null }
query: { ...loc.query, message: message !== undefined ? (messageId as string) : null }
},
defaultLocation: {
path: [appComponent, workspace, inboxId],
query: { message: message !== undefined ? (messageId as string) : null }
query: { ...loc.query, message: message !== undefined ? (messageId as string) : null }
}
}
}