[UBER-783, UBERF-61] Messsage thread notification fixes (#3640)

Signed-off-by: Oleg Solodkov <oleg.solodkov@xored.com>
This commit is contained in:
Oleg Solodkov 2023-08-30 12:52:07 +07:00 committed by GitHub
parent 93fd82a797
commit af73876255
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 121 additions and 35 deletions

View File

@ -721,7 +721,7 @@ export function createModel (builder: Builder, options = { addApplication: true
},
group: chunter.ids.ChunterNotificationGroup
},
chunter.ids.ThreadNotification
chunter.ids.ChannelNotification
)
builder.createDoc(
@ -738,7 +738,7 @@ export function createModel (builder: Builder, options = { addApplication: true
},
group: chunter.ids.ChunterNotificationGroup
},
chunter.ids.ChannelNotification
chunter.ids.ThreadNotification
)
createAction(builder, {

View File

@ -58,10 +58,6 @@ export function createModel (builder: Builder): void {
}
})
builder.mixin(chunter.ids.DMNotification, notification.class.NotificationType, serverNotification.mixin.TypeMatch, {
func: serverChunter.function.IsDirectMessage
})
builder.mixin(
chunter.ids.MentionNotification,
notification.class.NotificationType,
@ -71,6 +67,19 @@ export function createModel (builder: Builder): void {
}
)
builder.mixin(chunter.ids.DMNotification, notification.class.NotificationType, serverNotification.mixin.TypeMatch, {
func: serverChunter.function.IsDirectMessage
})
builder.mixin(
chunter.ids.ThreadNotification,
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverChunter.function.IsThreadMessage
}
)
builder.mixin(
chunter.ids.ChannelNotification,
notification.class.NotificationType,

View File

@ -23,6 +23,7 @@
"Comment": "Comment",
"Message": "Message",
"MessageOn": "Message on",
"On": "on",
"Reference": "Reference",
"Chat": "Chat",
"In": "In",

View File

@ -23,6 +23,7 @@
"Comment": "Комментарий",
"Message": "Сообщение",
"MessageOn": "Сообщение в",
"On": "в",
"MentionNotification": "Упомянул",
"Reference": "Ссылка",
"Chat": "Чат",

View File

@ -20,11 +20,14 @@
import { AttributeModel } from '@hcengineering/view'
import { getObjectPresenter } from '@hcengineering/view-resources'
import chunterResources from '../plugin'
export let value: Message
export let inline: boolean = false
export let disabled = false
const client = getClient()
const isThreadMessage = client.getHierarchy().isDerived(value._class, chunter.class.ThreadMessage)
let presenter: AttributeModel | undefined
getObjectPresenter(client, value.attachedToClass, { key: '' }).then((p) => {
@ -40,14 +43,16 @@
{#if inline}
{#if presenter && doc}
<div class="flex-presenter inline-presenter">
<div class="icon">
<Icon icon={chunter.icon.Thread} size={'small'} />
</div>
<span class="labels-row" style:text-transform={'lowercase'}>
<Label label={chunter.string.MessageOn} />
</span>
&nbsp;
<div class="flex-presenter">
{#if isThreadMessage}
<div class="icon">
<Icon icon={chunter.icon.Thread} size="small" />
</div>
<span class="labels-row" style:text-transform="lowercase">
<Label label={chunterResources.string.On} />
</span>
&nbsp;
{/if}
<svelte:component this={presenter.presenter} value={doc} inline {disabled} />
</div>
{/if}

View File

@ -16,20 +16,33 @@
import { Message } from '@hcengineering/chunter'
import { Person } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import { IdMap, Ref } from '@hcengineering/core'
import { Doc, IdMap, Ref } from '@hcengineering/core'
import { Avatar } from '@hcengineering/contact-resources'
import { Label, TimeSince } from '@hcengineering/ui'
import { NotificationClientImpl } from '@hcengineering/notification-resources'
import { DocUpdates } from '@hcengineering/notification'
import chunter from '../plugin'
export let message: Message
$: lastReply = message.lastReply ?? new Date().getTime()
$: employees = new Set(message.replies)
const notificationClient = NotificationClientImpl.getClient()
const docUpdates = notificationClient.docUpdatesStore
const shown: number = 4
let showReplies: Person[] = []
$: hasNew = checkNewReplies(message, $docUpdates)
$: updateQuery(employees, $personByIdStore)
function checkNewReplies (message: Message, docUpdates: Map<Ref<Doc>, DocUpdates>): boolean {
const docUpdate = docUpdates.get(message._id)
if (docUpdate === undefined) return false
return docUpdate.txes.filter((tx) => tx.isNew).length > 0
}
function updateQuery (employees: Set<Ref<Person>>, map: IdMap<Person>) {
showReplies = []
for (const employee of employees) {
@ -55,6 +68,9 @@
<div class="whitespace-nowrap ml-2 mr-2 over-underline">
<Label label={chunter.string.RepliesCount} params={{ replies: message.replies?.length ?? 0 }} />
</div>
{#if hasNew}
<div class="marker" />
{/if}
{#if (message.replies?.length ?? 0) > 1}
<div class="mr-1">
<Label label={chunter.string.LastReply} />
@ -101,4 +117,12 @@
background-color: var(--theme-bg-color);
}
}
.marker {
margin: 0 0.25rem 0 -0.25rem;
width: 0.425rem;
height: 0.425rem;
border-radius: 50%;
background-color: var(--highlight-red);
}
</style>

View File

@ -156,11 +156,12 @@
function newMessagesStart (comments: ThreadMessage[], docUpdates: Map<Ref<Doc>, DocUpdates>): number {
const docUpdate = docUpdates.get(_id)
const lastView = docUpdate?.txes?.[0]?.modifiedOn
const lastView = docUpdate?.txes?.findLast((tx) => !tx.isNew)
if (!docUpdate?.txes.some((tx) => tx.isNew)) return -1
if (docUpdate === undefined || lastView === undefined) return -1
for (let index = 0; index < comments.length; index++) {
const comment = comments[index]
if ((comment.createdOn ?? 0) >= lastView) return index
if ((comment.createdOn ?? 0) >= lastView.modifiedOn) return index
}
return -1
}

View File

@ -90,6 +90,7 @@ export default mergeIds(chunterId, chunter, {
CopyLink: '' as IntlString,
You: '' as IntlString,
YouHaveJoinedTheConversation: '' as IntlString,
NoMessages: '' as IntlString
NoMessages: '' as IntlString,
On: '' as IntlString
}
})

View File

@ -18,7 +18,17 @@
import { Class, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
import { DocUpdates } from '@hcengineering/notification'
import { getClient } from '@hcengineering/presentation'
import { AnyComponent, Button, Component, IconAdd, Tabs, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import {
AnyComponent,
Button,
Component,
IconAdd,
Tabs,
eventToHTMLElement,
getLocation,
navigate,
showPopup
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import contact from '@hcengineering/contact'
import { UsersPopup } from '@hcengineering/contact-resources'
@ -53,7 +63,6 @@
let _id: Ref<Doc> | undefined
let _class: Ref<Class<Doc>> | undefined
let selectedEmployee: Ref<PersonAccount> | undefined = undefined
let prevValue: DocUpdates | undefined = undefined
async function select (value: DocUpdates | undefined) {
if (!value) {
@ -62,19 +71,27 @@
_class = undefined
return
}
if (prevValue !== undefined) {
if (prevValue.txes.some((p) => p.isNew)) {
prevValue.txes.forEach((p) => (p.isNew = false))
const txes = prevValue.txes
await client.update(prevValue, { txes })
const isDmOpened = hierarchy.isDerived(value.attachedToClass, chunter.class.ChunterSpace)
if (!isDmOpened && value !== undefined) {
// chats messages are marked as read explicitly, but
// other notifications should be marked as read upon opening
if (value.txes.some((p) => p.isNew)) {
value.txes.forEach((p) => (p.isNew = false))
const txes = value.txes
await client.update(value, { txes })
}
}
const targetClass = hierarchy.getClass(value.attachedToClass)
const panelComponent = hierarchy.as(targetClass, view.mixin.ObjectPanel)
component = panelComponent.component ?? view.component.EditDoc
_id = value.attachedTo
_class = value.attachedToClass
prevValue = value
if (hierarchy.isDerived(value.attachedToClass, chunter.class.ChunterSpace)) {
openDM(value.attachedTo)
} else {
const targetClass = hierarchy.getClass(value.attachedToClass)
const panelComponent = hierarchy.as(targetClass, view.mixin.ObjectPanel)
component = panelComponent.component ?? view.component.EditDoc
_id = value.attachedTo
_class = value.attachedToClass
}
}
function openDM (value: Ref<Doc>) {
@ -84,6 +101,9 @@
component = panelComponent.component ?? view.component.EditDoc
_id = value
_class = chunter.class.DirectMessage
const loc = getLocation()
loc.path[3] = _id
navigate(loc)
}
}

View File

@ -760,7 +760,7 @@
<SpaceView {currentSpace} {currentView} {createItemDialog} {createItemLabel} />
{/if}
</div>
{#if asideId}
{#if asideId && currentSpace}
{@const asideComponent = navigatorModel?.aside ?? currentApplication?.aside}
{#if asideComponent !== undefined}
<div class="splitter" class:hovered={isResizing} on:mousedown={startResize} />

View File

@ -13,7 +13,15 @@
// limitations under the License.
//
import chunter, { Backlink, chunterId, ChunterSpace, Comment, Message, ThreadMessage } from '@hcengineering/chunter'
import chunter, {
Backlink,
chunterId,
ChunterSpace,
Comment,
DirectMessage,
Message,
ThreadMessage
} from '@hcengineering/chunter'
import contact, { Employee, PersonAccount } from '@hcengineering/contact'
import core, {
Account,
@ -455,8 +463,8 @@ export async function IsDirectMessage (
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
const space = (await control.findAll(chunter.class.DirectMessage, { _id: doc.space }))[0]
return space !== undefined
const dm = (await control.findAll(chunter.class.DirectMessage, { _id: doc._id as Ref<DirectMessage> }))[0]
return dm !== undefined
}
function isBacklink (ptx: TxCollectionCUD<Doc, Backlink>, hierarchy: Hierarchy): boolean {
@ -508,6 +516,20 @@ export async function IsChannelMessage (
return space !== undefined
}
/**
* @public
*/
export async function IsThreadMessage (
tx: Tx,
doc: Doc,
user: Ref<Account>,
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
const space = (await control.findAll(chunter.class.DirectMessage, { _id: doc.space }))[0]
return space !== undefined
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
@ -520,6 +542,7 @@ export default async () => ({
ChannelHTMLPresenter: channelHTMLPresenter,
ChannelTextPresenter: channelTextPresenter,
IsDirectMessage,
IsThreadMessage,
IsMeMentioned,
IsChannelMessage
}

View File

@ -49,6 +49,7 @@ export default plugin(serverChunterId, {
ChannelTextPresenter: '' as Resource<Presenter>,
IsDirectMessage: '' as TypeMatchFunc,
IsChannelMessage: '' as TypeMatchFunc,
IsThreadMessage: '' as TypeMatchFunc,
IsMeMentioned: '' as TypeMatchFunc
}
})