mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-23 00:37:47 +00:00
Fix email notifications (#6328)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
cc7240c7d9
commit
071551c66f
@ -129,8 +129,8 @@ export function defineNotifications (builder: Builder): void {
|
||||
defaultEnabled: false,
|
||||
group: chunter.ids.ChunterNotificationGroup,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
textTemplate: '{sender} replied to {doc}:\n\n{message}',
|
||||
htmlTemplate: '<p><b>{sender}</b> replied to {doc}:</p><p>{message}</p><p>{link}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
|
@ -556,8 +556,8 @@ export function createModel (builder: Builder): void {
|
||||
group: notification.ids.NotificationGroup,
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: '{sender} mentioned you in {doc} {message}',
|
||||
htmlTemplate: '<p>{sender}</b> mentioned you in {doc}</p> {message}',
|
||||
textTemplate: '{sender} mentioned you in {doc}: {message}',
|
||||
htmlTemplate: '<p><b>{sender}</b> mentioned you in {doc}:</p> <p>{message}</p> <p>{link}</p>',
|
||||
subjectTemplate: 'You were mentioned in {doc}'
|
||||
}
|
||||
},
|
||||
|
@ -37,6 +37,10 @@ export function createModel (builder: Builder): void {
|
||||
presenter: serverChunter.function.ChatMessageTextPresenter
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.ChatMessage, core.class.Class, serverNotification.mixin.HTMLPresenter, {
|
||||
presenter: serverChunter.function.ChatMessageTextPresenter
|
||||
})
|
||||
|
||||
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
||||
chunter.class.ChatMessage,
|
||||
core.class.Class,
|
||||
|
@ -56,6 +56,7 @@
|
||||
"CommonNotificationCollectionRemoved": "{senderName} removed {collection}",
|
||||
"Sound": "Sound",
|
||||
"SoundNotificationsDescription": "Receive sound notifications for events.",
|
||||
"NoAccessToObject": "You no longer have access to this object"
|
||||
"NoAccessToObject": "You no longer have access to this object",
|
||||
"ViewIn": "View in {app}"
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,7 @@
|
||||
"SoundNotificationsDescription": "Reciba notificaciones de sonido para eventos.",
|
||||
"CommonNotificationCollectionAdded": "{senderName} añadió {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} eliminó {collection}",
|
||||
"NoAccessToObject": "Ya no tienes acceso a este objeto"
|
||||
"NoAccessToObject": "Ya no tienes acceso a este objeto",
|
||||
"ViewIn": "Ver en {app}"
|
||||
}
|
||||
}
|
@ -56,6 +56,7 @@
|
||||
"SoundNotificationsDescription": "Recevez des notifications sonores pour les événements.",
|
||||
"CommonNotificationCollectionAdded": "{senderName} a ajouté {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} a supprimé {collection}",
|
||||
"NoAccessToObject": "Vous n'avez plus accès à cet objet"
|
||||
"NoAccessToObject": "Vous n'avez plus accès à cet objet",
|
||||
"ViewIn": "Voir dans {app}"
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@
|
||||
"SoundNotificationsDescription": "Receba notificações sonoras para eventos.",
|
||||
"CommonNotificationCollectionAdded": "{senderName} adicionou {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} removeu {collection}",
|
||||
"NoAccessToObject": "Você não tem mais acesso a este objeto"
|
||||
"NoAccessToObject": "Você não tem mais acesso a este objeto",
|
||||
"ViewIn": "Ver em {app}"
|
||||
}
|
||||
}
|
@ -56,6 +56,7 @@
|
||||
"SoundNotificationsDescription": "Получайте звуковые уведомления о событиях.",
|
||||
"CommonNotificationCollectionAdded": "{senderName} добавил {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} удалил {collection}",
|
||||
"NoAccessToObject": "У вас больше нет доступа к этому объекту"
|
||||
"NoAccessToObject": "У вас больше нет доступа к этому объекту",
|
||||
"ViewIn": "Посмотреть в {app}"
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@
|
||||
"SoundNotificationsDescription": "接收事件的声音通知。",
|
||||
"CommonNotificationCollectionAdded": "{senderName} 添加了 {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} 移除了 {collection}",
|
||||
"NoAccessToObject": "您不再可以访问此对象"
|
||||
"NoAccessToObject": "您不再可以访问此对象",
|
||||
"ViewIn": "在 {app} 中查看"
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { getCurrentAccount, groupByArray, IdMap, Ref, SortingOrder, Space } from '@hcengineering/core'
|
||||
import { Doc, getCurrentAccount, groupByArray, IdMap, Ref, SortingOrder, Space } from '@hcengineering/core'
|
||||
import { DocNotifyContext, InboxNotification, notificationId } from '@hcengineering/notification'
|
||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
@ -31,7 +31,8 @@
|
||||
Separator,
|
||||
TabItem,
|
||||
TabList,
|
||||
closePanel
|
||||
closePanel,
|
||||
getCurrentLocation
|
||||
} from '@hcengineering/ui'
|
||||
import view, { decodeObjectURI } from '@hcengineering/view'
|
||||
import { parseLinkId } from '@hcengineering/view-resources'
|
||||
@ -143,11 +144,23 @@
|
||||
|
||||
$: filteredData = filterData(filter, selectedTabId, inboxData, $contextByIdStore)
|
||||
|
||||
locationStore.subscribe((newLocation) => {
|
||||
void syncLocation(newLocation)
|
||||
const unsubscribeLoc = locationStore.subscribe((newLocation) => {
|
||||
void syncLocation(newLocation, $contextByDocStore)
|
||||
})
|
||||
|
||||
async function syncLocation (newLocation: Location): Promise<void> {
|
||||
let isContextsLoaded = false
|
||||
|
||||
const unsubscribeContexts = contextByDocStore.subscribe((docs) => {
|
||||
if (selectedContext !== undefined || docs.size === 0 || isContextsLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
const loc = getCurrentLocation()
|
||||
void syncLocation(loc, docs)
|
||||
isContextsLoaded = true
|
||||
})
|
||||
|
||||
async function syncLocation (newLocation: Location, contextByDoc: Map<Ref<Doc>, DocNotifyContext>): Promise<void> {
|
||||
const loc = await resolveLocation(newLocation)
|
||||
if (loc?.loc.path[2] !== notificationId) {
|
||||
return
|
||||
@ -161,7 +174,7 @@
|
||||
|
||||
const [id, _class] = decodeObjectURI(loc?.loc.path[3] ?? '')
|
||||
const _id = await parseLinkId(linkProviders, id, _class)
|
||||
const context = _id ? $contextByDocStore.get(_id) : undefined
|
||||
const context = _id ? contextByDoc.get(_id) : undefined
|
||||
|
||||
selectedContextId = context?._id
|
||||
|
||||
@ -357,7 +370,11 @@
|
||||
}
|
||||
]
|
||||
$: $deviceInfo.replacedPanel = replacedPanel
|
||||
onDestroy(() => ($deviceInfo.replacedPanel = undefined))
|
||||
onDestroy(() => {
|
||||
$deviceInfo.replacedPanel = undefined
|
||||
unsubscribeLoc()
|
||||
unsubscribeContexts()
|
||||
})
|
||||
</script>
|
||||
|
||||
<ActionContext
|
||||
|
@ -427,7 +427,8 @@ const notification = plugin(notificationId, {
|
||||
CommonNotificationCollectionRemoved: '' as IntlString,
|
||||
SoundNotificationsDescription: '' as IntlString,
|
||||
Sound: '' as IntlString,
|
||||
NoAccessToObject: '' as IntlString
|
||||
NoAccessToObject: '' as IntlString,
|
||||
ViewIn: '' as IntlString
|
||||
},
|
||||
function: {
|
||||
Notify: '' as Resource<NotifyFunc>,
|
||||
|
@ -53,7 +53,7 @@ import {
|
||||
getDocCollaborators,
|
||||
getMixinTx
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
import { markupToText, stripTags } from '@hcengineering/text'
|
||||
import { markupToHTML, markupToText, stripTags } from '@hcengineering/text'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
|
||||
@ -91,6 +91,10 @@ export async function ChatMessageTextPresenter (doc: ChatMessage): Promise<strin
|
||||
return markupToText(doc.message)
|
||||
}
|
||||
|
||||
export async function ChatMessageHtmlPresenter (doc: ChatMessage): Promise<string> {
|
||||
return markupToHTML(doc.message)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -580,6 +584,7 @@ export default async () => ({
|
||||
ChannelTextPresenter: channelTextPresenter,
|
||||
ChunterNotificationContentProvider: getChunterNotificationContent,
|
||||
ChatMessageTextPresenter,
|
||||
ChatMessageHtmlPresenter,
|
||||
JoinChannelTypeMatch
|
||||
}
|
||||
})
|
||||
|
@ -40,6 +40,7 @@ export default plugin(serverChunterId, {
|
||||
ChannelTextPresenter: '' as Resource<Presenter>,
|
||||
ChunterNotificationContentProvider: '' as Resource<NotificationContentProvider>,
|
||||
ChatMessageTextPresenter: '' as Resource<Presenter>,
|
||||
ChatMessageHtmlPresenter: '' as Resource<Presenter>,
|
||||
JoinChannelTypeMatch: '' as TypeMatchFunc
|
||||
}
|
||||
})
|
||||
|
@ -37,13 +37,15 @@
|
||||
"@types/jest": "^29.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/gmail": "^0.6.22",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/gmail": "^0.6.22"
|
||||
"@hcengineering/text": "^0.6.5"
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import serverNotification, {
|
||||
} from '@hcengineering/server-notification'
|
||||
import { getContentByTemplate } from '@hcengineering/server-notification-resources'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { ActivityMessage } from '@hcengineering/activity'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -134,7 +135,8 @@ async function notifyByEmail (
|
||||
doc: Doc | undefined,
|
||||
sender: SenderInfo,
|
||||
receiver: ReceiverInfo,
|
||||
data: InboxNotification
|
||||
data: InboxNotification,
|
||||
message?: ActivityMessage
|
||||
): Promise<void> {
|
||||
const account = receiver.account
|
||||
|
||||
@ -145,8 +147,7 @@ async function notifyByEmail (
|
||||
const senderPerson = sender.person
|
||||
const senderName = senderPerson !== undefined ? formatName(senderPerson.name, control.branding?.lastNameFirst) : ''
|
||||
|
||||
const content = await getContentByTemplate(doc, senderName, type, control, '', data)
|
||||
|
||||
const content = await getContentByTemplate(doc, senderName, type, control, '', data, message)
|
||||
if (content !== undefined) {
|
||||
await sendEmailNotification(control.ctx, content.text, content.html, content.subject, account.email)
|
||||
}
|
||||
@ -158,7 +159,8 @@ const SendEmailNotifications: NotificationProviderFunc = async (
|
||||
object: Doc,
|
||||
data: InboxNotification,
|
||||
receiver: ReceiverInfo,
|
||||
sender: SenderInfo
|
||||
sender: SenderInfo,
|
||||
message?: ActivityMessage
|
||||
): Promise<Tx[]> => {
|
||||
if (types.length === 0) {
|
||||
return []
|
||||
@ -169,7 +171,7 @@ const SendEmailNotifications: NotificationProviderFunc = async (
|
||||
}
|
||||
|
||||
for (const type of types) {
|
||||
await notifyByEmail(control, type._id, object, sender, receiver, data)
|
||||
await notifyByEmail(control, type._id, object, sender, receiver, data, message)
|
||||
}
|
||||
|
||||
return []
|
||||
|
@ -80,7 +80,7 @@ import serverNotification, {
|
||||
SenderInfo
|
||||
} from '@hcengineering/server-notification'
|
||||
import serverView from '@hcengineering/server-view'
|
||||
import { stripTags } from '@hcengineering/text'
|
||||
import { markupToText, stripTags } from '@hcengineering/text'
|
||||
import { encodeObjectURI } from '@hcengineering/view'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import webpush, { WebPushError } from 'web-push'
|
||||
@ -91,6 +91,7 @@ import {
|
||||
createPushCollaboratorsTx,
|
||||
getHTMLPresenter,
|
||||
getNotificationContent,
|
||||
getNotificationLink,
|
||||
getTextPresenter,
|
||||
getUsersInfo,
|
||||
isAllowed,
|
||||
@ -98,6 +99,7 @@ import {
|
||||
isShouldNotifyTx,
|
||||
isUserEmployeeInFieldValue,
|
||||
isUserInFieldValue,
|
||||
messageToMarkup,
|
||||
replaceAll,
|
||||
toReceiverInfo,
|
||||
updateNotifyContextsSpace
|
||||
@ -212,7 +214,8 @@ export async function getContentByTemplate (
|
||||
type: Ref<BaseNotificationType>,
|
||||
control: TriggerControl,
|
||||
data: string,
|
||||
notificationData?: InboxNotification
|
||||
notificationData?: InboxNotification,
|
||||
message?: ActivityMessage
|
||||
): Promise<Content | undefined> {
|
||||
if (doc === undefined) return
|
||||
const notificationType = control.modelDb.getObject(type)
|
||||
@ -220,11 +223,24 @@ export async function getContentByTemplate (
|
||||
|
||||
const textPart = await getTextPart(doc, control)
|
||||
if (textPart === undefined) return
|
||||
const params =
|
||||
const params: Record<string, string> =
|
||||
notificationData !== undefined
|
||||
? await getTranslatedNotificationContent(notificationData, notificationData._class, control)
|
||||
: {}
|
||||
|
||||
if (message !== undefined) {
|
||||
const markup = await messageToMarkup(control, message)
|
||||
params.message = markup !== undefined ? markupToText(markup) : params.message ?? ''
|
||||
} else if (params.message === undefined) {
|
||||
params.message = params.body ?? ''
|
||||
}
|
||||
|
||||
const link = await getNotificationLink(control, doc, message?._id)
|
||||
const app = control.branding?.title ?? 'Huly'
|
||||
const linkText = await translate(notification.string.ViewIn, { app })
|
||||
|
||||
params.link = `<a href='${link}'>${linkText}</a>`
|
||||
|
||||
const text = fillTemplate(notificationType.templates.textTemplate, sender, textPart, data, params)
|
||||
const htmlPart = await getHtmlPart(doc, control)
|
||||
const html = fillTemplate(notificationType.templates.htmlTemplate, sender, htmlPart ?? textPart, data, params)
|
||||
@ -838,16 +854,24 @@ export async function createCollabDocInfo (
|
||||
return res
|
||||
}
|
||||
|
||||
const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { objectId: object._id })
|
||||
|
||||
await updateContextsTimestamp(notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy)
|
||||
await removeContexts(notifyContexts, unsubscribe, control)
|
||||
|
||||
const docMessages = activityMessages.filter((message) => message.attachedTo === object._id)
|
||||
|
||||
if (docMessages.length === 0) {
|
||||
if (unsubscribe.length > 0) {
|
||||
const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, {
|
||||
objectId: object._id,
|
||||
user: { $in: unsubscribe }
|
||||
})
|
||||
await removeContexts(notifyContexts, unsubscribe, control)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { objectId: object._id })
|
||||
await removeContexts(notifyContexts, unsubscribe, control)
|
||||
await updateContextsTimestamp(notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy)
|
||||
|
||||
const targets = new Set(collaborators)
|
||||
|
||||
// user is not collaborator of himself, but we should notify user of changes related to users account (mentions, comments etc)
|
||||
|
@ -55,10 +55,11 @@ import serverNotification, {
|
||||
SenderInfo,
|
||||
TextPresenter
|
||||
} from '@hcengineering/server-notification'
|
||||
import { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
|
||||
import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
|
||||
import serverView from '@hcengineering/server-view'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import { encodeObjectURI } from '@hcengineering/view'
|
||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||
|
||||
import { NotifyResult } from './types'
|
||||
|
||||
@ -568,11 +569,45 @@ export async function getNotificationLink (
|
||||
id = await encodeFn(doc, control)
|
||||
}
|
||||
|
||||
let thread: string | undefined
|
||||
|
||||
if (control.hierarchy.isDerived(doc._class, activity.class.ActivityMessage)) {
|
||||
const id = (doc as ActivityMessage)._id
|
||||
|
||||
if (message === undefined) {
|
||||
message = id
|
||||
} else {
|
||||
thread = id
|
||||
}
|
||||
}
|
||||
|
||||
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
|
||||
const path = [workbenchId, 'platform', notificationId, encodeObjectURI(id, doc._class)]
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, notificationId, encodeObjectURI(id, doc._class), thread]
|
||||
.filter((x): x is string => x !== undefined)
|
||||
.map((p) => encodeURIComponent(p))
|
||||
.join('/')
|
||||
|
||||
const link = concatLink(front, path)
|
||||
|
||||
return message !== undefined ? `${link}?message=${message}` : link
|
||||
}
|
||||
|
||||
export async function messageToMarkup (control: TriggerControl, message: ActivityMessage): Promise<string | undefined> {
|
||||
const { hierarchy } = control
|
||||
if (hierarchy.isDerived(message._class, chunter.class.ChatMessage)) {
|
||||
const chatMessage = message as ChatMessage
|
||||
return chatMessage.message
|
||||
} else {
|
||||
const resource = getTextPresenter(message._class, control.hierarchy)
|
||||
|
||||
if (resource !== undefined) {
|
||||
const fn = await getResource(resource.presenter)
|
||||
const textData = await fn(message, control)
|
||||
if (textData !== undefined && textData !== '') {
|
||||
return textData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user