mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-09 09:20:54 +00:00
UBER-845: Add NotificationPresenter to send rich text notifications (#3729)
Signed-off-by: Maxim Karmatskikh <mkarmatskih@gmail.com>
This commit is contained in:
parent
a3fd97e3b4
commit
1ca9de297a
@ -362,6 +362,7 @@ specifiers:
|
|||||||
rfc6902: ^5.0.1
|
rfc6902: ^5.0.1
|
||||||
sass: ^1.53.0
|
sass: ^1.53.0
|
||||||
sass-loader: ^13.2.0
|
sass-loader: ^13.2.0
|
||||||
|
saxes: ^6.0.0
|
||||||
sharp: ~0.30.7
|
sharp: ~0.30.7
|
||||||
simplytyped: ^3.3.0
|
simplytyped: ^3.3.0
|
||||||
smartcrop: ~2.0.5
|
smartcrop: ~2.0.5
|
||||||
@ -753,6 +754,7 @@ dependencies:
|
|||||||
rfc6902: 5.0.1
|
rfc6902: 5.0.1
|
||||||
sass: 1.56.1
|
sass: 1.56.1
|
||||||
sass-loader: 13.2.0_sass@1.56.1+webpack@5.75.0
|
sass-loader: 13.2.0_sass@1.56.1+webpack@5.75.0
|
||||||
|
saxes: 6.0.0
|
||||||
sharp: 0.30.7
|
sharp: 0.30.7
|
||||||
simplytyped: 3.3.0_typescript@4.8.4
|
simplytyped: 3.3.0_typescript@4.8.4
|
||||||
smartcrop: 2.0.5
|
smartcrop: 2.0.5
|
||||||
@ -15128,6 +15130,13 @@ packages:
|
|||||||
xmlchars: 2.2.0
|
xmlchars: 2.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/saxes/6.0.0:
|
||||||
|
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||||
|
engines: {node: '>=v12.22.7'}
|
||||||
|
dependencies:
|
||||||
|
xmlchars: 2.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/scheduler/0.23.0:
|
/scheduler/0.23.0:
|
||||||
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
|
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -21263,7 +21272,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/server-tracker-resources.tgz:
|
file:projects/server-tracker-resources.tgz:
|
||||||
resolution: {integrity: sha512-t7nGyhfhEjSqEWWptyy3dts4WixZayRj/3aGr66LWM1JXQaGfBgGOTjBD1FKFJptRcAJngE3+39D6vqPOqx0VQ==, tarball: file:projects/server-tracker-resources.tgz}
|
resolution: {integrity: sha512-T7YWkcgBG3DOJu188v9G42cTL0oSlOViSqtPuG9BPbwpFiojMA859qmciaZK1tdYvqgq+4XnUgHmRCslNSEKYg==, tarball: file:projects/server-tracker-resources.tgz}
|
||||||
name: '@rush-temp/server-tracker-resources'
|
name: '@rush-temp/server-tracker-resources'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -22018,7 +22027,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
file:projects/text.tgz_a7abb371f17d2bc77abec7bc53398db5:
|
file:projects/text.tgz_a7abb371f17d2bc77abec7bc53398db5:
|
||||||
resolution: {integrity: sha512-QfLKyYxcLXLoqBMnbP4k2ND5871b+ycKCz7+NZ7xBtPtEb3Ee6jPdFrf8wbmnIYX/dLV0VThNKXcJ4ZiWxNvbA==, tarball: file:projects/text.tgz}
|
resolution: {integrity: sha512-G5lQZT9m9iBfZJlr+/pwyq5NCU5bEG+JMbxjf2npi9wSxa0/1EwWMAAk4WJ+AAKBq51GI+uAHVhXYhDEQzMHDA==, tarball: file:projects/text.tgz}
|
||||||
id: file:projects/text.tgz
|
id: file:projects/text.tgz
|
||||||
name: '@rush-temp/text'
|
name: '@rush-temp/text'
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
@ -22051,6 +22060,7 @@ packages:
|
|||||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||||
prettier: 2.8.8
|
prettier: 2.8.8
|
||||||
|
saxes: 6.0.0
|
||||||
typescript: 4.8.4
|
typescript: 4.8.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- prosemirror-keymap
|
- prosemirror-keymap
|
||||||
|
@ -41,6 +41,10 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.mixin(chunter.class.DirectMessage, core.class.Class, serverNotification.mixin.NotificationPresenter, {
|
||||||
|
presenter: serverChunter.function.ChunterNotificationContentProvider
|
||||||
|
})
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverChunter.trigger.BacklinkTrigger
|
trigger: serverChunter.trigger.BacklinkTrigger
|
||||||
})
|
})
|
||||||
|
@ -24,9 +24,11 @@ import { Resource } from '@hcengineering/platform'
|
|||||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||||
import serverNotification, {
|
import serverNotification, {
|
||||||
HTMLPresenter,
|
HTMLPresenter,
|
||||||
|
NotificationPresenter,
|
||||||
Presenter,
|
Presenter,
|
||||||
TextPresenter,
|
TextPresenter,
|
||||||
TypeMatch
|
TypeMatch,
|
||||||
|
NotificationContentProvider
|
||||||
} from '@hcengineering/server-notification'
|
} from '@hcengineering/server-notification'
|
||||||
|
|
||||||
export { serverNotificationId } from '@hcengineering/server-notification'
|
export { serverNotificationId } from '@hcengineering/server-notification'
|
||||||
@ -41,6 +43,11 @@ export class TTextPresenter extends TClass implements TextPresenter {
|
|||||||
presenter!: Resource<Presenter>
|
presenter!: Resource<Presenter>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mixin(serverNotification.mixin.NotificationPresenter, core.class.Class)
|
||||||
|
export class TNotificationPresenter extends TClass implements NotificationPresenter {
|
||||||
|
presenter!: Resource<NotificationContentProvider>
|
||||||
|
}
|
||||||
|
|
||||||
@Mixin(serverNotification.mixin.TypeMatch, notification.class.NotificationType)
|
@Mixin(serverNotification.mixin.TypeMatch, notification.class.NotificationType)
|
||||||
export class TTypeMatch extends TNotificationType implements TypeMatch {
|
export class TTypeMatch extends TNotificationType implements TypeMatch {
|
||||||
func!: Resource<
|
func!: Resource<
|
||||||
@ -49,7 +56,7 @@ export class TTypeMatch extends TNotificationType implements TypeMatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(THTMLPresenter, TTextPresenter, TTypeMatch)
|
builder.createModel(THTMLPresenter, TTextPresenter, TTypeMatch, TNotificationPresenter)
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverNotification.trigger.OnBacklinkCreate
|
trigger: serverNotification.trigger.OnBacklinkCreate
|
||||||
|
@ -32,6 +32,10 @@ export function createModel (builder: Builder): void {
|
|||||||
presenter: serverTracker.function.IssueTextPresenter
|
presenter: serverTracker.function.IssueTextPresenter
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(tracker.class.Issue, core.class.Class, serverNotification.mixin.NotificationPresenter, {
|
||||||
|
presenter: serverTracker.function.IssueNotificationContentProvider
|
||||||
|
})
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverTracker.trigger.OnIssueUpdate
|
trigger: serverTracker.trigger.OnIssueUpdate
|
||||||
})
|
})
|
||||||
|
@ -17,6 +17,7 @@ import { Extensions, getSchema } from '@tiptap/core'
|
|||||||
import { generateJSON, generateHTML } from '@tiptap/html'
|
import { generateJSON, generateHTML } from '@tiptap/html'
|
||||||
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||||
|
|
||||||
|
import { defaultExtensions } from './extensions'
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -33,3 +34,48 @@ export function parseHTML (content: string, extensions: Extensions): ProseMirror
|
|||||||
|
|
||||||
return ProseMirrorNode.fromJSON(schema, json)
|
return ProseMirrorNode.fromJSON(schema, json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ELLIPSIS_CHAR = '…'
|
||||||
|
const WHITESPACE = ' '
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function stripTags (htmlString: string, textLimit = 0, extensions: Extensions | undefined = undefined): string {
|
||||||
|
const effectiveExtensions = extensions ?? defaultExtensions
|
||||||
|
const parsed = parseHTML(htmlString, effectiveExtensions)
|
||||||
|
|
||||||
|
const textParts: string[] = []
|
||||||
|
let charCount = 0
|
||||||
|
let isHardStop = false
|
||||||
|
|
||||||
|
parsed.descendants((node, _pos, parent): boolean => {
|
||||||
|
if (isHardStop) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type.isText) {
|
||||||
|
const text = node.text ?? ''
|
||||||
|
if (textLimit > 0 && charCount + text.length > textLimit) {
|
||||||
|
const toAddCount = textLimit - charCount
|
||||||
|
const textPart = text.substring(0, toAddCount)
|
||||||
|
textParts.push(textPart)
|
||||||
|
textParts.push(ELLIPSIS_CHAR)
|
||||||
|
isHardStop = true
|
||||||
|
} else {
|
||||||
|
textParts.push(text)
|
||||||
|
charCount += text.length
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else if (node.type.isBlock) {
|
||||||
|
if (textParts.length > 0 && textParts[textParts.length - 1] !== WHITESPACE) {
|
||||||
|
textParts.push(WHITESPACE)
|
||||||
|
charCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = textParts.join('')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -76,6 +76,8 @@
|
|||||||
"LastMessage": "Last message",
|
"LastMessage": "Last message",
|
||||||
"You": "You",
|
"You": "You",
|
||||||
"YouHaveJoinedTheConversation": "You have joined the conversation",
|
"YouHaveJoinedTheConversation": "You have joined the conversation",
|
||||||
"NoMessages": "There are no messages yet"
|
"NoMessages": "There are no messages yet",
|
||||||
|
"DirectNotificationTitle": "{senderName}",
|
||||||
|
"DirectNotificationBody": "{message}"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -76,6 +76,8 @@
|
|||||||
"LastMessage": "Последнее сообщение",
|
"LastMessage": "Последнее сообщение",
|
||||||
"You": "Вы",
|
"You": "Вы",
|
||||||
"YouHaveJoinedTheConversation": "Вы присоединились к диалогу",
|
"YouHaveJoinedTheConversation": "Вы присоединились к диалогу",
|
||||||
"NoMessages": "Сообщений пока нет"
|
"NoMessages": "Сообщений пока нет",
|
||||||
|
"DirectNotificationTitle": "{senderName}",
|
||||||
|
"DirectNotificationBody": "{message}"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -182,7 +182,9 @@ export default plugin(chunterId, {
|
|||||||
Message: '' as IntlString,
|
Message: '' as IntlString,
|
||||||
MessageOn: '' as IntlString,
|
MessageOn: '' as IntlString,
|
||||||
UnarchiveConfirm: '' as IntlString,
|
UnarchiveConfirm: '' as IntlString,
|
||||||
ConvertToPrivate: '' as IntlString
|
ConvertToPrivate: '' as IntlString,
|
||||||
|
DirectNotificationTitle: '' as IntlString,
|
||||||
|
DirectNotificationBody: '' as IntlString
|
||||||
},
|
},
|
||||||
resolver: {
|
resolver: {
|
||||||
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
|
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
"People": "People",
|
"People": "People",
|
||||||
"All": "All",
|
"All": "All",
|
||||||
"Read": "Read",
|
"Read": "Read",
|
||||||
"Unread": "Unread"
|
"Unread": "Unread",
|
||||||
|
"CommonNotificationTitle": "{title}",
|
||||||
|
"CommonNotificationBody": "Updated by {senderName}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
"People": "Люди",
|
"People": "Люди",
|
||||||
"All": "Все",
|
"All": "Все",
|
||||||
"Read": "Прочитанное",
|
"Read": "Прочитанное",
|
||||||
"Unread": "Не прочитанное"
|
"Unread": "Не прочитанное",
|
||||||
|
"CommonNotificationTitle": "{title}",
|
||||||
|
"CommonNotificationBody": "Обновление от {senderName}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,6 +95,16 @@ export interface NotificationTemplate {
|
|||||||
subjectTemplate: string
|
subjectTemplate: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface NotificationContent {
|
||||||
|
title: IntlString
|
||||||
|
body: IntlString
|
||||||
|
intlParams: Record<string, string | number>
|
||||||
|
intlParamsNotLocalized?: Record<string, IntlString>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -171,6 +181,10 @@ export interface DocUpdateTx {
|
|||||||
modifiedBy: Ref<Account>
|
modifiedBy: Ref<Account>
|
||||||
modifiedOn: Timestamp
|
modifiedOn: Timestamp
|
||||||
isNew: boolean
|
isNew: boolean
|
||||||
|
title?: IntlString
|
||||||
|
body?: IntlString
|
||||||
|
intlParams?: Record<string, string | number>
|
||||||
|
intlParamsNotLocalized?: Record<string, IntlString>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -263,7 +277,9 @@ const notification = plugin(notificationId, {
|
|||||||
Notification: '' as IntlString,
|
Notification: '' as IntlString,
|
||||||
Notifications: '' as IntlString,
|
Notifications: '' as IntlString,
|
||||||
DontTrack: '' as IntlString,
|
DontTrack: '' as IntlString,
|
||||||
Inbox: '' as IntlString
|
Inbox: '' as IntlString,
|
||||||
|
CommonNotificationTitle: '' as IntlString,
|
||||||
|
CommonNotificationBody: '' as IntlString
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
GetNotificationClient: '' as Resource<NotificationClientFactoy>
|
GetNotificationClient: '' as Resource<NotificationClientFactoy>
|
||||||
|
@ -274,7 +274,13 @@
|
|||||||
"UnsetParent": "Parent issue will be unset",
|
"UnsetParent": "Parent issue will be unset",
|
||||||
"Unarchive": "Unarchive",
|
"Unarchive": "Unarchive",
|
||||||
"UnarchiveConfirm": "Do you want to unarchive project?",
|
"UnarchiveConfirm": "Do you want to unarchive project?",
|
||||||
"AllProjects": "All projects"
|
"AllProjects": "All projects",
|
||||||
|
"IssueNotificationTitle": "{issueTitle}",
|
||||||
|
"IssueNotificationBody": "Updated by {senderName}",
|
||||||
|
"IssueNotificationChanged": "{senderName} changed {property}",
|
||||||
|
"IssueNotificationChangedProperty": "{senderName} changed {property} to \"{newValue}\"",
|
||||||
|
"IssueNotificationMessage": "{senderName}: {message}",
|
||||||
|
"IssueAssigneedToYou": "Assigned to you"
|
||||||
},
|
},
|
||||||
"status": {}
|
"status": {}
|
||||||
}
|
}
|
||||||
|
@ -274,7 +274,13 @@
|
|||||||
"UnsetParent": "Родительская задача будет убрана",
|
"UnsetParent": "Родительская задача будет убрана",
|
||||||
"Unarchive": "Разархивировать",
|
"Unarchive": "Разархивировать",
|
||||||
"UnarchiveConfirm": "Вы действительно хотите разархивировать?",
|
"UnarchiveConfirm": "Вы действительно хотите разархивировать?",
|
||||||
"AllProjects": "All projects"
|
"AllProjects": "All projects",
|
||||||
|
"IssueNotificationTitle": "{issueTitle}",
|
||||||
|
"IssueNotificationBody": "Обновлено {senderName}",
|
||||||
|
"IssueNotificationChanged": "{senderName} изменил {property}",
|
||||||
|
"IssueNotificationChangedProperty": "{senderName} изменил {property} на \"{newValue}\"",
|
||||||
|
"IssueNotificationMessage": "{senderName}: {message}",
|
||||||
|
"IssueAssigneedToYou": "Назначено вам"
|
||||||
},
|
},
|
||||||
"status": {}
|
"status": {}
|
||||||
}
|
}
|
||||||
|
@ -481,7 +481,13 @@ export default plugin(trackerId, {
|
|||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
ConfigLabel: '' as IntlString,
|
ConfigLabel: '' as IntlString,
|
||||||
NewRelatedIssue: '' as IntlString
|
NewRelatedIssue: '' as IntlString,
|
||||||
|
IssueNotificationTitle: '' as IntlString,
|
||||||
|
IssueNotificationBody: '' as IntlString,
|
||||||
|
IssueNotificationChanged: '' as IntlString,
|
||||||
|
IssueNotificationChangedProperty: '' as IntlString,
|
||||||
|
IssueNotificationMessage: '' as IntlString,
|
||||||
|
IssueAssigneedToYou: '' as IntlString
|
||||||
},
|
},
|
||||||
mixin: {
|
mixin: {
|
||||||
ProjectIssueTargetOptions: '' as Ref<Mixin<ProjectIssueTargetOptions>>
|
ProjectIssueTargetOptions: '' as Ref<Mixin<ProjectIssueTargetOptions>>
|
||||||
|
@ -43,11 +43,12 @@ import core, {
|
|||||||
TxRemoveDoc,
|
TxRemoveDoc,
|
||||||
TxUpdateDoc
|
TxUpdateDoc
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, { Collaborators, NotificationType } from '@hcengineering/notification'
|
import notification, { Collaborators, NotificationType, NotificationContent } from '@hcengineering/notification'
|
||||||
import { getMetadata } from '@hcengineering/platform'
|
import { getMetadata, IntlString } from '@hcengineering/platform'
|
||||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||||
import { getDocCollaborators, getMixinTx, pushNotification } from '@hcengineering/server-notification-resources'
|
import { getDocCollaborators, getMixinTx, pushNotification } from '@hcengineering/server-notification-resources'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
|
import { stripTags } from '@hcengineering/text'
|
||||||
import { getBacklinks } from './backlinks'
|
import { getBacklinks } from './backlinks'
|
||||||
|
|
||||||
function getCreateBacklinksTxes (
|
function getCreateBacklinksTxes (
|
||||||
@ -442,7 +443,7 @@ export async function OnMessageSent (tx: Tx, control: TriggerControl): Promise<T
|
|||||||
|
|
||||||
if (anotherPerson == null) return []
|
if (anotherPerson == null) return []
|
||||||
|
|
||||||
pushNotification(control, res, sender, channel, dmCreationTx, docUpdates, anotherPerson)
|
await pushNotification(control, res, sender, channel, dmCreationTx, docUpdates, anotherPerson)
|
||||||
} else if (docUpdate.hidden) {
|
} else if (docUpdate.hidden) {
|
||||||
res.push(control.txFactory.createTxUpdateDoc(docUpdate._class, docUpdate.space, docUpdate._id, { hidden: false }))
|
res.push(control.txFactory.createTxUpdateDoc(docUpdate._class, docUpdate.space, docUpdate._id, { hidden: false }))
|
||||||
}
|
}
|
||||||
@ -536,6 +537,40 @@ export async function IsThreadMessage (
|
|||||||
return space !== undefined
|
return space !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NOTIFICATION_BODY_SIZE = 50
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function getChunterNotificationContent (
|
||||||
|
doc: Doc,
|
||||||
|
tx: TxCUD<Doc>,
|
||||||
|
target: Ref<Account>,
|
||||||
|
control: TriggerControl
|
||||||
|
): Promise<NotificationContent> {
|
||||||
|
const title: IntlString = chunter.string.DirectNotificationTitle
|
||||||
|
let body: IntlString = chunter.string.Message
|
||||||
|
const intlParams: Record<string, string | number> = {}
|
||||||
|
|
||||||
|
if (tx._class === core.class.TxCollectionCUD) {
|
||||||
|
const ptx = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||||
|
if (ptx.tx._class === core.class.TxCreateDoc) {
|
||||||
|
if (ptx.tx.objectClass === chunter.class.Message) {
|
||||||
|
const createTx = ptx.tx as TxCreateDoc<Message>
|
||||||
|
const message = createTx.attributes.content
|
||||||
|
const plainTextMessage = stripTags(message, NOTIFICATION_BODY_SIZE)
|
||||||
|
intlParams.message = plainTextMessage
|
||||||
|
body = chunter.string.DirectNotificationBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
intlParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export default async () => ({
|
export default async () => ({
|
||||||
trigger: {
|
trigger: {
|
||||||
@ -547,6 +582,7 @@ export default async () => ({
|
|||||||
CommentRemove,
|
CommentRemove,
|
||||||
ChannelHTMLPresenter: channelHTMLPresenter,
|
ChannelHTMLPresenter: channelHTMLPresenter,
|
||||||
ChannelTextPresenter: channelTextPresenter,
|
ChannelTextPresenter: channelTextPresenter,
|
||||||
|
ChunterNotificationContentProvider: getChunterNotificationContent,
|
||||||
IsDirectMessage,
|
IsDirectMessage,
|
||||||
IsThreadMessage,
|
IsThreadMessage,
|
||||||
IsMeMentioned,
|
IsMeMentioned,
|
||||||
|
@ -17,7 +17,7 @@ import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } fr
|
|||||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
import { TriggerFunc } from '@hcengineering/server-core'
|
import { TriggerFunc } from '@hcengineering/server-core'
|
||||||
import { Presenter, TypeMatchFunc } from '@hcengineering/server-notification'
|
import { Presenter, TypeMatchFunc, NotificationContentProvider } from '@hcengineering/server-notification'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -50,6 +50,7 @@ export default plugin(serverChunterId, {
|
|||||||
IsDirectMessage: '' as TypeMatchFunc,
|
IsDirectMessage: '' as TypeMatchFunc,
|
||||||
IsChannelMessage: '' as TypeMatchFunc,
|
IsChannelMessage: '' as TypeMatchFunc,
|
||||||
IsThreadMessage: '' as TypeMatchFunc,
|
IsThreadMessage: '' as TypeMatchFunc,
|
||||||
IsMeMentioned: '' as TypeMatchFunc
|
IsMeMentioned: '' as TypeMatchFunc,
|
||||||
|
ChunterNotificationContentProvider: '' as Resource<NotificationContentProvider>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -45,16 +45,18 @@ import notification, {
|
|||||||
ClassCollaborators,
|
ClassCollaborators,
|
||||||
Collaborators,
|
Collaborators,
|
||||||
DocUpdates,
|
DocUpdates,
|
||||||
|
DocUpdateTx,
|
||||||
EmailNotification,
|
EmailNotification,
|
||||||
NotificationProvider,
|
NotificationProvider,
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '@hcengineering/notification'
|
} from '@hcengineering/notification'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { IntlString, getResource } from '@hcengineering/platform'
|
||||||
import type { TriggerControl } from '@hcengineering/server-core'
|
import type { TriggerControl } from '@hcengineering/server-core'
|
||||||
import serverNotification, {
|
import serverNotification, {
|
||||||
HTMLPresenter,
|
HTMLPresenter,
|
||||||
TextPresenter,
|
TextPresenter,
|
||||||
getEmployee,
|
getEmployee,
|
||||||
|
NotificationPresenter,
|
||||||
getPersonAccount,
|
getPersonAccount,
|
||||||
getPersonAccountById
|
getPersonAccountById
|
||||||
} from '@hcengineering/server-notification'
|
} from '@hcengineering/server-notification'
|
||||||
@ -182,6 +184,10 @@ export function getTextPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy)
|
|||||||
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.TextPresenter)
|
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.TextPresenter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getNotificationPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy): NotificationPresenter | undefined {
|
||||||
|
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.NotificationPresenter)
|
||||||
|
}
|
||||||
|
|
||||||
function fillTemplate (template: string, sender: string, doc: string, data: string): string {
|
function fillTemplate (template: string, sender: string, doc: string, data: string): string {
|
||||||
let res = replaceAll(template, '{sender}', sender)
|
let res = replaceAll(template, '{sender}', sender)
|
||||||
res = replaceAll(res, '{doc}', doc)
|
res = replaceAll(res, '{doc}', doc)
|
||||||
@ -462,10 +468,43 @@ async function isShouldNotify (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function findPersonForAccount (control: TriggerControl, personId: Ref<Person>): Promise<Person | undefined> {
|
||||||
|
const persons = await control.findAll(contact.class.Person, { _id: personId })
|
||||||
|
if (persons !== undefined && persons.length > 0) {
|
||||||
|
return persons[0]
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFallbackNotificationFullfillment (
|
||||||
|
object: Doc,
|
||||||
|
originTx: TxCUD<Doc>,
|
||||||
|
control: TriggerControl
|
||||||
|
): Promise<Record<string, string | number>> {
|
||||||
|
const intlParams: Record<string, string | number> = {}
|
||||||
|
|
||||||
|
const textPresenter = getTextPresenter(object._class, control.hierarchy)
|
||||||
|
if (textPresenter !== undefined) {
|
||||||
|
const textPresenterFunc = await getResource(textPresenter.presenter)
|
||||||
|
intlParams.title = await textPresenterFunc(object, control)
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = control.modelDb.getObject(originTx.modifiedBy) as PersonAccount
|
||||||
|
if (account !== undefined) {
|
||||||
|
const senderPerson = await findPersonForAccount(control, account.person)
|
||||||
|
if (senderPerson !== undefined) {
|
||||||
|
const senderName = formatName(senderPerson.name)
|
||||||
|
intlParams.senderName = senderName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intlParams
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function pushNotification (
|
export async function pushNotification (
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
res: Tx[],
|
res: Tx[],
|
||||||
target: Ref<Account>,
|
target: Ref<Account>,
|
||||||
@ -473,7 +512,46 @@ export function pushNotification (
|
|||||||
originTx: TxCUD<Doc>,
|
originTx: TxCUD<Doc>,
|
||||||
docUpdates: DocUpdates[],
|
docUpdates: DocUpdates[],
|
||||||
modifiedBy?: Ref<Account>
|
modifiedBy?: Ref<Account>
|
||||||
): void {
|
): Promise<void> {
|
||||||
|
let title: IntlString = notification.string.CommonNotificationTitle
|
||||||
|
let body: IntlString = notification.string.CommonNotificationBody
|
||||||
|
let intlParams: Record<string, string | number> = await getFallbackNotificationFullfillment(object, originTx, control)
|
||||||
|
let intlParamsNotLocalized: Record<string, IntlString> | undefined
|
||||||
|
|
||||||
|
const notificationPresenter = getNotificationPresenter(object._class, control.hierarchy)
|
||||||
|
if (notificationPresenter !== undefined) {
|
||||||
|
const getFuillfillmentParams = await getResource(notificationPresenter.presenter)
|
||||||
|
const updateIntlParams = await getFuillfillmentParams(object, originTx, target, control)
|
||||||
|
title = updateIntlParams.title
|
||||||
|
body = updateIntlParams.body
|
||||||
|
intlParams = {
|
||||||
|
...intlParams,
|
||||||
|
...updateIntlParams.intlParams
|
||||||
|
}
|
||||||
|
if (updateIntlParams.intlParamsNotLocalized != null) {
|
||||||
|
intlParamsNotLocalized = updateIntlParams.intlParamsNotLocalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx: DocUpdateTx = {
|
||||||
|
_id: originTx._id,
|
||||||
|
modifiedOn: originTx.modifiedOn,
|
||||||
|
modifiedBy: modifiedBy ?? originTx.modifiedBy,
|
||||||
|
isNew: true
|
||||||
|
}
|
||||||
|
if (title !== undefined) {
|
||||||
|
tx.title = title
|
||||||
|
}
|
||||||
|
if (body !== undefined) {
|
||||||
|
tx.body = body
|
||||||
|
}
|
||||||
|
if (intlParams !== undefined) {
|
||||||
|
tx.intlParams = intlParams
|
||||||
|
}
|
||||||
|
if (intlParamsNotLocalized !== undefined) {
|
||||||
|
tx.intlParamsNotLocalized = intlParamsNotLocalized
|
||||||
|
}
|
||||||
|
|
||||||
const current = docUpdates.find((p) => p.user === target)
|
const current = docUpdates.find((p) => p.user === target)
|
||||||
if (current === undefined) {
|
if (current === undefined) {
|
||||||
res.push(
|
res.push(
|
||||||
@ -483,27 +561,13 @@ export function pushNotification (
|
|||||||
attachedToClass: object._class,
|
attachedToClass: object._class,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
lastTxTime: originTx.modifiedOn,
|
lastTxTime: originTx.modifiedOn,
|
||||||
txes: [
|
txes: [tx]
|
||||||
{
|
|
||||||
_id: originTx._id,
|
|
||||||
modifiedOn: originTx.modifiedOn,
|
|
||||||
modifiedBy: modifiedBy ?? originTx.modifiedBy,
|
|
||||||
isNew: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
res.push(
|
res.push(
|
||||||
control.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
|
control.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
|
||||||
$push: {
|
$push: { txes: tx }
|
||||||
txes: {
|
|
||||||
_id: originTx._id,
|
|
||||||
modifiedOn: originTx.modifiedOn,
|
|
||||||
modifiedBy: modifiedBy ?? originTx.modifiedBy,
|
|
||||||
isNew: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
res.push(
|
res.push(
|
||||||
@ -528,7 +592,7 @@ async function getNotificationTxes (
|
|||||||
const res: Tx[] = []
|
const res: Tx[] = []
|
||||||
const allowed = await isShouldNotify(control, tx, originTx, object, target, isOwn, isSpace)
|
const allowed = await isShouldNotify(control, tx, originTx, object, target, isOwn, isSpace)
|
||||||
if (allowed.allowed) {
|
if (allowed.allowed) {
|
||||||
pushNotification(control, res, target, object, originTx, docUpdates)
|
await pushNotification(control, res, target, object, originTx, docUpdates)
|
||||||
}
|
}
|
||||||
if (allowed.emails.length === 0) return res
|
if (allowed.emails.length === 0) return res
|
||||||
const acc = await getPersonAccountById(target, control)
|
const acc = await getPersonAccountById(target, control)
|
||||||
@ -711,7 +775,7 @@ async function updateCollaboratorsMixin (
|
|||||||
attachedTo: tx.objectId
|
attachedTo: tx.objectId
|
||||||
})
|
})
|
||||||
for (const collab of newCollabs) {
|
for (const collab of newCollabs) {
|
||||||
pushNotification(control, res, collab, prevDoc, originTx, docUpdates)
|
await pushNotification(control, res, collab, prevDoc, originTx, docUpdates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
|
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
|
||||||
import { Account, Class, Doc, Mixin, Ref, Tx } from '@hcengineering/core'
|
import { Account, Class, Doc, Mixin, Ref, Tx, TxCUD } from '@hcengineering/core'
|
||||||
import { NotificationType } from '@hcengineering/notification'
|
import { NotificationType, NotificationContent } from '@hcengineering/notification'
|
||||||
import { Plugin, Resource, plugin } from '@hcengineering/platform'
|
import { Plugin, Resource, plugin } from '@hcengineering/platform'
|
||||||
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
|
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
|
||||||
|
|
||||||
@ -112,6 +112,23 @@ export interface TypeMatch extends NotificationType {
|
|||||||
func: TypeMatchFunc
|
func: TypeMatchFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type NotificationContentProvider = (
|
||||||
|
doc: Doc,
|
||||||
|
tx: TxCUD<Doc>,
|
||||||
|
target: Ref<Account>,
|
||||||
|
control: TriggerControl
|
||||||
|
) => Promise<NotificationContent>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface NotificationPresenter extends Class<Doc> {
|
||||||
|
presenter: Resource<NotificationContentProvider>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -119,7 +136,8 @@ export default plugin(serverNotificationId, {
|
|||||||
mixin: {
|
mixin: {
|
||||||
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
|
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
|
||||||
TextPresenter: '' as Ref<Mixin<TextPresenter>>,
|
TextPresenter: '' as Ref<Mixin<TextPresenter>>,
|
||||||
TypeMatch: '' as Ref<Mixin<TypeMatch>>
|
TypeMatch: '' as Ref<Mixin<TypeMatch>>,
|
||||||
|
NotificationPresenter: '' as Ref<Mixin<NotificationPresenter>>
|
||||||
},
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
OnBacklinkCreate: '' as Resource<TriggerFunc>,
|
OnBacklinkCreate: '' as Resource<TriggerFunc>,
|
||||||
|
@ -31,11 +31,13 @@
|
|||||||
"@hcengineering/server-core": "^0.6.1",
|
"@hcengineering/server-core": "^0.6.1",
|
||||||
"@hcengineering/tracker": "^0.6.11",
|
"@hcengineering/tracker": "^0.6.11",
|
||||||
"@hcengineering/contact": "^0.6.19",
|
"@hcengineering/contact": "^0.6.19",
|
||||||
|
"@hcengineering/chunter": "^0.6.10",
|
||||||
"@hcengineering/notification": "^0.6.14",
|
"@hcengineering/notification": "^0.6.14",
|
||||||
"@hcengineering/task": "^0.6.11",
|
"@hcengineering/task": "^0.6.11",
|
||||||
"@hcengineering/view": "^0.6.8",
|
"@hcengineering/view": "^0.6.8",
|
||||||
"@hcengineering/login": "^0.6.7",
|
"@hcengineering/login": "^0.6.7",
|
||||||
"@hcengineering/workbench": "^0.6.8",
|
"@hcengineering/workbench": "^0.6.8",
|
||||||
"@hcengineering/server-task-resources": "^0.6.0"
|
"@hcengineering/server-task-resources": "^0.6.0",
|
||||||
|
"@hcengineering/text": "^0.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import core, {
|
import core, {
|
||||||
|
Account,
|
||||||
AttachedDoc,
|
AttachedDoc,
|
||||||
concatLink,
|
concatLink,
|
||||||
Doc,
|
Doc,
|
||||||
@ -29,11 +30,16 @@ import core, {
|
|||||||
TxUpdateDoc,
|
TxUpdateDoc,
|
||||||
WithLookup
|
WithLookup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { getMetadata } from '@hcengineering/platform'
|
import { getMetadata, IntlString } from '@hcengineering/platform'
|
||||||
|
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||||
import tracker, { Component, Issue, IssueParentInfo, TimeSpendReport, trackerId } from '@hcengineering/tracker'
|
import tracker, { Component, Issue, IssueParentInfo, TimeSpendReport, trackerId } from '@hcengineering/tracker'
|
||||||
|
import { NotificationContent } from '@hcengineering/notification'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
|
|
||||||
|
import chunter, { Comment } from '@hcengineering/chunter'
|
||||||
|
import { stripTags } from '@hcengineering/text'
|
||||||
|
|
||||||
async function updateSubIssues (
|
async function updateSubIssues (
|
||||||
updateTx: TxUpdateDoc<Issue>,
|
updateTx: TxUpdateDoc<Issue>,
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
@ -69,6 +75,83 @@ export async function issueTextPresenter (doc: Doc, control: TriggerControl): Pr
|
|||||||
return issueName
|
return issueName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSamePerson (control: TriggerControl, assignee: Ref<Person>, target: Ref<Account>): boolean {
|
||||||
|
const targetAccount = control.modelDb.getObject(target) as PersonAccount
|
||||||
|
return assignee === targetAccount?.person
|
||||||
|
}
|
||||||
|
|
||||||
|
const NOTIFICATION_BODY_SIZE = 50
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function getIssueNotificationContent (
|
||||||
|
doc: Doc,
|
||||||
|
tx: TxCUD<Doc>,
|
||||||
|
target: Ref<Account>,
|
||||||
|
control: TriggerControl
|
||||||
|
): Promise<NotificationContent> {
|
||||||
|
const issue = doc as Issue
|
||||||
|
|
||||||
|
const issueShortName = await issueTextPresenter(doc, control)
|
||||||
|
const issueTitle = `${issueShortName}: ${issue.title}`
|
||||||
|
|
||||||
|
const title = tracker.string.IssueNotificationTitle
|
||||||
|
let body = tracker.string.IssueNotificationBody
|
||||||
|
const intlParams: Record<string, string | number> = {
|
||||||
|
issueTitle
|
||||||
|
}
|
||||||
|
const intlParamsNotLocalized: Record<string, IntlString> = {}
|
||||||
|
|
||||||
|
if (tx._class === core.class.TxCollectionCUD) {
|
||||||
|
const ptx = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||||
|
|
||||||
|
if (ptx.tx._class === core.class.TxCreateDoc) {
|
||||||
|
if (ptx.tx.objectClass === chunter.class.Comment) {
|
||||||
|
const createTx = ptx.tx as TxCreateDoc<Comment>
|
||||||
|
const message = createTx.attributes.message
|
||||||
|
const plainTextMessage = stripTags(message, NOTIFICATION_BODY_SIZE)
|
||||||
|
intlParams.message = plainTextMessage
|
||||||
|
}
|
||||||
|
} else if (ptx.tx._class === core.class.TxUpdateDoc) {
|
||||||
|
const updateTx = ptx.tx as TxUpdateDoc<Issue>
|
||||||
|
|
||||||
|
if (
|
||||||
|
updateTx.operations.assignee !== null &&
|
||||||
|
updateTx.operations.assignee !== undefined &&
|
||||||
|
isSamePerson(control, updateTx.operations.assignee, target)
|
||||||
|
) {
|
||||||
|
body = tracker.string.IssueAssigneedToYou
|
||||||
|
} else {
|
||||||
|
const attributes = control.hierarchy.getAllAttributes(doc._class)
|
||||||
|
for (const attrName in updateTx.operations) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(updateTx.operations, attrName)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const attr = attributes.get(attrName)
|
||||||
|
if (attr !== null && attr !== undefined) {
|
||||||
|
intlParamsNotLocalized.property = attr.label
|
||||||
|
if (attr.type._class === core.class.TypeString) {
|
||||||
|
body = tracker.string.IssueNotificationChangedProperty
|
||||||
|
intlParams.newValue = (issue as any)[attr.name]?.toString()
|
||||||
|
} else {
|
||||||
|
body = tracker.string.IssueNotificationChanged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
intlParams,
|
||||||
|
intlParamsNotLocalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -157,7 +240,8 @@ export async function OnIssueUpdate (tx: Tx, control: TriggerControl): Promise<T
|
|||||||
export default async () => ({
|
export default async () => ({
|
||||||
function: {
|
function: {
|
||||||
IssueHTMLPresenter: issueHTMLPresenter,
|
IssueHTMLPresenter: issueHTMLPresenter,
|
||||||
IssueTextPresenter: issueTextPresenter
|
IssueTextPresenter: issueTextPresenter,
|
||||||
|
IssueNotificationContentProvider: getIssueNotificationContent
|
||||||
},
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
OnIssueUpdate,
|
OnIssueUpdate,
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
import { TriggerFunc } from '@hcengineering/server-core'
|
import { TriggerFunc } from '@hcengineering/server-core'
|
||||||
import { Presenter } from '@hcengineering/server-notification'
|
import { Presenter, NotificationContentProvider } from '@hcengineering/server-notification'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -29,7 +29,8 @@ export const serverTrackerId = 'server-tracker' as Plugin
|
|||||||
export default plugin(serverTrackerId, {
|
export default plugin(serverTrackerId, {
|
||||||
function: {
|
function: {
|
||||||
IssueHTMLPresenter: '' as Resource<Presenter>,
|
IssueHTMLPresenter: '' as Resource<Presenter>,
|
||||||
IssueTextPresenter: '' as Resource<Presenter>
|
IssueTextPresenter: '' as Resource<Presenter>,
|
||||||
|
IssueNotificationContentProvider: '' as Resource<NotificationContentProvider>
|
||||||
},
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
OnIssueUpdate: '' as Resource<TriggerFunc>,
|
OnIssueUpdate: '' as Resource<TriggerFunc>,
|
||||||
|
Loading…
Reference in New Issue
Block a user