mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
UBERF-7513: Improve notifications model to allow external notifications channels (#6037)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
0fc0115c99
commit
e4192b27fc
113
models/activity/src/actions.ts
Normal file
113
models/activity/src/actions.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import type { Builder } from '@hcengineering/model'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
|
||||
import activity from './plugin'
|
||||
|
||||
export function buildActions (builder: Builder): void {
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.AddReaction,
|
||||
label: activity.string.AddReaction,
|
||||
icon: activity.icon.Emoji,
|
||||
input: 'focus',
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
inline: true,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.AddReactionAction
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.SaveForLater,
|
||||
label: activity.string.SaveForLater,
|
||||
icon: activity.icon.Bookmark,
|
||||
input: 'focus',
|
||||
inline: true,
|
||||
actionProps: {
|
||||
size: 'x-small'
|
||||
},
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
visibilityTester: activity.function.CanSaveForLater,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.SaveForLaterAction
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.RemoveFromSaved,
|
||||
label: activity.string.RemoveFromLater,
|
||||
icon: activity.icon.BookmarkFilled,
|
||||
input: 'focus',
|
||||
inline: true,
|
||||
actionProps: {
|
||||
iconProps: {
|
||||
fill: 'var(--global-accent-TextColor)'
|
||||
}
|
||||
},
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
visibilityTester: activity.function.CanRemoveFromSaved,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.RemoveFromLaterAction
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.PinMessage,
|
||||
label: view.string.Pin,
|
||||
icon: view.icon.Pin,
|
||||
input: 'focus',
|
||||
inline: true,
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
visibilityTester: activity.function.CanPinMessage,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.PinMessageAction
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.UnpinMessage,
|
||||
label: view.string.Unpin,
|
||||
icon: view.icon.Pin,
|
||||
input: 'focus',
|
||||
inline: true,
|
||||
actionProps: {
|
||||
iconProps: {
|
||||
fill: 'var(--global-accent-TextColor)'
|
||||
}
|
||||
},
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
visibilityTester: activity.function.CanUnpinMessage,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.UnpinMessageAction
|
||||
)
|
||||
}
|
@ -67,12 +67,13 @@ import {
|
||||
} from '@hcengineering/model'
|
||||
import { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import notification from '@hcengineering/notification'
|
||||
import view from '@hcengineering/model-view'
|
||||
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
|
||||
import activity from './plugin'
|
||||
import { buildActions } from './actions'
|
||||
import { buildNotifications } from './notification'
|
||||
|
||||
export { activityId } from '@hcengineering/activity'
|
||||
export { activityOperation, migrateMessagesSpace } from './migration'
|
||||
@ -335,22 +336,10 @@ export function createModel (builder: Builder): void {
|
||||
activity.ids.ReactionAddedActivityViewlet
|
||||
)
|
||||
|
||||
builder.mixin(activity.class.ActivityMessage, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['createdBy', 'repliedPersons']
|
||||
})
|
||||
|
||||
builder.mixin(activity.class.DocUpdateMessage, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['createdBy', 'repliedPersons']
|
||||
})
|
||||
|
||||
builder.mixin(activity.class.ActivityMessage, core.class.Class, view.mixin.ObjectPanel, {
|
||||
component: view.component.AttachedDocPanel
|
||||
})
|
||||
|
||||
builder.mixin(activity.class.ActivityMessage, core.class.Class, notification.mixin.NotificationContextPresenter, {
|
||||
labelPresenter: activity.component.ActivityMessageNotificationLabel
|
||||
})
|
||||
|
||||
builder.mixin<Class<DocUpdateMessage>, IndexingConfiguration<DocUpdateMessage>>(
|
||||
activity.class.DocUpdateMessage,
|
||||
core.class.Class,
|
||||
@ -371,24 +360,6 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: false,
|
||||
generated: false,
|
||||
label: activity.string.Reactions,
|
||||
group: activity.ids.ActivityNotificationGroup,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: activity.class.Reaction,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: false
|
||||
}
|
||||
},
|
||||
activity.ids.AddReactionNotification
|
||||
)
|
||||
|
||||
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
||||
domain: DOMAIN_ACTIVITY,
|
||||
indexes: [
|
||||
@ -405,112 +376,8 @@ export function createModel (builder: Builder): void {
|
||||
]
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.AddReaction,
|
||||
label: activity.string.AddReaction,
|
||||
icon: activity.icon.Emoji,
|
||||
input: 'focus',
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
inline: true,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.AddReactionAction
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.SaveForLater,
|
||||
label: activity.string.SaveForLater,
|
||||
icon: activity.icon.Bookmark,
|
||||
input: 'focus',
|
||||
inline: true,
|
||||
actionProps: {
|
||||
size: 'x-small'
|
||||
},
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
visibilityTester: activity.function.CanSaveForLater,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.SaveForLaterAction
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.RemoveFromSaved,
|
||||
label: activity.string.RemoveFromLater,
|
||||
icon: activity.icon.BookmarkFilled,
|
||||
input: 'focus',
|
||||
inline: true,
|
||||
actionProps: {
|
||||
iconProps: {
|
||||
fill: 'var(--global-accent-TextColor)'
|
||||
}
|
||||
},
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
visibilityTester: activity.function.CanRemoveFromSaved,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.RemoveFromLaterAction
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.PinMessage,
|
||||
label: view.string.Pin,
|
||||
icon: view.icon.Pin,
|
||||
input: 'focus',
|
||||
inline: true,
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
visibilityTester: activity.function.CanPinMessage,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.PinMessageAction
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: activity.actionImpl.UnpinMessage,
|
||||
label: view.string.Unpin,
|
||||
icon: view.icon.Pin,
|
||||
input: 'focus',
|
||||
inline: true,
|
||||
actionProps: {
|
||||
iconProps: {
|
||||
fill: 'var(--global-accent-TextColor)'
|
||||
}
|
||||
},
|
||||
category: activity.category.Activity,
|
||||
target: activity.class.ActivityMessage,
|
||||
visibilityTester: activity.function.CanUnpinMessage,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
activity.ids.UnpinMessageAction
|
||||
)
|
||||
buildActions(builder)
|
||||
buildNotifications(builder)
|
||||
}
|
||||
|
||||
export default activity
|
||||
|
55
models/activity/src/notification.ts
Normal file
55
models/activity/src/notification.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import notification from '@hcengineering/notification'
|
||||
import core from '@hcengineering/core'
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
|
||||
import activity from './plugin'
|
||||
|
||||
export function buildNotifications (builder: Builder): void {
|
||||
builder.createDoc(
|
||||
notification.class.NotificationGroup,
|
||||
core.space.Model,
|
||||
{
|
||||
label: activity.string.Activity,
|
||||
icon: activity.icon.Activity
|
||||
},
|
||||
activity.ids.ActivityNotificationGroup
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: false,
|
||||
generated: false,
|
||||
label: activity.string.Reactions,
|
||||
group: activity.ids.ActivityNotificationGroup,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: activity.class.Reaction,
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{sender} reacted to {doc}: {reaction}',
|
||||
htmlTemplate: '<p><b>{sender}</b> reacted to {doc}: {reaction}</p>',
|
||||
subjectTemplate: 'Reaction on {doc}'
|
||||
}
|
||||
},
|
||||
activity.ids.AddReactionNotification
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [activity.ids.AddReactionNotification]
|
||||
})
|
||||
|
||||
builder.mixin(activity.class.ActivityMessage, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['createdBy', 'repliedPersons']
|
||||
})
|
||||
|
||||
builder.mixin(activity.class.DocUpdateMessage, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['createdBy', 'repliedPersons']
|
||||
})
|
||||
|
||||
builder.mixin(activity.class.ActivityMessage, core.class.Class, notification.mixin.NotificationContextPresenter, {
|
||||
labelPresenter: activity.component.ActivityMessageNotificationLabel
|
||||
})
|
||||
}
|
@ -231,14 +231,17 @@ export function createModel (builder: Builder): void {
|
||||
htmlTemplate: 'Reminder: {doc}',
|
||||
subjectTemplate: 'Reminder: {doc}'
|
||||
},
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.EmailNotification]: false
|
||||
}
|
||||
defaultEnabled: false
|
||||
},
|
||||
calendar.ids.ReminderNotification
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [calendar.ids.ReminderNotification]
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.DocUpdateMessageViewlet,
|
||||
core.space.Model,
|
||||
|
@ -376,15 +376,11 @@ export function createModel (builder: Builder): void {
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: chunter.class.ChatMessage,
|
||||
attachedToClass: chunter.class.DirectMessage,
|
||||
providers: {
|
||||
[notification.providers.EmailNotification]: false,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.PlatformNotification]: true
|
||||
},
|
||||
defaultEnabled: false,
|
||||
group: chunter.ids.ChunterNotificationGroup,
|
||||
templates: {
|
||||
textTemplate: '{sender} has send you a message: {doc} {data}',
|
||||
htmlTemplate: '<p><b>{sender}</b> has send you a message {doc}</p> {data}',
|
||||
textTemplate: '{sender} has sent you a message: {doc} {message}',
|
||||
htmlTemplate: '<p><b>{sender}</b> has sent you a message {doc}</p> {message}',
|
||||
subjectTemplate: 'You have new direct message in {doc}'
|
||||
}
|
||||
},
|
||||
@ -400,11 +396,14 @@ export function createModel (builder: Builder): void {
|
||||
hidden: false,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: chunter.class.ChatMessage,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true
|
||||
},
|
||||
group: chunter.ids.ChunterNotificationGroup
|
||||
attachedToClass: chunter.class.Channel,
|
||||
defaultEnabled: false,
|
||||
group: chunter.ids.ChunterNotificationGroup,
|
||||
templates: {
|
||||
textTemplate: '{sender} has sent a message in {doc}: {message}',
|
||||
htmlTemplate: '<p><b>{sender}</b> has sent a message in {doc}</p> {message}',
|
||||
subjectTemplate: 'You have new message in {doc}'
|
||||
}
|
||||
},
|
||||
chunter.ids.ChannelNotification
|
||||
)
|
||||
@ -418,11 +417,13 @@ export function createModel (builder: Builder): void {
|
||||
hidden: false,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: chunter.class.ThreadMessage,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true
|
||||
},
|
||||
group: chunter.ids.ChunterNotificationGroup
|
||||
defaultEnabled: false,
|
||||
group: chunter.ids.ChunterNotificationGroup,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
chunter.ids.ThreadNotification
|
||||
)
|
||||
@ -649,6 +650,18 @@ export function createModel (builder: Builder): void {
|
||||
filters: ['name', 'topic', 'private', 'archived', 'members'],
|
||||
strict: true
|
||||
})
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [chunter.ids.DMNotification, chunter.ids.ChannelNotification, chunter.ids.ThreadNotification]
|
||||
})
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.PushNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [chunter.ids.DMNotification, chunter.ids.ChannelNotification, chunter.ids.ThreadNotification]
|
||||
})
|
||||
}
|
||||
|
||||
export default chunter
|
||||
|
@ -34,6 +34,7 @@ import {
|
||||
import activity, { migrateMessagesSpace, DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
|
||||
import notification from '@hcengineering/notification'
|
||||
import contactPlugin, { type PersonAccount } from '@hcengineering/contact'
|
||||
import { DOMAIN_NOTIFICATION } from '@hcengineering/model-notification'
|
||||
|
||||
import chunter from './plugin'
|
||||
import { DOMAIN_CHUNTER } from './index'
|
||||
@ -187,6 +188,9 @@ async function removeOldClasses (client: MigrationClient): Promise<void> {
|
||||
|
||||
for (const _class of classes) {
|
||||
await client.deleteMany(DOMAIN_CHUNTER, { _class })
|
||||
await client.deleteMany(DOMAIN_ACTIVITY, { attachedToClass: _class })
|
||||
await client.deleteMany(DOMAIN_ACTIVITY, { objectClass: _class })
|
||||
await client.deleteMany(DOMAIN_NOTIFICATION, { attachedToClass: _class })
|
||||
await client.deleteMany(DOMAIN_TX, { objectClass: _class })
|
||||
await client.deleteMany(DOMAIN_TX, { 'tx.objectClass': _class })
|
||||
}
|
||||
@ -226,7 +230,7 @@ export const chunterOperation: MigrateOperation = {
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'remove-old-classes',
|
||||
state: 'remove-old-classes-v1',
|
||||
func: async (client) => {
|
||||
await removeOldClasses(client)
|
||||
}
|
||||
|
@ -902,9 +902,11 @@ export function defineNotifications (builder: Builder): void {
|
||||
field: 'content',
|
||||
txClasses: [core.class.TxUpdateDoc],
|
||||
objectClass: documents.class.ControlledDocument,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: false
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
documents.notification.ContentNotification
|
||||
@ -922,11 +924,7 @@ export function defineNotifications (builder: Builder): void {
|
||||
field: 'state',
|
||||
txClasses: [core.class.TxUpdateDoc],
|
||||
objectClass: documents.class.ControlledDocument,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: false,
|
||||
[notification.providers.EmailNotification]: false
|
||||
},
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{sender} changed {doc} status',
|
||||
htmlTemplate: '<p>{sender} changed {doc} status</p>',
|
||||
@ -948,11 +946,7 @@ export function defineNotifications (builder: Builder): void {
|
||||
field: 'coAuthors',
|
||||
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
||||
objectClass: documents.class.ControlledDocument,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.EmailNotification]: true
|
||||
},
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: '{sender} assigned you as a co-author of {doc}',
|
||||
htmlTemplate: '<p>{sender} assigned you as a co-author of {doc}</p>',
|
||||
@ -962,6 +956,12 @@ export function defineNotifications (builder: Builder): void {
|
||||
documents.notification.CoAuthorsNotification
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [documents.notification.StateNotification, documents.notification.ContentNotification]
|
||||
})
|
||||
|
||||
generateClassNotificationTypes(
|
||||
builder,
|
||||
documents.class.ControlledDocument,
|
||||
|
@ -461,14 +461,22 @@ function defineDocument (builder: Builder): void {
|
||||
field: 'content',
|
||||
txClasses: [core.class.TxUpdateDoc],
|
||||
objectClass: document.class.Document,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: false
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
document.ids.ContentNotification
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [document.ids.ContentNotification]
|
||||
})
|
||||
|
||||
generateClassNotificationTypes(
|
||||
builder,
|
||||
document.class.Document,
|
||||
|
@ -35,6 +35,7 @@
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/model-love": "^0.6.0",
|
||||
"@hcengineering/gmail": "^0.6.22",
|
||||
"@hcengineering/gmail-resources": "^0.6.0",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
@ -43,6 +44,7 @@
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/setting": "^0.6.17",
|
||||
"@hcengineering/ui": "^0.6.15"
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/preference": "^0.6.13"
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
|
||||
import notification from '@hcengineering/model-notification'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import setting from '@hcengineering/setting'
|
||||
import love from '@hcengineering/model-love'
|
||||
|
||||
import gmail from './plugin'
|
||||
|
||||
export { gmailId } from '@hcengineering/gmail'
|
||||
@ -234,10 +236,7 @@ export function createModel (builder: Builder): void {
|
||||
objectClass: gmail.class.Message,
|
||||
group: gmail.ids.EmailNotificationGroup,
|
||||
allowedForAuthor: true,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: false
|
||||
}
|
||||
defaultEnabled: false
|
||||
},
|
||||
gmail.ids.EmailNotification
|
||||
)
|
||||
@ -258,4 +257,36 @@ export function createModel (builder: Builder): void {
|
||||
{ createdOn: -1 }
|
||||
]
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
core.space.Model,
|
||||
{
|
||||
icon: contact.icon.Email,
|
||||
label: gmail.string.Email,
|
||||
description: gmail.string.EmailNotificationsDescription,
|
||||
defaultEnabled: true,
|
||||
canDisable: true,
|
||||
depends: notification.providers.InboxNotificationProvider,
|
||||
order: 30
|
||||
},
|
||||
gmail.providers.EmailNotificationProvider
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [gmail.ids.EmailNotification]
|
||||
})
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: gmail.providers.EmailNotificationProvider,
|
||||
ignoredTypes: [
|
||||
gmail.ids.EmailNotification,
|
||||
notification.ids.CollaboratoAddNotification,
|
||||
love.ids.InviteNotification,
|
||||
love.ids.KnockNotification
|
||||
],
|
||||
enabledTypes: []
|
||||
})
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { type Ref, type Space } from '@hcengineering/core'
|
||||
import core, { type Class, type Doc, type Ref, type Space } from '@hcengineering/core'
|
||||
import { gmailId } from '@hcengineering/gmail'
|
||||
import {
|
||||
migrateSpace,
|
||||
@ -23,6 +23,24 @@ import {
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import { DOMAIN_GMAIL } from '.'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
|
||||
|
||||
import gmail from './plugin'
|
||||
|
||||
async function migrateSettings (client: MigrationClient): Promise<void> {
|
||||
await client.update(
|
||||
DOMAIN_PREFERENCE,
|
||||
{
|
||||
_class: 'notification:class:NotificationSetting' as Ref<Class<Doc>>,
|
||||
attachedTo: 'notification:providers:EmailNotification' as Ref<Doc>
|
||||
},
|
||||
{
|
||||
_class: notification.class.NotificationTypeSetting,
|
||||
attachedTo: gmail.providers.EmailNotificationProvider
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const gmailOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
@ -32,6 +50,10 @@ export const gmailOperation: MigrateOperation = {
|
||||
func: async (client: MigrationClient) => {
|
||||
await migrateSpace(client, 'gmail:space:Gmail' as Ref<Space>, core.space.Workspace, [DOMAIN_GMAIL])
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'migrate-setting',
|
||||
func: migrateSettings
|
||||
}
|
||||
])
|
||||
},
|
||||
|
@ -483,11 +483,7 @@ export function createModel (builder: Builder): void {
|
||||
// will be created with different trigger
|
||||
txClasses: [],
|
||||
objectClass: hr.class.Request,
|
||||
providers: {
|
||||
[notification.providers.EmailNotification]: true,
|
||||
[notification.providers.BrowserNotification]: false,
|
||||
[notification.providers.PlatformNotification]: true
|
||||
},
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: 'New request: {doc}',
|
||||
htmlTemplate: 'New request: {doc}',
|
||||
@ -508,11 +504,7 @@ export function createModel (builder: Builder): void {
|
||||
// will be created with different trigger
|
||||
txClasses: [],
|
||||
objectClass: hr.class.Request,
|
||||
providers: {
|
||||
[notification.providers.EmailNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.PlatformNotification]: true
|
||||
},
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: 'Request updated: {doc}',
|
||||
htmlTemplate: 'Request updated: {doc}',
|
||||
@ -533,11 +525,7 @@ export function createModel (builder: Builder): void {
|
||||
// will be created with different trigger
|
||||
txClasses: [],
|
||||
objectClass: hr.class.Request,
|
||||
providers: {
|
||||
[notification.providers.EmailNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.PlatformNotification]: true
|
||||
},
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: 'Request removed: {doc}',
|
||||
htmlTemplate: 'Request removed: {doc}',
|
||||
@ -558,11 +546,7 @@ export function createModel (builder: Builder): void {
|
||||
// will be created with different trigger
|
||||
txClasses: [],
|
||||
objectClass: hr.class.PublicHoliday,
|
||||
providers: {
|
||||
[notification.providers.EmailNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.PlatformNotification]: true
|
||||
},
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: 'New public holiday: {doc}',
|
||||
htmlTemplate: 'New public holiday: {doc}',
|
||||
|
@ -367,11 +367,7 @@ export function createModel (builder: Builder): void {
|
||||
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
|
||||
subjectTemplate: '{doc} was assigned to you'
|
||||
},
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.EmailNotification]: true
|
||||
}
|
||||
defaultEnabled: true
|
||||
},
|
||||
lead.ids.AssigneeNotification
|
||||
)
|
||||
@ -420,9 +416,11 @@ export function createModel (builder: Builder): void {
|
||||
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
||||
objectClass: lead.class.Funnel,
|
||||
spaceSubscribe: true,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: false,
|
||||
[notification.providers.BrowserNotification]: false
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
lead.ids.LeadCreateNotification
|
||||
|
@ -187,10 +187,7 @@ export function createModel (builder: Builder): void {
|
||||
group: love.ids.LoveNotificationGroup,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: love.class.Invite,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true
|
||||
}
|
||||
defaultEnabled: true
|
||||
},
|
||||
love.ids.InviteNotification
|
||||
)
|
||||
@ -205,14 +202,18 @@ export function createModel (builder: Builder): void {
|
||||
group: love.ids.LoveNotificationGroup,
|
||||
txClasses: [],
|
||||
objectClass: love.class.JoinRequest,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.SoundNotification]: true
|
||||
}
|
||||
defaultEnabled: true
|
||||
},
|
||||
love.ids.KnockNotification
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.SoundNotificationProvider,
|
||||
excludeIgnore: [love.ids.KnockNotification],
|
||||
ignoredTypes: [],
|
||||
enabledTypes: []
|
||||
})
|
||||
|
||||
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
||||
domain: DOMAIN_LOVE,
|
||||
disabled: [{ space: 1 }, { modifiedOn: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }]
|
||||
|
@ -42,6 +42,7 @@
|
||||
"@hcengineering/setting": "^0.6.17",
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/workbench": "^0.6.16"
|
||||
"@hcengineering/workbench": "^0.6.16",
|
||||
"@hcengineering/preference": "^0.6.13"
|
||||
}
|
||||
}
|
||||
|
@ -69,15 +69,17 @@ import {
|
||||
type NotificationObjectPresenter,
|
||||
type NotificationPreferencesGroup,
|
||||
type NotificationPreview,
|
||||
type NotificationProvider,
|
||||
type NotificationSetting,
|
||||
type NotificationStatus,
|
||||
type NotificationTemplate,
|
||||
type NotificationType,
|
||||
type PushSubscription,
|
||||
type PushSubscriptionKeys
|
||||
type PushSubscriptionKeys,
|
||||
type NotificationProvider,
|
||||
type NotificationProviderSetting,
|
||||
type NotificationTypeSetting,
|
||||
type NotificationProviderDefaults
|
||||
} from '@hcengineering/notification'
|
||||
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import { type Asset, type IntlString } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { type AnyComponent, type Location } from '@hcengineering/ui/src/types'
|
||||
|
||||
@ -112,7 +114,7 @@ export class TBaseNotificationType extends TDoc implements BaseNotificationType
|
||||
generated!: boolean
|
||||
label!: IntlString
|
||||
group!: Ref<NotificationGroup>
|
||||
providers!: Record<Ref<NotificationProvider>, boolean>
|
||||
defaultEnabled!: boolean
|
||||
hidden!: boolean
|
||||
templates?: NotificationTemplate
|
||||
}
|
||||
@ -142,20 +144,19 @@ export class TNotificationPreferencesGroup extends TDoc implements NotificationP
|
||||
presenter!: AnyComponent
|
||||
}
|
||||
|
||||
@Model(notification.class.NotificationProvider, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TNotificationProvider extends TDoc implements NotificationProvider {
|
||||
label!: IntlString
|
||||
depends?: Ref<NotificationProvider>
|
||||
onChange?: Resource<(value: boolean) => Promise<boolean>>
|
||||
}
|
||||
|
||||
@Model(notification.class.NotificationSetting, preference.class.Preference)
|
||||
export class TNotificationSetting extends TPreference implements NotificationSetting {
|
||||
@Model(notification.class.NotificationTypeSetting, preference.class.Preference)
|
||||
export class TNotificationTypeSetting extends TPreference implements NotificationTypeSetting {
|
||||
declare attachedTo: Ref<TNotificationProvider>
|
||||
type!: Ref<BaseNotificationType>
|
||||
enabled!: boolean
|
||||
}
|
||||
|
||||
@Model(notification.class.NotificationProviderSetting, preference.class.Preference)
|
||||
export class TNotificationProviderSetting extends TPreference implements NotificationProviderSetting {
|
||||
declare attachedTo: Ref<TNotificationProvider>
|
||||
enabled!: boolean
|
||||
}
|
||||
|
||||
@Mixin(notification.mixin.ClassCollaborators, core.class.Class)
|
||||
export class TClassCollaborators extends TClass {
|
||||
fields!: string[]
|
||||
@ -284,6 +285,26 @@ export class TActivityNotificationViewlet extends TDoc implements ActivityNotifi
|
||||
presenter!: AnyComponent
|
||||
}
|
||||
|
||||
@Model(notification.class.NotificationProvider, core.class.Doc)
|
||||
export class TNotificationProvider extends TDoc implements NotificationProvider {
|
||||
icon!: Asset
|
||||
label!: IntlString
|
||||
description!: IntlString
|
||||
defaultEnabled!: boolean
|
||||
order!: number
|
||||
depends?: Ref<NotificationProvider>
|
||||
ignoreAll?: boolean
|
||||
canDisable!: boolean
|
||||
}
|
||||
|
||||
@Model(notification.class.NotificationProviderDefaults, core.class.Doc)
|
||||
export class TNotificationProviderDefaults extends TDoc implements NotificationProviderDefaults {
|
||||
provider!: Ref<NotificationProvider>
|
||||
excludeIgnore?: Ref<BaseNotificationType>[]
|
||||
ignoredTypes!: Ref<BaseNotificationType>[]
|
||||
enabledTypes!: Ref<BaseNotificationType>[]
|
||||
}
|
||||
|
||||
export const notificationActionTemplates = template({
|
||||
pinContext: {
|
||||
action: notification.actionImpl.PinDocNotifyContext,
|
||||
@ -311,8 +332,6 @@ export function createModel (builder: Builder): void {
|
||||
builder.createModel(
|
||||
TBrowserNotification,
|
||||
TNotificationType,
|
||||
TNotificationProvider,
|
||||
TNotificationSetting,
|
||||
TNotificationGroup,
|
||||
TNotificationPreferencesGroup,
|
||||
TClassCollaborators,
|
||||
@ -328,44 +347,11 @@ export function createModel (builder: Builder): void {
|
||||
TBaseNotificationType,
|
||||
TCommonNotificationType,
|
||||
TMentionInboxNotification,
|
||||
TPushSubscription
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.Inbox
|
||||
},
|
||||
notification.providers.PlatformNotification
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.Push,
|
||||
depends: notification.providers.PlatformNotification
|
||||
},
|
||||
notification.providers.BrowserNotification
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.Sound
|
||||
},
|
||||
notification.providers.SoundNotification
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.EmailNotification
|
||||
},
|
||||
notification.providers.EmailNotification
|
||||
TPushSubscription,
|
||||
TNotificationProvider,
|
||||
TNotificationProviderSetting,
|
||||
TNotificationTypeSetting,
|
||||
TNotificationProviderDefaults
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
@ -434,9 +420,7 @@ export function createModel (builder: Builder): void {
|
||||
group: notification.ids.NotificationGroup,
|
||||
txClasses: [],
|
||||
objectClass: notification.mixin.Collaborators,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true
|
||||
}
|
||||
defaultEnabled: true
|
||||
},
|
||||
notification.ids.CollaboratoAddNotification
|
||||
)
|
||||
@ -560,14 +544,10 @@ export function createModel (builder: Builder): void {
|
||||
generated: false,
|
||||
hidden: false,
|
||||
group: notification.ids.NotificationGroup,
|
||||
providers: {
|
||||
[notification.providers.EmailNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.PlatformNotification]: true
|
||||
},
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: '{sender} mentioned you in {doc} {data}',
|
||||
htmlTemplate: '<p>{sender}</b> mentioned you in {doc}</p> {data}',
|
||||
textTemplate: '{sender} mentioned you in {doc} {message}',
|
||||
htmlTemplate: '<p>{sender}</b> mentioned you in {doc}</p> {message}',
|
||||
subjectTemplate: 'You were mentioned in {doc}'
|
||||
}
|
||||
},
|
||||
@ -654,6 +634,7 @@ export function createModel (builder: Builder): void {
|
||||
indexes: []
|
||||
}
|
||||
)
|
||||
|
||||
builder.mixin<Class<InboxNotification>, IndexingConfiguration<InboxNotification>>(
|
||||
notification.class.InboxNotification,
|
||||
core.class.Class,
|
||||
@ -672,6 +653,73 @@ export function createModel (builder: Builder): void {
|
||||
indexes: []
|
||||
}
|
||||
)
|
||||
|
||||
builder.mixin<Class<BrowserNotification>, IndexingConfiguration<BrowserNotification>>(
|
||||
notification.class.BrowserNotification,
|
||||
core.class.Class,
|
||||
core.mixin.IndexConfiguration,
|
||||
{
|
||||
searchDisabled: true,
|
||||
indexes: []
|
||||
}
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationPreferencesGroup, core.space.Model, {
|
||||
label: notification.string.General,
|
||||
icon: notification.icon.Notifications,
|
||||
presenter: notification.component.GeneralPreferencesGroup
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
core.space.Model,
|
||||
{
|
||||
icon: notification.icon.Inbox,
|
||||
label: notification.string.Inbox,
|
||||
description: notification.string.InboxNotificationsDescription,
|
||||
defaultEnabled: true,
|
||||
canDisable: false,
|
||||
order: 10
|
||||
},
|
||||
notification.providers.InboxNotificationProvider
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
core.space.Model,
|
||||
{
|
||||
icon: notification.icon.Notifications,
|
||||
label: notification.string.Push,
|
||||
description: notification.string.PushNotificationsDescription,
|
||||
depends: notification.providers.InboxNotificationProvider,
|
||||
defaultEnabled: true,
|
||||
canDisable: true,
|
||||
order: 20
|
||||
},
|
||||
notification.providers.PushNotificationProvider
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
core.space.Model,
|
||||
{
|
||||
icon: notification.icon.Notifications,
|
||||
label: notification.string.Sound,
|
||||
description: notification.string.SoundNotificationsDescription,
|
||||
depends: notification.providers.PushNotificationProvider,
|
||||
defaultEnabled: true,
|
||||
canDisable: true,
|
||||
ignoreAll: true,
|
||||
order: 25
|
||||
},
|
||||
notification.providers.SoundNotificationProvider
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.PushNotificationProvider,
|
||||
ignoredTypes: [notification.ids.CollaboratoAddNotification],
|
||||
enabledTypes: []
|
||||
})
|
||||
}
|
||||
|
||||
export function generateClassNotificationTypes (
|
||||
@ -687,6 +735,8 @@ export function generateClassNotificationTypes (
|
||||
hierarchy.isDerived(_class, core.class.AttachedDoc) ? core.class.AttachedDoc : core.class.Doc
|
||||
)
|
||||
const filtered = Array.from(attributes.values()).filter((p) => p.hidden !== true && p.readonly !== true)
|
||||
const enabledInboxTypes: Ref<BaseNotificationType>[] = []
|
||||
|
||||
for (const attribute of filtered) {
|
||||
if (ignoreKeys.includes(attribute.name)) continue
|
||||
const isCollection: boolean = core.class.Collection === attribute.type._class
|
||||
@ -704,9 +754,11 @@ export function generateClassNotificationTypes (
|
||||
objectClass,
|
||||
txClasses,
|
||||
hidden: false,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: defaultEnabled.includes(attribute.name),
|
||||
[notification.providers.BrowserNotification]: false
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{doc} updated'
|
||||
},
|
||||
label: attribute.label
|
||||
}
|
||||
@ -715,5 +767,17 @@ export function generateClassNotificationTypes (
|
||||
}
|
||||
const id = `${notification.class.NotificationType}_${_class}_${attribute.name}` as Ref<NotificationType>
|
||||
builder.createDoc(notification.class.NotificationType, core.space.Model, data, id)
|
||||
|
||||
if (defaultEnabled.includes(attribute.name)) {
|
||||
enabledInboxTypes.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
if (enabledInboxTypes.length > 0) {
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: enabledInboxTypes
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import notification, { notificationId, type DocNotifyContext } from '@hcengineering/notification'
|
||||
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
|
||||
|
||||
import { DOMAIN_NOTIFICATION } from './index'
|
||||
|
||||
@ -67,6 +68,32 @@ export async function removeNotifications (
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateSettings (client: MigrationClient): Promise<void> {
|
||||
await client.update(
|
||||
DOMAIN_PREFERENCE,
|
||||
{
|
||||
_class: 'notification:class:NotificationSetting' as Ref<Class<Doc>>,
|
||||
attachedTo: 'notification:providers:BrowserNotification' as Ref<Doc>
|
||||
},
|
||||
{
|
||||
_class: notification.class.NotificationTypeSetting,
|
||||
attachedTo: notification.providers.PushNotificationProvider
|
||||
}
|
||||
)
|
||||
|
||||
await client.update(
|
||||
DOMAIN_PREFERENCE,
|
||||
{
|
||||
_class: 'notification:class:NotificationSetting' as Ref<Class<Doc>>,
|
||||
attachedTo: 'notification:providers:PlatformNotification' as Ref<Doc>
|
||||
},
|
||||
{
|
||||
_class: notification.class.NotificationTypeSetting,
|
||||
attachedTo: notification.providers.InboxNotificationProvider
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const notificationOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, notificationId, [
|
||||
@ -96,6 +123,10 @@ export const notificationOperation: MigrateOperation = {
|
||||
DOMAIN_NOTIFICATION
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'migrate-setting',
|
||||
func: migrateSettings
|
||||
}
|
||||
])
|
||||
},
|
||||
|
@ -118,7 +118,7 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['createdBy']
|
||||
fields: ['createdBy', 'assignee']
|
||||
})
|
||||
|
||||
builder.mixin(recruit.mixin.Candidate, core.class.Mixin, view.mixin.ObjectFactory, {
|
||||
@ -1314,11 +1314,7 @@ export function createModel (builder: Builder): void {
|
||||
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
|
||||
subjectTemplate: '{doc} was assigned to you'
|
||||
},
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.EmailNotification]: true
|
||||
}
|
||||
defaultEnabled: true
|
||||
},
|
||||
recruit.ids.AssigneeNotification
|
||||
)
|
||||
@ -1354,9 +1350,11 @@ export function createModel (builder: Builder): void {
|
||||
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
||||
objectClass: recruit.class.Applicant,
|
||||
spaceSubscribe: true,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: false,
|
||||
[notification.providers.BrowserNotification]: false
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
recruit.ids.ApplicationCreateNotification
|
||||
|
@ -135,8 +135,11 @@ export function createReviewModel (builder: Builder): void {
|
||||
group: recruit.ids.ReviewNotificationGroup,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: recruit.class.Review,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
recruit.ids.ReviewCreateNotification
|
||||
|
@ -134,11 +134,7 @@ export function createModel (builder: Builder): void {
|
||||
group: request.ids.RequestNotificationGroup,
|
||||
label: request.string.Request,
|
||||
allowedForAuthor: true,
|
||||
providers: {
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.EmailNotification]: true
|
||||
},
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: '{sender} sent you a {doc}',
|
||||
htmlTemplate: '<p><b>{sender}</b> sent you a {doc}</p>',
|
||||
|
@ -17,11 +17,17 @@ import { type Builder } from '@hcengineering/model'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import core from '@hcengineering/core/src/component'
|
||||
import serverActivity from '@hcengineering/server-activity'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
import activity from '@hcengineering/activity'
|
||||
|
||||
export { activityServerOperation } from './migration'
|
||||
export { serverActivityId } from '@hcengineering/server-activity'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.mixin(activity.class.Reaction, core.class.Class, serverNotification.mixin.NotificationPresenter, {
|
||||
presenter: serverActivity.function.ReactionNotificationContentProvider
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverActivity.trigger.OnReactionChanged,
|
||||
txMatch: {
|
||||
|
@ -24,14 +24,18 @@ import serverChunter from '@hcengineering/server-chunter'
|
||||
export { serverChunterId } from '@hcengineering/server-chunter'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.mixin(chunter.class.Channel, core.class.Class, serverNotification.mixin.HTMLPresenter, {
|
||||
builder.mixin(chunter.class.ChunterSpace, core.class.Class, serverNotification.mixin.HTMLPresenter, {
|
||||
presenter: serverChunter.function.ChannelHTMLPresenter
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.Channel, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
builder.mixin(chunter.class.ChunterSpace, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
presenter: serverChunter.function.ChannelTextPresenter
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.ChatMessage, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
presenter: serverChunter.function.ChatMessageTextPresenter
|
||||
})
|
||||
|
||||
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
||||
chunter.class.ChatMessage,
|
||||
core.class.Class,
|
||||
|
@ -34,6 +34,11 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
)
|
||||
|
||||
builder.createDoc(serverNotification.class.NotificationProviderResources, core.space.Model, {
|
||||
provider: gmail.providers.EmailNotificationProvider,
|
||||
fn: serverGmail.function.SendEmailNotifications
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverGmail.trigger.OnMessageCreate,
|
||||
txMatch: {
|
||||
|
@ -14,12 +14,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Builder, Mixin } from '@hcengineering/model'
|
||||
import { type Builder, Mixin, Model } from '@hcengineering/model'
|
||||
|
||||
import core, { type Account, type Doc, type Ref, type Tx } from '@hcengineering/core'
|
||||
import { TClass } from '@hcengineering/model-core'
|
||||
import { TClass, TDoc } from '@hcengineering/model-core'
|
||||
import { TNotificationType } from '@hcengineering/model-notification'
|
||||
import notification, { type NotificationType } from '@hcengineering/notification'
|
||||
import notification, { type NotificationProvider, type NotificationType } from '@hcengineering/notification'
|
||||
import { type Resource } from '@hcengineering/platform'
|
||||
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
|
||||
import serverNotification, {
|
||||
@ -28,7 +28,9 @@ import serverNotification, {
|
||||
type Presenter,
|
||||
type TextPresenter,
|
||||
type TypeMatch,
|
||||
type NotificationContentProvider
|
||||
type NotificationContentProvider,
|
||||
type NotificationProviderResources,
|
||||
type NotificationProviderFunc
|
||||
} from '@hcengineering/server-notification'
|
||||
|
||||
export { serverNotificationId } from '@hcengineering/server-notification'
|
||||
@ -55,8 +57,20 @@ export class TTypeMatch extends TNotificationType implements TypeMatch {
|
||||
>
|
||||
}
|
||||
|
||||
@Model(serverNotification.class.NotificationProviderResources, core.class.Doc)
|
||||
export class TNotificationProviderResources extends TDoc implements NotificationProviderResources {
|
||||
provider!: Ref<NotificationProvider>
|
||||
fn!: Resource<NotificationProviderFunc>
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(THTMLPresenter, TTextPresenter, TTypeMatch, TNotificationPresenter)
|
||||
builder.createModel(
|
||||
THTMLPresenter,
|
||||
TTextPresenter,
|
||||
TTypeMatch,
|
||||
TNotificationPresenter,
|
||||
TNotificationProviderResources
|
||||
)
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverNotification.trigger.OnActivityNotificationViewed,
|
||||
|
@ -566,10 +566,7 @@ export function createModel (builder: Builder): void {
|
||||
htmlTemplate: '<p>Integration with {doc} was disabled</p>',
|
||||
subjectTemplate: 'Integration with {doc} was disabled'
|
||||
},
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.EmailNotification]: true
|
||||
}
|
||||
defaultEnabled: true
|
||||
},
|
||||
setting.ids.IntegrationDisabledNotification
|
||||
)
|
||||
|
@ -199,13 +199,17 @@ export function createModel (builder: Builder): void {
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: telegram.class.Message,
|
||||
group: telegram.ids.NotificationGroup,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true
|
||||
}
|
||||
defaultEnabled: false
|
||||
},
|
||||
telegram.ids.NewMessageNotification
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [telegram.ids.NewMessageNotification]
|
||||
})
|
||||
|
||||
builder.mixin(telegram.class.Message, core.class.Class, core.mixin.FullTextSearchContext, {
|
||||
parentPropagate: false,
|
||||
childProcessingAllowed: true
|
||||
|
@ -369,13 +369,22 @@ export function createModel (builder: Builder): void {
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: time.class.ProjectToDo,
|
||||
onlyOwn: true,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
time.ids.ToDoCreated
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [time.ids.ToDoCreated]
|
||||
})
|
||||
|
||||
builder.mixin(time.class.ToDo, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['user']
|
||||
})
|
||||
|
@ -149,11 +149,7 @@ function defineNotifications (builder: Builder): void {
|
||||
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
|
||||
subjectTemplate: '{doc} was assigned to you'
|
||||
},
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true,
|
||||
[notification.providers.BrowserNotification]: true,
|
||||
[notification.providers.EmailNotification]: true
|
||||
}
|
||||
defaultEnabled: true
|
||||
},
|
||||
tracker.ids.AssigneeNotification
|
||||
)
|
||||
|
@ -587,10 +587,7 @@ function defineTrainingRequest (builder: Builder): void {
|
||||
group: training.notification.TrainingGroup,
|
||||
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
||||
objectClass: training.class.TrainingRequest,
|
||||
providers: {
|
||||
[notification.providers.EmailNotification]: true,
|
||||
[notification.providers.PlatformNotification]: true
|
||||
},
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: '{sender} sent you a training request {doc}',
|
||||
htmlTemplate: '<p><b>{sender}</b> sent you a training request {doc}</p>',
|
||||
|
@ -1,19 +1,26 @@
|
||||
import { type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type Asset, getMetadata } from '@hcengineering/platform'
|
||||
import { type Asset, getMetadata, getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '.'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
const sounds = new Map<Asset, AudioBufferSourceNode>()
|
||||
const context = new AudioContext()
|
||||
|
||||
export async function prepareSound (key: string, _class?: Ref<Class<Doc>>, loop = false, play = false): Promise<void> {
|
||||
const notificationType =
|
||||
_class !== undefined
|
||||
? getClient().getModel().findAllSync(notification.class.NotificationType, { objectClass: _class })
|
||||
: undefined
|
||||
const notAllowed = notificationType?.[0].providers[notification.providers.SoundNotification] === false
|
||||
if (notificationType === undefined || notAllowed) {
|
||||
return
|
||||
}
|
||||
if (_class === undefined) return
|
||||
|
||||
const client = getClient()
|
||||
const notificationType = client
|
||||
.getModel()
|
||||
.findAllSync(notification.class.NotificationType, { objectClass: _class })[0]
|
||||
|
||||
if (notificationType === undefined) return
|
||||
|
||||
const isAllowedFn = await getResource(notification.function.IsNotificationAllowed)
|
||||
const allowed: boolean = isAllowedFn(notificationType, notification.providers.SoundNotificationProvider)
|
||||
|
||||
if (!allowed) return
|
||||
|
||||
try {
|
||||
const soundUrl = getMetadata(key as Asset) as string
|
||||
const audioBuffer = await fetch(soundUrl)
|
||||
|
@ -702,6 +702,7 @@ input.search {
|
||||
.w-32 { width: 8rem; }
|
||||
.w-60 { width: 15rem; }
|
||||
.w-85 { width: 21.25rem; }
|
||||
.w-120 { width: 30rem; }
|
||||
.w-165 { width: 41.25rem; }
|
||||
.min-w-0 { min-width: 0; }
|
||||
.min-w-2 { min-width: .5rem; }
|
||||
|
@ -43,6 +43,8 @@
|
||||
"Thread": "Thread",
|
||||
"AddReaction": "Add reaction",
|
||||
"SaveForLater": "Save for later",
|
||||
"RemoveFromLater": "Remove from saved"
|
||||
"RemoveFromLater": "Remove from saved",
|
||||
"ReactionNotificationTitle": "Reaction on {title}",
|
||||
"ReactionNotificationBody": "{senderName}: {reaction}"
|
||||
}
|
||||
}
|
@ -42,6 +42,8 @@
|
||||
"Thread": "Tópico",
|
||||
"AddReaction": "Adicionar Reacción",
|
||||
"SaveForLater": "Guardar para mas tarde",
|
||||
"RemoveFromLater": "Remover de los Guardados"
|
||||
"RemoveFromLater": "Remover de los Guardados",
|
||||
"ReactionNotificationTitle": "Reacción sobre {title}",
|
||||
"ReactionNotificationBody": "{senderName}: {reaction}"
|
||||
}
|
||||
}
|
@ -43,6 +43,8 @@
|
||||
"Thread": "Fil",
|
||||
"AddReaction": "Ajouter une réaction",
|
||||
"SaveForLater": "Enregistrer pour plus tard",
|
||||
"RemoveFromLater": "Retirer des enregistrements"
|
||||
"RemoveFromLater": "Retirer des enregistrements",
|
||||
"ReactionNotificationTitle": "Réaction sur {title}",
|
||||
"ReactionNotificationBody": "{senderName}: {reaction}"
|
||||
}
|
||||
}
|
@ -42,6 +42,8 @@
|
||||
"Thread": "Tópico",
|
||||
"AddReaction": "Adicionar Reação",
|
||||
"SaveForLater": "Guardar para mais tarde",
|
||||
"RemoveFromLater": "Remover dos Guardados"
|
||||
"RemoveFromLater": "Remover dos Guardados",
|
||||
"ReactionNotificationTitle": "Reação em {title}",
|
||||
"ReactionNotificationBody": "{senderName}: {reaction}"
|
||||
}
|
||||
}
|
@ -43,6 +43,8 @@
|
||||
"Thread": "Обсуждение",
|
||||
"AddReaction": "Добавить реакцию",
|
||||
"SaveForLater": "Cохранить",
|
||||
"RemoveFromLater": "Удалить из сохраненных"
|
||||
"RemoveFromLater": "Удалить из сохраненных",
|
||||
"ReactionNotificationTitle": "Реакция на {title}",
|
||||
"ReactionNotificationBody": "{senderName}: {reaction}"
|
||||
}
|
||||
}
|
@ -43,6 +43,8 @@
|
||||
"Thread": "线程",
|
||||
"AddReaction": "添加回应",
|
||||
"SaveForLater": "稍后保存",
|
||||
"RemoveFromLater": "从保存中移除"
|
||||
"RemoveFromLater": "从保存中移除",
|
||||
"ReactionNotificationTitle": "反應於 {title}",
|
||||
"ReactionNotificationBody": "{senderName}: {reaction}"
|
||||
}
|
||||
}
|
||||
|
@ -302,7 +302,9 @@ export default plugin(activityId, {
|
||||
Mentions: '' as IntlString,
|
||||
MentionedYouIn: '' as IntlString,
|
||||
Messages: '' as IntlString,
|
||||
Thread: '' as IntlString
|
||||
Thread: '' as IntlString,
|
||||
ReactionNotificationTitle: '' as IntlString,
|
||||
ReactionNotificationBody: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
Activity: '' as AnyComponent,
|
||||
|
@ -28,6 +28,7 @@
|
||||
import IconAttachments from './icons/Attachments.svelte'
|
||||
import UploadDuo from './icons/UploadDuo.svelte'
|
||||
|
||||
export let object: Doc | undefined = undefined
|
||||
export let objectId: Ref<Doc>
|
||||
export let space: Ref<Space>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
@ -57,7 +58,7 @@
|
||||
await createAttachments(
|
||||
client,
|
||||
list,
|
||||
{ objectClass: _class, objectId, space },
|
||||
{ objectClass: object?._class ?? _class, objectId, space },
|
||||
attachmentClass,
|
||||
attachmentClassOptions
|
||||
)
|
||||
|
@ -37,6 +37,7 @@
|
||||
"NewIncomingMessage": "Sent you a new email",
|
||||
"ConfigLabel": "Email",
|
||||
"ConfigDescription": "Extension for Gmail email integration",
|
||||
"GooglePrivacy": "Huly’s use and transfer of information received from Google APIs to any other app will adhere to <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Google API Services User Data Policy</a>, including the Limited Use requirements."
|
||||
"GooglePrivacy": "Huly’s use and transfer of information received from Google APIs to any other app will adhere to <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Google API Services User Data Policy</a>, including the Limited Use requirements.",
|
||||
"EmailNotificationsDescription": "Receive personal notifications on email."
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@
|
||||
"NewIncomingMessage": "Te ha enviado un nuevo correo electrónico",
|
||||
"ConfigLabel": "Correo Electrónico",
|
||||
"ConfigDescription": "Extensión para la integración de correo electrónico de Gmail",
|
||||
"GooglePrivacy": "Huly’s use and transfer of information received from Google APIs to any other app will adhere to <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Google API Services User Data Policy</a>, including the Limited Use requirements."
|
||||
"GooglePrivacy": "Huly’s use and transfer of information received from Google APIs to any other app will adhere to <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Google API Services User Data Policy</a>, including the Limited Use requirements.",
|
||||
"EmailNotificationsDescription": "Reciba notificaciones personales por correo electrónico."
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@
|
||||
"NewIncomingMessage": "Vous a envoyé un nouvel email",
|
||||
"ConfigLabel": "Email",
|
||||
"ConfigDescription": "Extension pour l'intégration des emails Gmail",
|
||||
"GooglePrivacy": "L'utilisation et le transfert des informations reçues des API Google par Huly à toute autre application respecteront les <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">règles d'utilisation des données utilisateur des services API Google</a>, y compris les exigences d'utilisation limitée."
|
||||
"GooglePrivacy": "L'utilisation et le transfert des informations reçues des API Google par Huly à toute autre application respecteront les <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">règles d'utilisation des données utilisateur des services API Google</a>, y compris les exigences d'utilisation limitée.",
|
||||
"EmailNotificationsDescription": "Recevez des notifications personnelles par e-mail."
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@
|
||||
"NewIncomingMessage": "Recebeu um novo email",
|
||||
"ConfigLabel": "Email",
|
||||
"ConfigDescription": "Extensão para a integração de email do Gmail",
|
||||
"GooglePrivacy": "Huly’s use and transfer of information received from Google APIs to any other app will adhere to <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Google API Services User Data Policy</a>, including the Limited Use requirements."
|
||||
"GooglePrivacy": "Huly’s use and transfer of information received from Google APIs to any other app will adhere to <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Google API Services User Data Policy</a>, including the Limited Use requirements.",
|
||||
"EmailNotificationsDescription": "Receba notificações pessoais por e-mail."
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@
|
||||
"NewIncomingMessage": "Прислал вам новое сообщение",
|
||||
"ConfigLabel": "Электронная почта",
|
||||
"ConfigDescription": "Расширение по работе с Gmail электронной почтой",
|
||||
"GooglePrivacy": "Использование и передача информации, полученной Huly от Google API, будет соответствовать <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Политике использования данных пользователей Google API</a>, включая требования ограниченного использования."
|
||||
"GooglePrivacy": "Использование и передача информации, полученной Huly от Google API, будет соответствовать <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Политике использования данных пользователей Google API</a>, включая требования ограниченного использования.",
|
||||
"EmailNotificationsDescription": "Получайте персональные уведомления на электронную почту."
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@
|
||||
"NewIncomingMessage": "给您发送了一封新邮件",
|
||||
"ConfigLabel": "电子邮件",
|
||||
"ConfigDescription": "Gmail 邮件集成扩展",
|
||||
"GooglePrivacy": "Huly 从 Google API 接收的信息的使用和传输将遵守 <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Google API 服务用户数据政策</a>,包括有限使用要求。"
|
||||
"GooglePrivacy": "Huly 从 Google API 接收的信息的使用和传输将遵守 <a href=\"https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes\" target=\"_blank\">Google API 服务用户数据政策</a>,包括有限使用要求。",
|
||||
"EmailNotificationsDescription": "透過電子郵件接收個人通知。"
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
import { ChannelItem } from '@hcengineering/contact'
|
||||
import type { Account, AttachedDoc, Class, Doc, Ref, Timestamp } from '@hcengineering/core'
|
||||
import { NotificationType } from '@hcengineering/notification'
|
||||
import { NotificationProvider, NotificationType } from '@hcengineering/notification'
|
||||
import type { IntlString, Plugin } from '@hcengineering/platform'
|
||||
import { Metadata, plugin } from '@hcengineering/platform'
|
||||
import type { Handler, IntegrationType } from '@hcengineering/setting'
|
||||
@ -89,7 +89,8 @@ export default plugin(gmailId, {
|
||||
},
|
||||
string: {
|
||||
From: '' as IntlString,
|
||||
To: '' as IntlString
|
||||
To: '' as IntlString,
|
||||
EmailNotificationsDescription: '' as IntlString
|
||||
},
|
||||
integrationType: {
|
||||
Gmail: '' as Ref<IntegrationType>
|
||||
@ -108,5 +109,8 @@ export default plugin(gmailId, {
|
||||
},
|
||||
metadata: {
|
||||
GmailURL: '' as Metadata<string>
|
||||
},
|
||||
providers: {
|
||||
EmailNotificationProvider: '' as Ref<NotificationProvider>
|
||||
}
|
||||
})
|
||||
|
@ -49,6 +49,12 @@
|
||||
"Unreads": "Unreads",
|
||||
"EnablePush": "Enable push notifications",
|
||||
"NotificationBlockedInBrowser": "Notifications are blocked in your browser. Please enable notifications in your browser settings",
|
||||
"Sound": "Sound"
|
||||
"General": "General",
|
||||
"InboxNotificationsDescription": "Receive personal notifications in your Huly inbox.",
|
||||
"PushNotificationsDescription": "Receive personal notifications on desktop.",
|
||||
"CommonNotificationCollectionAdded": "{senderName} added {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} removed {collection}",
|
||||
"Sound": "Sound",
|
||||
"SoundNotificationsDescription": "Receive sound notifications for events."
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,12 @@
|
||||
"Push": "Push",
|
||||
"EnablePush": "Habilitar notificaciones push",
|
||||
"NotificationBlockedInBrowser": "Las notificaciones están bloqueadas en tu navegador. Por favor, habilita las notificaciones en la configuración de tu navegador.",
|
||||
"Sound": "Sonido"
|
||||
"General": "General",
|
||||
"InboxNotificationsDescription": "Reciba notificaciones personales en su bandeja de entrada de Huly.",
|
||||
"PushNotificationsDescription": "Reciba notificaciones personales en el escritorio.",
|
||||
"Sound": "Sonido",
|
||||
"SoundNotificationsDescription": "Reciba notificaciones de sonido para eventos.",
|
||||
"CommonNotificationCollectionAdded": "{senderName} añadió {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} eliminó {collection}"
|
||||
}
|
||||
}
|
@ -49,6 +49,12 @@
|
||||
"Unreads": "Non lus",
|
||||
"EnablePush": "Activer les notifications push",
|
||||
"NotificationBlockedInBrowser": "Les notifications sont bloquées dans votre navigateur. Veuillez activer les notifications dans les paramètres de votre navigateur",
|
||||
"Sound": "Son"
|
||||
"General": "Général",
|
||||
"InboxNotificationsDescription": "Recevez des notifications personnelles dans votre boîte de réception Huly.",
|
||||
"PushNotificationsDescription": "Recevez des notifications personnelles sur le bureau.",
|
||||
"Sound": "Son",
|
||||
"SoundNotificationsDescription": "Recevez des notifications sonores pour les événements.",
|
||||
"CommonNotificationCollectionAdded": "{senderName} a ajouté {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} a supprimé {collection}"
|
||||
}
|
||||
}
|
@ -48,6 +48,12 @@
|
||||
"Push": "Push",
|
||||
"EnablePush": "Ativar notificações push",
|
||||
"NotificationBlockedInBrowser": "Notificações bloqueadas no navegador. Por favor habilite las notificaciones en la configuración de su navegador.",
|
||||
"Sound": "Som"
|
||||
"General": "Geral",
|
||||
"InboxNotificationsDescription": "Receba notificações pessoais em sua caixa de entrada do Huly.",
|
||||
"PushNotificationsDescription": "Receba notificações pessoais no desktop.",
|
||||
"Sound": "Som",
|
||||
"SoundNotificationsDescription": "Receba notificações sonoras para eventos.",
|
||||
"CommonNotificationCollectionAdded": "{senderName} adicionou {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} removeu {collection}"
|
||||
}
|
||||
}
|
@ -49,6 +49,12 @@
|
||||
"Unreads": "Непрочитанные",
|
||||
"EnablePush": "Включить Push-уведомления",
|
||||
"NotificationBlockedInBrowser": "Уведомления заблокированы в вашем браузере. Пожалуйста, включите уведомления в настройках браузера",
|
||||
"Sound": "Звук"
|
||||
"General": "Основное",
|
||||
"InboxNotificationsDescription": "Получайте персональные уведомления на свой почтовый ящик Huly.",
|
||||
"PushNotificationsDescription": "Получайте персональные уведомления на рабочий стол.",
|
||||
"Sound": "Звук",
|
||||
"SoundNotificationsDescription": "Получайте звуковые уведомления о событиях.",
|
||||
"CommonNotificationCollectionAdded": "{senderName} добавил {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} удалил {collection}"
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,12 @@
|
||||
"Unreads": "未读",
|
||||
"EnablePush": "启用推送通知",
|
||||
"NotificationBlockedInBrowser": "通知在您的浏览器中被阻止。请在浏览器设置中启用通知",
|
||||
"Sound": "声音"
|
||||
"General": "通用",
|
||||
"InboxNotificationsDescription": "在您的 Huly 收件匣中接收個人通知。",
|
||||
"PushNotificationsDescription": "在桌面上接收個人通知。",
|
||||
"Sound": "声音",
|
||||
"SoundNotificationsDescription": "接收事件的声音通知。",
|
||||
"CommonNotificationCollectionAdded": "{senderName} 添加了 {collection}",
|
||||
"CommonNotificationCollectionRemoved": "{senderName} 移除了 {collection}"
|
||||
}
|
||||
}
|
||||
|
@ -1,158 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2023 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 core, { IdMap, Ref, toIdMap } from '@hcengineering/core'
|
||||
import type {
|
||||
BaseNotificationType,
|
||||
NotificationGroup,
|
||||
NotificationProvider,
|
||||
NotificationSetting,
|
||||
NotificationType
|
||||
} from '@hcengineering/notification'
|
||||
import { IntlString, getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Grid, Label, ToggleWithLabel } from '@hcengineering/ui'
|
||||
import notification from '../plugin'
|
||||
|
||||
export let group: Ref<NotificationGroup>
|
||||
export let settings: Map<Ref<BaseNotificationType>, NotificationSetting[]>
|
||||
|
||||
const client = getClient()
|
||||
$: types = client.getModel().findAllSync(notification.class.BaseNotificationType, { group })
|
||||
$: typesMap = toIdMap(types)
|
||||
const providers: NotificationProvider[] = client.getModel().findAllSync(notification.class.NotificationProvider, {})
|
||||
const providersMap: IdMap<NotificationProvider> = toIdMap(providers)
|
||||
|
||||
$: column = providers.length + 1
|
||||
|
||||
function getStatus (
|
||||
settings: Map<Ref<BaseNotificationType>, NotificationSetting[]>,
|
||||
type: Ref<BaseNotificationType>,
|
||||
provider: Ref<NotificationProvider>
|
||||
): boolean {
|
||||
const setting = getSetting(settings, type, provider)
|
||||
if (setting !== undefined) return setting.enabled
|
||||
const prov = providersMap.get(provider)
|
||||
if (prov === undefined) return false
|
||||
const typeValue = typesMap.get(type)
|
||||
if (typeValue === undefined) return false
|
||||
return typeValue?.providers?.[provider] ?? false
|
||||
}
|
||||
|
||||
function changeHandler (type: Ref<BaseNotificationType>, provider: Ref<NotificationProvider>): (evt: any) => void {
|
||||
return (evt: any) => {
|
||||
void change(type, provider, evt.detail)
|
||||
}
|
||||
}
|
||||
|
||||
async function change (
|
||||
typeId: Ref<BaseNotificationType>,
|
||||
providerId: Ref<NotificationProvider>,
|
||||
value: boolean
|
||||
): Promise<void> {
|
||||
const provider = providersMap.get(providerId)
|
||||
if (provider === undefined) return
|
||||
if (provider.onChange !== undefined) {
|
||||
const f = await getResource(provider.onChange)
|
||||
const res = await f(value)
|
||||
if (!res) {
|
||||
value = !value
|
||||
}
|
||||
}
|
||||
const current = getSetting(settings, typeId, providerId)
|
||||
if (current === undefined) {
|
||||
await client.createDoc(notification.class.NotificationSetting, core.space.Workspace, {
|
||||
attachedTo: providerId,
|
||||
type: typeId,
|
||||
enabled: value
|
||||
})
|
||||
} else {
|
||||
await client.update(current, {
|
||||
enabled: value
|
||||
})
|
||||
}
|
||||
if (value) {
|
||||
if (provider?.depends !== undefined) {
|
||||
const current = getStatus(settings, typeId, provider.depends)
|
||||
if (!current) {
|
||||
await change(typeId, provider.depends, true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const dependents = providers.filter((p) => p.depends === providerId)
|
||||
for (const dependent of dependents) {
|
||||
await change(typeId, dependent._id, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSetting (
|
||||
map: Map<Ref<BaseNotificationType>, NotificationSetting[]>,
|
||||
type: Ref<BaseNotificationType>,
|
||||
provider: Ref<NotificationProvider>
|
||||
): NotificationSetting | undefined {
|
||||
const typeMap = map.get(type)
|
||||
if (typeMap === undefined) return
|
||||
return typeMap.find((p) => p.attachedTo === provider)
|
||||
}
|
||||
|
||||
const isNotificationType = (type: BaseNotificationType): type is NotificationType => {
|
||||
return type._class === notification.class.NotificationType
|
||||
}
|
||||
|
||||
function getLabel (type: BaseNotificationType): IntlString {
|
||||
if (isNotificationType(type) && type.attachedToClass !== undefined) {
|
||||
return notification.string.AddedRemoved
|
||||
}
|
||||
|
||||
return notification.string.Change
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<Grid {column} columnGap={5} rowGap={1.5}>
|
||||
{#each types as type}
|
||||
<div class="flex">
|
||||
{#if type.generated}
|
||||
<Label label={getLabel(type)} />:
|
||||
{/if}
|
||||
<Label label={type.label} />
|
||||
</div>
|
||||
{#each providers as provider (provider._id)}
|
||||
{#if type.providers[provider._id] !== undefined}
|
||||
<div class="toggle">
|
||||
<ToggleWithLabel
|
||||
label={provider.label}
|
||||
on={getStatus(settings, type._id, provider._id)}
|
||||
on:change={changeHandler(type._id, provider._id)}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div />
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,105 @@
|
||||
<!--
|
||||
// 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 { getClient } from '@hcengineering/presentation'
|
||||
import notification, { NotificationProvider } from '@hcengineering/notification'
|
||||
import { Icon, Label, ModernToggle } from '@hcengineering/ui'
|
||||
import core, { Ref } from '@hcengineering/core'
|
||||
|
||||
import { providersSettings } from '../../utils'
|
||||
|
||||
const client = getClient()
|
||||
const providers = client
|
||||
.getModel()
|
||||
.findAllSync(notification.class.NotificationProvider, {})
|
||||
.sort((provider1, provider2) => provider1.order - provider2.order)
|
||||
|
||||
function getProviderStatus (ref: Ref<NotificationProvider>): boolean {
|
||||
const provider = providers.find(({ _id }) => _id === ref)
|
||||
|
||||
if (provider === undefined) return false
|
||||
|
||||
const setting = $providersSettings.find(({ attachedTo }) => attachedTo === provider._id)
|
||||
return setting?.enabled ?? provider.defaultEnabled
|
||||
}
|
||||
|
||||
async function updateStatus (ref: Ref<NotificationProvider>, enabled: boolean): Promise<void> {
|
||||
const setting = $providersSettings.find(({ attachedTo }) => attachedTo === ref)
|
||||
if (setting !== undefined) {
|
||||
await client.update(setting, { enabled })
|
||||
setting.enabled = enabled
|
||||
} else {
|
||||
await client.createDoc(notification.class.NotificationProviderSetting, core.space.Workspace, {
|
||||
attachedTo: ref,
|
||||
enabled
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function onToggle (provider: NotificationProvider): Promise<void> {
|
||||
const setting = $providersSettings.find(({ attachedTo }) => attachedTo === provider._id)
|
||||
const enabled = setting !== undefined ? !setting.enabled : !provider.defaultEnabled
|
||||
|
||||
await updateStatus(provider._id, enabled)
|
||||
|
||||
if (enabled && provider?.depends !== undefined) {
|
||||
const current = getProviderStatus(provider.depends)
|
||||
if (!current) {
|
||||
await updateStatus(provider.depends, true)
|
||||
}
|
||||
} else if (!enabled) {
|
||||
const dependents = providers.filter((p) => p.depends === provider._id)
|
||||
for (const dependent of dependents) {
|
||||
await updateStatus(dependent._id, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each providers as provider}
|
||||
{@const setting = $providersSettings.find(({ attachedTo }) => attachedTo === provider._id)}
|
||||
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<div class="flex-col flex-gap-1 mb-4 w-120">
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<Icon icon={provider.icon} size="medium" />
|
||||
<span class="label font-semi-bold">
|
||||
<Label label={provider.label} />
|
||||
</span>
|
||||
</div>
|
||||
<span class="description">
|
||||
<Label label={provider.description} />
|
||||
</span>
|
||||
</div>
|
||||
{#if provider.canDisable}
|
||||
<ModernToggle
|
||||
size="small"
|
||||
checked={setting?.enabled ?? provider.defaultEnabled}
|
||||
on:change={() => onToggle(provider)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<style lang="scss">
|
||||
.label {
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--global-secondary-TextColor);
|
||||
}
|
||||
</style>
|
@ -0,0 +1,194 @@
|
||||
<!--
|
||||
// Copyright © 2023 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 core, { IdMap, Ref, toIdMap } from '@hcengineering/core'
|
||||
import {
|
||||
BaseNotificationType,
|
||||
NotificationProvider,
|
||||
NotificationGroup,
|
||||
NotificationType,
|
||||
NotificationTypeSetting,
|
||||
NotificationProviderDefaults
|
||||
} from '@hcengineering/notification'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Grid, Label, ModernToggle } from '@hcengineering/ui'
|
||||
|
||||
import notification from '../../plugin'
|
||||
import { providersSettings } from '../../utils'
|
||||
|
||||
export let group: Ref<NotificationGroup>
|
||||
export let settings: Map<Ref<BaseNotificationType>, NotificationTypeSetting[]>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const providers: NotificationProvider[] = client
|
||||
.getModel()
|
||||
.findAllSync(notification.class.NotificationProvider, {})
|
||||
.sort((provider1, provider2) => provider1.order - provider2.order)
|
||||
const providerDefaults: NotificationProviderDefaults[] = client
|
||||
.getModel()
|
||||
.findAllSync(notification.class.NotificationProviderDefaults, {})
|
||||
const providersMap: IdMap<NotificationProvider> = toIdMap(providers)
|
||||
|
||||
$: types = client.getModel().findAllSync(notification.class.BaseNotificationType, { group })
|
||||
$: typesMap = toIdMap(types)
|
||||
|
||||
function getStatus (
|
||||
settings: Map<Ref<BaseNotificationType>, NotificationTypeSetting[]>,
|
||||
type: Ref<BaseNotificationType>,
|
||||
provider: Ref<NotificationProvider>
|
||||
): boolean {
|
||||
const setting = getTypeSetting(settings, type, provider)
|
||||
if (setting !== undefined) return setting.enabled
|
||||
const prov = providersMap.get(provider)
|
||||
if (prov === undefined) return false
|
||||
const providerEnabled = providerDefaults.some((it) => it.provider === provider && it.enabledTypes.includes(type))
|
||||
if (providerEnabled) return true
|
||||
const typeValue = typesMap.get(type)
|
||||
if (typeValue === undefined) return false
|
||||
return typeValue.defaultEnabled
|
||||
}
|
||||
|
||||
async function onToggle (
|
||||
typeId: Ref<BaseNotificationType>,
|
||||
providerId: Ref<NotificationProvider>,
|
||||
value: boolean
|
||||
): Promise<void> {
|
||||
const provider = providersMap.get(providerId)
|
||||
if (provider === undefined) return
|
||||
const currentSetting = getTypeSetting(settings, typeId, providerId)
|
||||
if (currentSetting === undefined) {
|
||||
await client.createDoc(notification.class.NotificationTypeSetting, core.space.Workspace, {
|
||||
attachedTo: providerId,
|
||||
type: typeId,
|
||||
enabled: value
|
||||
})
|
||||
} else {
|
||||
await client.update(currentSetting, {
|
||||
enabled: value
|
||||
})
|
||||
}
|
||||
if (value && provider?.depends !== undefined) {
|
||||
const current = getStatus(settings, typeId, provider.depends)
|
||||
if (!current) {
|
||||
await onToggle(typeId, provider.depends, true)
|
||||
}
|
||||
} else if (!value) {
|
||||
const dependents = providers.filter(({ depends }) => depends === providerId)
|
||||
for (const dependent of dependents) {
|
||||
await onToggle(typeId, dependent._id, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeSetting (
|
||||
map: Map<Ref<BaseNotificationType>, NotificationTypeSetting[]>,
|
||||
type: Ref<BaseNotificationType>,
|
||||
provider: Ref<NotificationProvider>
|
||||
): NotificationTypeSetting | undefined {
|
||||
const typeMap = map.get(type)
|
||||
if (typeMap === undefined) return
|
||||
return typeMap.find((p) => p.attachedTo === provider)
|
||||
}
|
||||
|
||||
const isNotificationType = (type: BaseNotificationType): type is NotificationType => {
|
||||
return type._class === notification.class.NotificationType
|
||||
}
|
||||
|
||||
function getLabel (type: BaseNotificationType): IntlString {
|
||||
if (isNotificationType(type) && type.attachedToClass !== undefined) {
|
||||
return notification.string.AddedRemoved
|
||||
}
|
||||
|
||||
return notification.string.Change
|
||||
}
|
||||
|
||||
function isIgnored (type: Ref<BaseNotificationType>, provider: NotificationProvider): boolean {
|
||||
const ignored = providerDefaults.some((it) => provider._id === it.provider && it.ignoredTypes.includes(type))
|
||||
|
||||
if (ignored) return true
|
||||
|
||||
if (provider.ignoreAll === true) {
|
||||
return !providerDefaults.some(
|
||||
(it) => provider._id === it.provider && it.excludeIgnore !== undefined && it.excludeIgnore.includes(type)
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
$: filteredProviders = providers.filter((provider) => {
|
||||
const providerSetting = $providersSettings.find((p) => p.attachedTo === provider._id)
|
||||
|
||||
if (providerSetting !== undefined && !providerSetting.enabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (providerSetting === undefined && !provider.defaultEnabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (provider.ignoreAll === true) {
|
||||
const ignoreExcluded = providerDefaults
|
||||
.map((it) => (provider._id === it.provider && it.excludeIgnore !== undefined ? it.excludeIgnore : []))
|
||||
.flat()
|
||||
|
||||
return types.some((type) => ignoreExcluded.includes(type._id))
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
$: column = filteredProviders.length + 1
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<Grid {column} columnGap={5} rowGap={1.5}>
|
||||
{#each types as type}
|
||||
<div class="flex">
|
||||
{#if type.generated}
|
||||
<Label label={getLabel(type)} />:
|
||||
{/if}
|
||||
<Label label={type.label} />
|
||||
</div>
|
||||
{#each filteredProviders as provider (provider._id)}
|
||||
{#if !isIgnored(type._id, provider)}
|
||||
{@const status = getStatus(settings, type._id, provider._id)}
|
||||
<div class="toggle">
|
||||
<ModernToggle
|
||||
size="small"
|
||||
label={provider.label}
|
||||
checked={status}
|
||||
on:change={() => onToggle(type._id, provider._id, !status)}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div />
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
@ -19,26 +19,28 @@
|
||||
BaseNotificationType,
|
||||
NotificationGroup,
|
||||
NotificationPreferencesGroup,
|
||||
NotificationSetting
|
||||
NotificationTypeSetting
|
||||
} from '@hcengineering/notification'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
Location,
|
||||
Scroller,
|
||||
getCurrentResolvedLocation,
|
||||
navigate,
|
||||
resolvedLocationStore,
|
||||
Header,
|
||||
Breadcrumb,
|
||||
defineSeparators,
|
||||
settingsSeparators,
|
||||
Separator,
|
||||
getCurrentResolvedLocation,
|
||||
Header,
|
||||
Loading,
|
||||
Location,
|
||||
navigate,
|
||||
NavItem,
|
||||
Loading
|
||||
resolvedLocationStore,
|
||||
Scroller,
|
||||
Separator,
|
||||
settingsSeparators
|
||||
} from '@hcengineering/ui'
|
||||
import notification from '../plugin'
|
||||
|
||||
import notification from '../../plugin'
|
||||
import NotificationGroupSetting from './NotificationGroupSetting.svelte'
|
||||
import { providersSettings, typesSettings } from '../../utils'
|
||||
|
||||
const client = getClient()
|
||||
const groups: NotificationGroup[] = client.getModel().findAllSync(notification.class.NotificationGroup, {})
|
||||
@ -46,13 +48,14 @@
|
||||
.getModel()
|
||||
.findAllSync(notification.class.NotificationPreferencesGroup, {})
|
||||
|
||||
let settings = new Map<Ref<BaseNotificationType>, NotificationSetting[]>()
|
||||
let settings = new Map<Ref<BaseNotificationType>, NotificationTypeSetting[]>()
|
||||
|
||||
const query = createQuery()
|
||||
let isProviderSettingLoading = true
|
||||
let isTypeSettingLoading = true
|
||||
|
||||
let loading = true
|
||||
$: loading = isProviderSettingLoading || isTypeSettingLoading
|
||||
|
||||
query.query(notification.class.NotificationSetting, {}, (res) => {
|
||||
const unsubscribeTypeSetting = typesSettings.subscribe((res) => {
|
||||
settings = new Map()
|
||||
for (const value of res) {
|
||||
const arr = settings.get(value.type) ?? []
|
||||
@ -60,7 +63,11 @@
|
||||
settings.set(value.type, arr)
|
||||
}
|
||||
settings = settings
|
||||
loading = false
|
||||
isTypeSettingLoading = false
|
||||
})
|
||||
|
||||
const unsubscribeProviderSetting = providersSettings.subscribe(() => {
|
||||
isProviderSettingLoading = false
|
||||
})
|
||||
|
||||
let group: Ref<NotificationGroup> | undefined = undefined
|
||||
@ -74,14 +81,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(
|
||||
resolvedLocationStore.subscribe((loc) => {
|
||||
void (async (loc: Location): Promise<void> => {
|
||||
group = loc.path[4] as Ref<NotificationGroup>
|
||||
currentPreferenceGroup = undefined
|
||||
})(loc)
|
||||
})
|
||||
)
|
||||
const unsubscribeLocation = resolvedLocationStore.subscribe((loc) => {
|
||||
void (async (loc: Location): Promise<void> => {
|
||||
group = loc.path[4] as Ref<NotificationGroup>
|
||||
currentPreferenceGroup = undefined
|
||||
})(loc)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribeLocation()
|
||||
unsubscribeTypeSetting()
|
||||
unsubscribeProviderSetting()
|
||||
})
|
||||
defineSeparators('notificationSettings', settingsSeparators)
|
||||
</script>
|
||||
|
||||
@ -132,7 +143,7 @@
|
||||
<div class="antiNav-space" />
|
||||
</Scroller>
|
||||
</div>
|
||||
<Separator name={'notificationSettings'} index={0} color={'var(--theme-divider-color)'} />
|
||||
<Separator name="notificationSettings" index={0} color={'var(--theme-divider-color)'} />
|
||||
<div class="hulyComponent-content__column content">
|
||||
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
|
||||
<div class="hulyComponent-content">
|
@ -17,7 +17,7 @@
|
||||
import { type Resources } from '@hcengineering/platform'
|
||||
|
||||
import Inbox from './components/inbox/Inbox.svelte'
|
||||
import NotificationSettings from './components/NotificationSettings.svelte'
|
||||
import NotificationSettings from './components/settings/NotificationSettings.svelte'
|
||||
import NotificationPresenter from './components/NotificationPresenter.svelte'
|
||||
import DocNotifyContextPresenter from './components/DocNotifyContextPresenter.svelte'
|
||||
import CollaboratorsChanged from './components/activity/CollaboratorsChanged.svelte'
|
||||
@ -25,6 +25,7 @@ import ActivityInboxNotificationPresenter from './components/inbox/ActivityInbox
|
||||
import CommonInboxNotificationPresenter from './components/inbox/CommonInboxNotificationPresenter.svelte'
|
||||
import NotificationCollaboratorsChanged from './components/NotificationCollaboratorsChanged.svelte'
|
||||
import ReactionNotificationPresenter from './components/ReactionNotificationPresenter.svelte'
|
||||
import GeneralPreferencesGroup from './components/settings/GeneralPreferencesGroup.svelte'
|
||||
import {
|
||||
unsubscribe,
|
||||
resolveLocation,
|
||||
@ -42,7 +43,8 @@ import {
|
||||
readAll,
|
||||
unreadAll,
|
||||
checkPermission,
|
||||
unarchiveContextNotifications
|
||||
unarchiveContextNotifications,
|
||||
isNotificationAllowed
|
||||
} from './utils'
|
||||
|
||||
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
||||
@ -63,7 +65,8 @@ export default async (): Promise<Resources> => ({
|
||||
ActivityInboxNotificationPresenter,
|
||||
CommonInboxNotificationPresenter,
|
||||
NotificationCollaboratorsChanged,
|
||||
ReactionNotificationPresenter
|
||||
ReactionNotificationPresenter,
|
||||
GeneralPreferencesGroup
|
||||
},
|
||||
function: {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
@ -73,7 +76,8 @@ export default async (): Promise<Resources> => ({
|
||||
CanReadNotifyContext: canReadNotifyContext,
|
||||
CanUnReadNotifyContext: canUnReadNotifyContext,
|
||||
HasInboxNotifications: hasInboxNotifications,
|
||||
CheckPushPermission: checkPermission
|
||||
CheckPushPermission: checkPermission,
|
||||
IsNotificationAllowed: isNotificationAllowed
|
||||
},
|
||||
actionImpl: {
|
||||
Unsubscribe: unsubscribe,
|
||||
|
@ -43,9 +43,13 @@ import notification, {
|
||||
type DisplayInboxNotification,
|
||||
type DocNotifyContext,
|
||||
type InboxNotification,
|
||||
type MentionInboxNotification
|
||||
type MentionInboxNotification,
|
||||
type BaseNotificationType,
|
||||
type NotificationProvider,
|
||||
type NotificationProviderSetting,
|
||||
type NotificationTypeSetting
|
||||
} from '@hcengineering/notification'
|
||||
import { MessageBox, getClient } from '@hcengineering/presentation'
|
||||
import { MessageBox, getClient, createQuery } from '@hcengineering/presentation'
|
||||
import {
|
||||
getCurrentLocation,
|
||||
getLocation,
|
||||
@ -65,6 +69,23 @@ import { getObjectLinkId } from '@hcengineering/view-resources'
|
||||
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
||||
import { type InboxData, type InboxNotificationsFilter } from './types'
|
||||
|
||||
export const providersSettings = writable<NotificationProviderSetting[]>([])
|
||||
export const typesSettings = writable<NotificationTypeSetting[]>([])
|
||||
|
||||
const providerSettingsQuery = createQuery(true)
|
||||
const typeSettingsQuery = createQuery(true)
|
||||
|
||||
export function loadNotificationSettings (): void {
|
||||
providerSettingsQuery.query(notification.class.NotificationProviderSetting, {}, (res) => {
|
||||
providersSettings.set(res)
|
||||
})
|
||||
typeSettingsQuery.query(notification.class.NotificationTypeSetting, {}, (res) => {
|
||||
typesSettings.set(res)
|
||||
})
|
||||
}
|
||||
|
||||
loadNotificationSettings()
|
||||
|
||||
export async function hasDocNotifyContextPinAction (docNotifyContext: DocNotifyContext): Promise<boolean> {
|
||||
if (docNotifyContext.hidden) {
|
||||
return false
|
||||
@ -777,3 +798,39 @@ export function notificationsComparator (notifications1: InboxNotification, noti
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
export function isNotificationAllowed (type: BaseNotificationType, providerId: Ref<NotificationProvider>): boolean {
|
||||
const client = getClient()
|
||||
const provider = client.getModel().findAllSync(notification.class.NotificationProvider, { _id: providerId })[0]
|
||||
if (provider === undefined) return false
|
||||
|
||||
const providerSetting = get(providersSettings).find((it) => it.attachedTo === providerId)
|
||||
if (providerSetting !== undefined && !providerSetting.enabled) return false
|
||||
if (providerSetting === undefined && !provider.defaultEnabled) return false
|
||||
|
||||
const providerDefaults = client.getModel().findAllSync(notification.class.NotificationProviderDefaults, {})
|
||||
|
||||
if (providerDefaults.some((it) => it.provider === provider._id && it.ignoredTypes.includes(type._id))) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (provider.ignoreAll === true) {
|
||||
const excludedIgnore = providerDefaults.some(
|
||||
(it) => provider._id === it.provider && it.excludeIgnore !== undefined && it.excludeIgnore.includes(type._id)
|
||||
)
|
||||
|
||||
if (!excludedIgnore) return false
|
||||
}
|
||||
|
||||
const setting = get(typesSettings).find((it) => it.attachedTo === provider._id && it.type === type._id)
|
||||
|
||||
if (setting !== undefined) {
|
||||
return setting.enabled
|
||||
}
|
||||
|
||||
if (providerDefaults.some((it) => it.provider === provider._id && it.enabledTypes.includes(type._id))) {
|
||||
return true
|
||||
}
|
||||
|
||||
return type.defaultEnabled
|
||||
}
|
||||
|
@ -124,8 +124,7 @@ export interface BaseNotificationType extends Doc {
|
||||
// allowed to change setting (probably we should show it, but disable toggle??)
|
||||
hidden: boolean
|
||||
group: Ref<NotificationGroup>
|
||||
// allowed providers and default value for it
|
||||
providers: Record<Ref<NotificationProvider>, boolean>
|
||||
defaultEnabled: boolean
|
||||
// templates for email (and browser/push?)
|
||||
templates?: NotificationTemplate
|
||||
}
|
||||
@ -152,19 +151,30 @@ export interface NotificationType extends BaseNotificationType {
|
||||
|
||||
export interface CommonNotificationType extends BaseNotificationType {}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface NotificationProvider extends Doc {
|
||||
label: IntlString
|
||||
description: IntlString
|
||||
icon: Asset
|
||||
defaultEnabled: boolean
|
||||
depends?: Ref<NotificationProvider>
|
||||
onChange?: Resource<(value: boolean) => Promise<boolean>>
|
||||
canDisable: boolean
|
||||
ignoreAll?: boolean
|
||||
order: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface NotificationSetting extends Preference {
|
||||
export interface NotificationProviderDefaults extends Doc {
|
||||
provider: Ref<NotificationProvider>
|
||||
excludeIgnore?: Ref<BaseNotificationType>[]
|
||||
ignoredTypes: Ref<BaseNotificationType>[]
|
||||
enabledTypes: Ref<BaseNotificationType>[]
|
||||
}
|
||||
|
||||
export interface NotificationProviderSetting extends Preference {
|
||||
attachedTo: Ref<NotificationProvider>
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface NotificationTypeSetting extends Preference {
|
||||
attachedTo: Ref<NotificationProvider>
|
||||
type: Ref<BaseNotificationType>
|
||||
enabled: boolean
|
||||
@ -329,8 +339,6 @@ const notification = plugin(notificationId, {
|
||||
BaseNotificationType: '' as Ref<Class<BaseNotificationType>>,
|
||||
NotificationType: '' as Ref<Class<NotificationType>>,
|
||||
CommonNotificationType: '' as Ref<Class<CommonNotificationType>>,
|
||||
NotificationProvider: '' as Ref<Class<NotificationProvider>>,
|
||||
NotificationSetting: '' as Ref<Class<NotificationSetting>>,
|
||||
NotificationGroup: '' as Ref<Class<NotificationGroup>>,
|
||||
NotificationPreferencesGroup: '' as Ref<Class<NotificationPreferencesGroup>>,
|
||||
DocNotifyContext: '' as Ref<Class<DocNotifyContext>>,
|
||||
@ -338,7 +346,11 @@ const notification = plugin(notificationId, {
|
||||
ActivityInboxNotification: '' as Ref<Class<ActivityInboxNotification>>,
|
||||
CommonInboxNotification: '' as Ref<Class<CommonInboxNotification>>,
|
||||
ActivityNotificationViewlet: '' as Ref<Class<ActivityNotificationViewlet>>,
|
||||
MentionInboxNotification: '' as Ref<Class<MentionInboxNotification>>
|
||||
MentionInboxNotification: '' as Ref<Class<MentionInboxNotification>>,
|
||||
NotificationProvider: '' as Ref<Class<NotificationProvider>>,
|
||||
NotificationTypeSetting: '' as Ref<Class<NotificationTypeSetting>>,
|
||||
NotificationProviderSetting: '' as Ref<Class<NotificationProviderSetting>>,
|
||||
NotificationProviderDefaults: '' as Ref<Mixin<NotificationProviderDefaults>>
|
||||
},
|
||||
ids: {
|
||||
NotificationSettings: '' as Ref<Doc>,
|
||||
@ -350,10 +362,9 @@ const notification = plugin(notificationId, {
|
||||
PushPublicKey: '' as Metadata<string>
|
||||
},
|
||||
providers: {
|
||||
PlatformNotification: '' as Ref<NotificationProvider>,
|
||||
BrowserNotification: '' as Ref<NotificationProvider>,
|
||||
EmailNotification: '' as Ref<NotificationProvider>,
|
||||
SoundNotification: '' as Ref<NotificationProvider>
|
||||
InboxNotificationProvider: '' as Ref<NotificationProvider>,
|
||||
PushNotificationProvider: '' as Ref<NotificationProvider>,
|
||||
SoundNotificationProvider: '' as Ref<NotificationProvider>
|
||||
},
|
||||
integrationType: {
|
||||
MobileApp: '' as Ref<IntegrationType>
|
||||
@ -364,7 +375,8 @@ const notification = plugin(notificationId, {
|
||||
CollaboratorsChanged: '' as AnyComponent,
|
||||
DocNotifyContextPresenter: '' as AnyComponent,
|
||||
NotificationCollaboratorsChanged: '' as AnyComponent,
|
||||
ReactionNotificationPresenter: '' as AnyComponent
|
||||
ReactionNotificationPresenter: '' as AnyComponent,
|
||||
GeneralPreferencesGroup: '' as AnyComponent
|
||||
},
|
||||
action: {
|
||||
PinDocNotifyContext: '' as Ref<Action>,
|
||||
@ -402,6 +414,12 @@ const notification = plugin(notificationId, {
|
||||
YouAddedCollaborators: '' as IntlString,
|
||||
YouRemovedCollaborators: '' as IntlString,
|
||||
Push: '' as IntlString,
|
||||
General: '' as IntlString,
|
||||
InboxNotificationsDescription: '' as IntlString,
|
||||
PushNotificationsDescription: '' as IntlString,
|
||||
CommonNotificationCollectionAdded: '' as IntlString,
|
||||
CommonNotificationCollectionRemoved: '' as IntlString,
|
||||
SoundNotificationsDescription: '' as IntlString,
|
||||
Sound: '' as IntlString
|
||||
},
|
||||
function: {
|
||||
@ -410,6 +428,9 @@ const notification = plugin(notificationId, {
|
||||
GetInboxNotificationsClient: '' as Resource<InboxNotificationsClientFactory>,
|
||||
HasInboxNotifications: '' as Resource<
|
||||
(notificationsByContext: Map<Ref<DocNotifyContext>, InboxNotification[]>) => Promise<boolean>
|
||||
>,
|
||||
IsNotificationAllowed: '' as Resource<
|
||||
(type: BaseNotificationType, providerId: Ref<NotificationProvider>) => boolean
|
||||
>
|
||||
},
|
||||
resolver: {
|
||||
|
@ -36,9 +36,12 @@ import type { TriggerControl } from '@hcengineering/server-core'
|
||||
import {
|
||||
createCollabDocInfo,
|
||||
createCollaboratorNotifications,
|
||||
getTextPresenter,
|
||||
removeDocInboxNotifications
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
import { PersonAccount } from '@hcengineering/contact'
|
||||
import { NotificationContent } from '@hcengineering/notification'
|
||||
import { getResource, translate } from '@hcengineering/platform'
|
||||
|
||||
import { getDocUpdateAction, getTxAttributesUpdates } from './utils'
|
||||
import { ReferenceTrigger } from './references'
|
||||
@ -48,11 +51,16 @@ export async function OnReactionChanged (originTx: Tx, control: TriggerControl):
|
||||
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Reaction>
|
||||
|
||||
if (innerTx._class === core.class.TxCreateDoc) {
|
||||
return await createReactionNotifications(tx, control)
|
||||
const txes = await createReactionNotifications(tx, control)
|
||||
|
||||
await control.apply(txes, true)
|
||||
return txes
|
||||
}
|
||||
|
||||
if (innerTx._class === core.class.TxRemoveDoc) {
|
||||
return await removeReactionNotifications(tx, control)
|
||||
const txes = await removeReactionNotifications(tx, control)
|
||||
await control.apply(txes, true)
|
||||
return txes
|
||||
}
|
||||
|
||||
return []
|
||||
@ -411,6 +419,36 @@ async function OnDocRemoved (originTx: TxCUD<Doc>, control: TriggerControl): Pro
|
||||
return messages.map((message) => control.txFactory.createTxRemoveDoc(message._class, message.space, message._id))
|
||||
}
|
||||
|
||||
async function ReactionNotificationContentProvider (
|
||||
doc: ActivityMessage,
|
||||
originTx: TxCUD<Doc>,
|
||||
_: Ref<Account>,
|
||||
control: TriggerControl
|
||||
): Promise<NotificationContent> {
|
||||
const tx = TxProcessor.extractTx(originTx) as TxCreateDoc<Reaction>
|
||||
const presenter = getTextPresenter(doc._class, control.hierarchy)
|
||||
const reaction = TxProcessor.createDoc2Doc(tx)
|
||||
|
||||
let text = ''
|
||||
|
||||
if (presenter !== undefined) {
|
||||
const fn = await getResource(presenter.presenter)
|
||||
|
||||
text = await fn(doc, control)
|
||||
} else {
|
||||
text = await translate(activity.string.Message, {})
|
||||
}
|
||||
|
||||
return {
|
||||
title: activity.string.ReactionNotificationTitle,
|
||||
body: activity.string.ReactionNotificationBody,
|
||||
intlParams: {
|
||||
title: text,
|
||||
reaction: reaction.emoji
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export * from './references'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
@ -420,5 +458,8 @@ export default async () => ({
|
||||
ActivityMessagesHandler,
|
||||
OnDocRemoved,
|
||||
OnReactionChanged
|
||||
},
|
||||
function: {
|
||||
ReactionNotificationContentProvider
|
||||
}
|
||||
})
|
||||
|
@ -21,6 +21,7 @@ import core, {
|
||||
CollaborativeDoc,
|
||||
Data,
|
||||
Doc,
|
||||
generateId,
|
||||
Hierarchy,
|
||||
Ref,
|
||||
Space,
|
||||
@ -35,7 +36,7 @@ import core, {
|
||||
TxUpdateDoc,
|
||||
Type
|
||||
} from '@hcengineering/core'
|
||||
import notification, { MentionInboxNotification } from '@hcengineering/notification'
|
||||
import notification, { CommonInboxNotification, MentionInboxNotification } from '@hcengineering/notification'
|
||||
import {
|
||||
extractReferences,
|
||||
markupToPmNode,
|
||||
@ -52,7 +53,8 @@ import {
|
||||
shouldNotifyCommon,
|
||||
isShouldNotifyTx,
|
||||
NotifyResult,
|
||||
createPushFromInbox
|
||||
applyNotificationProviders,
|
||||
getNotificationContent
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
|
||||
async function getPersonAccount (person: Ref<Person>, control: TriggerControl): Promise<PersonAccount | undefined> {
|
||||
@ -182,57 +184,61 @@ export async function getPersonNotificationTxes (
|
||||
const notifyResult = await shouldNotifyCommon(control, receiver._id, notification.ids.MentionCommonNotificationType)
|
||||
const messageNotifyResult = await getMessageNotifyResult(reference, receiver, control, originTx, doc)
|
||||
|
||||
if (messageNotifyResult.allowed) {
|
||||
notifyResult.allowed = false
|
||||
}
|
||||
if (messageNotifyResult.push) {
|
||||
notifyResult.push = false
|
||||
}
|
||||
if (messageNotifyResult.emails.length > 0) {
|
||||
notifyResult.emails = []
|
||||
}
|
||||
|
||||
const txes = await getCommonNotificationTxes(
|
||||
control,
|
||||
doc,
|
||||
data,
|
||||
receiverInfo,
|
||||
senderInfo,
|
||||
reference.srcDocId,
|
||||
reference.srcDocClass,
|
||||
space,
|
||||
originTx.modifiedOn,
|
||||
notifyResult,
|
||||
notification.class.MentionInboxNotification
|
||||
)
|
||||
|
||||
if (!notifyResult.allowed && notifyResult.push) {
|
||||
const exists = (
|
||||
await control.findAll(
|
||||
notification.class.ActivityInboxNotification,
|
||||
{ attachedTo: reference.attachedDocId as Ref<ActivityMessage>, user: receiver._id },
|
||||
{ limit: 1, projection: { _id: 1 } }
|
||||
)
|
||||
)[0]
|
||||
|
||||
if (exists !== undefined) {
|
||||
const pushTx = await createPushFromInbox(
|
||||
control,
|
||||
receiverInfo,
|
||||
reference.srcDocId,
|
||||
reference.srcDocClass,
|
||||
{ ...data, docNotifyContext: exists.docNotifyContext },
|
||||
notification.class.MentionInboxNotification,
|
||||
senderInfo,
|
||||
exists._id,
|
||||
new Map()
|
||||
)
|
||||
if (pushTx !== undefined) {
|
||||
res.push(pushTx)
|
||||
}
|
||||
for (const [provider] of messageNotifyResult.entries()) {
|
||||
if (notifyResult.has(provider)) {
|
||||
notifyResult.delete(provider)
|
||||
}
|
||||
}
|
||||
res.push(...txes)
|
||||
|
||||
if (notifyResult.has(notification.providers.InboxNotificationProvider)) {
|
||||
const txes = await getCommonNotificationTxes(
|
||||
control,
|
||||
doc,
|
||||
data,
|
||||
receiverInfo,
|
||||
senderInfo,
|
||||
reference.srcDocId,
|
||||
reference.srcDocClass,
|
||||
space,
|
||||
originTx.modifiedOn,
|
||||
notifyResult,
|
||||
notification.class.MentionInboxNotification
|
||||
)
|
||||
res.push(...txes)
|
||||
} else {
|
||||
const context = (
|
||||
await control.findAll(
|
||||
notification.class.DocNotifyContext,
|
||||
{ attachedTo: reference.srcDocId, user: receiver._id },
|
||||
{ projection: { _id: 1 } }
|
||||
)
|
||||
)[0]
|
||||
if (context !== undefined) {
|
||||
const content = await getNotificationContent(originTx, receiver, senderInfo, doc, control)
|
||||
const notificationData: CommonInboxNotification = {
|
||||
...data,
|
||||
...content,
|
||||
docNotifyContext: context._id,
|
||||
_id: generateId(),
|
||||
_class: notification.class.CommonInboxNotification,
|
||||
space,
|
||||
modifiedOn: originTx.modifiedOn,
|
||||
modifiedBy: sender._id
|
||||
}
|
||||
await applyNotificationProviders(
|
||||
notificationData,
|
||||
notifyResult,
|
||||
reference.srcDocId,
|
||||
reference.srcDocClass,
|
||||
control,
|
||||
res,
|
||||
doc,
|
||||
receiverInfo,
|
||||
senderInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
@ -322,17 +328,17 @@ async function getMessageNotifyResult (
|
||||
reference.attachedDocId === undefined ||
|
||||
tx._class !== core.class.TxCreateDoc
|
||||
) {
|
||||
return { allowed: false, emails: [], push: false }
|
||||
return new Map()
|
||||
}
|
||||
|
||||
const mixin = control.hierarchy.as(doc, notification.mixin.Collaborators)
|
||||
|
||||
if (mixin === undefined || !mixin.collaborators.includes(account._id)) {
|
||||
return { allowed: false, emails: [], push: false }
|
||||
return new Map()
|
||||
}
|
||||
|
||||
if (!hierarchy.isDerived(reference.attachedDocClass, activity.class.ActivityMessage)) {
|
||||
return { allowed: false, emails: [], push: false }
|
||||
return new Map()
|
||||
}
|
||||
|
||||
return await isShouldNotifyTx(control, tx, originTx, doc, account, false, false, undefined)
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
import { Plugin, Resource, plugin } from '@hcengineering/platform'
|
||||
import type { TriggerFunc } from '@hcengineering/server-core'
|
||||
import { NotificationContentProvider } from '@hcengineering/server-notification'
|
||||
|
||||
export * from './types'
|
||||
export * from './utils'
|
||||
@ -33,5 +34,8 @@ export default plugin(serverActivityId, {
|
||||
OnDocRemoved: '' as Resource<TriggerFunc>,
|
||||
OnReactionChanged: '' as Resource<TriggerFunc>,
|
||||
ReferenceTrigger: '' as Resource<TriggerFunc>
|
||||
},
|
||||
function: {
|
||||
ReactionNotificationContentProvider: '' as Resource<NotificationContentProvider>
|
||||
}
|
||||
})
|
||||
|
@ -37,17 +37,18 @@ import core, {
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import notification, { Collaborators, NotificationContent } from '@hcengineering/notification'
|
||||
import { getMetadata, IntlString } from '@hcengineering/platform'
|
||||
import { getMetadata, IntlString, translate } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import {
|
||||
createCollaboratorNotifications,
|
||||
getDocCollaborators,
|
||||
getMixinTx
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
import { stripTags } from '@hcengineering/text'
|
||||
import { markupToText, stripTags } from '@hcengineering/text'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
|
||||
import { encodeObjectURI } from '@hcengineering/view'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -55,9 +56,10 @@ import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
|
||||
export async function channelHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const channel = doc as ChunterSpace
|
||||
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
|
||||
const path = `${workbenchId}/${control.workspace.workspaceUrl}/${chunterId}/${channel._id}`
|
||||
const path = `${workbenchId}/${control.workspace.workspaceUrl}/${chunterId}/${encodeObjectURI(channel._id, channel._class)}`
|
||||
const link = concatLink(front, path)
|
||||
return `<a href='${link}'>${channel.name}</a>`
|
||||
const name = await channelTextPresenter(channel)
|
||||
return `<a href='${link}'>${name}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,9 +67,18 @@ export async function channelHTMLPresenter (doc: Doc, control: TriggerControl):
|
||||
*/
|
||||
export async function channelTextPresenter (doc: Doc): Promise<string> {
|
||||
const channel = doc as ChunterSpace
|
||||
|
||||
if (channel._class === chunter.class.DirectMessage) {
|
||||
return await translate(chunter.string.Direct, {})
|
||||
}
|
||||
|
||||
return `${channel.name}`
|
||||
}
|
||||
|
||||
export async function ChatMessageTextPresenter (doc: ChatMessage): Promise<string> {
|
||||
return markupToText(doc.message)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -456,6 +467,7 @@ export default async () => ({
|
||||
CommentRemove,
|
||||
ChannelHTMLPresenter: channelHTMLPresenter,
|
||||
ChannelTextPresenter: channelTextPresenter,
|
||||
ChunterNotificationContentProvider: getChunterNotificationContent
|
||||
ChunterNotificationContentProvider: getChunterNotificationContent,
|
||||
ChatMessageTextPresenter
|
||||
}
|
||||
})
|
||||
|
@ -37,6 +37,7 @@ export default plugin(serverChunterId, {
|
||||
CommentRemove: '' as Resource<ObjectDDParticipantFunc>,
|
||||
ChannelHTMLPresenter: '' as Resource<Presenter>,
|
||||
ChannelTextPresenter: '' as Resource<Presenter>,
|
||||
ChunterNotificationContentProvider: '' as Resource<NotificationContentProvider>
|
||||
ChunterNotificationContentProvider: '' as Resource<NotificationContentProvider>,
|
||||
ChatMessageTextPresenter: '' as Resource<Presenter>
|
||||
}
|
||||
})
|
||||
|
@ -40,6 +40,8 @@
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@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"
|
||||
|
@ -13,10 +13,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact, { Channel } from '@hcengineering/contact'
|
||||
import contact, { Channel, formatName } from '@hcengineering/contact'
|
||||
import {
|
||||
Account,
|
||||
Class,
|
||||
concatLink,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
FindOptions,
|
||||
@ -29,7 +30,10 @@ import {
|
||||
} from '@hcengineering/core'
|
||||
import gmail, { Message } from '@hcengineering/gmail'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import notification, { NotificationType } from '@hcengineering/notification'
|
||||
import notification, { BaseNotificationType, InboxNotification, NotificationType } from '@hcengineering/notification'
|
||||
import serverNotification, { NotificationProviderFunc, UserInfo } from '@hcengineering/server-notification'
|
||||
import { getContentByTemplate } from '@hcengineering/server-notification-resources'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -131,6 +135,94 @@ export async function IsIncomingMessage (
|
||||
return message.incoming && message.sendOn > (doc.createdOn ?? doc.modifiedOn)
|
||||
}
|
||||
|
||||
export async function sendEmailNotification (
|
||||
text: string,
|
||||
html: string,
|
||||
subject: string,
|
||||
receiver: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
const sesURL = getMetadata(serverNotification.metadata.SesUrl)
|
||||
if (sesURL === undefined || sesURL === '') {
|
||||
console.log('Please provide email service url to enable email confirmations.')
|
||||
return
|
||||
}
|
||||
await fetch(concatLink(sesURL, '/send'), {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
html,
|
||||
subject,
|
||||
to: [receiver]
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
console.log('Could not send email notification', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function notifyByEmail (
|
||||
control: TriggerControl,
|
||||
type: Ref<BaseNotificationType>,
|
||||
doc: Doc | undefined,
|
||||
sender: UserInfo,
|
||||
receiver: UserInfo,
|
||||
data: InboxNotification
|
||||
): Promise<void> {
|
||||
const account = receiver.account
|
||||
|
||||
if (account === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const senderPerson = sender.person
|
||||
const senderName = senderPerson !== undefined ? formatName(senderPerson.name, control.branding?.lastNameFirst) : ''
|
||||
|
||||
const content = await getContentByTemplate(doc, senderName, type, control, '', data)
|
||||
|
||||
if (content !== undefined) {
|
||||
await sendEmailNotification(content.text, content.html, content.subject, account.email)
|
||||
}
|
||||
}
|
||||
|
||||
const SendEmailNotifications: NotificationProviderFunc = async (
|
||||
control: TriggerControl,
|
||||
types: BaseNotificationType[],
|
||||
object: Doc,
|
||||
data: InboxNotification,
|
||||
receiver: UserInfo,
|
||||
sender: UserInfo
|
||||
): Promise<Tx[]> => {
|
||||
if (types.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (receiver.person === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const isEmployee = control.hierarchy.hasMixin(receiver.person, contact.mixin.Employee)
|
||||
|
||||
if (!isEmployee) {
|
||||
return []
|
||||
}
|
||||
|
||||
const employee = control.hierarchy.as(receiver.person, contact.mixin.Employee)
|
||||
|
||||
if (!employee.active) {
|
||||
return []
|
||||
}
|
||||
|
||||
for (const type of types) {
|
||||
await notifyByEmail(control, type._id, object, sender, receiver, data)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
@ -138,6 +230,7 @@ export default async () => ({
|
||||
},
|
||||
function: {
|
||||
IsIncomingMessage,
|
||||
FindMessages
|
||||
FindMessages,
|
||||
SendEmailNotifications
|
||||
}
|
||||
})
|
||||
|
@ -17,7 +17,7 @@
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { ObjectDDParticipantFunc, TriggerFunc } from '@hcengineering/server-core'
|
||||
import { TypeMatchFunc } from '@hcengineering/server-notification'
|
||||
import { NotificationProviderFunc, TypeMatchFunc } from '@hcengineering/server-notification'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -33,6 +33,7 @@ export default plugin(serverGmailId, {
|
||||
},
|
||||
function: {
|
||||
IsIncomingMessage: '' as TypeMatchFunc,
|
||||
FindMessages: '' as Resource<ObjectDDParticipantFunc>
|
||||
FindMessages: '' as Resource<ObjectDDParticipantFunc>,
|
||||
SendEmailNotifications: '' as Resource<NotificationProviderFunc>
|
||||
}
|
||||
})
|
||||
|
@ -43,6 +43,8 @@
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||
"@hcengineering/gmail": "^0.6.22",
|
||||
"@hcengineering/server-gmail-resources": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/hr": "^0.6.19"
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ import notification, { NotificationType } from '@hcengineering/notification'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import { getEmployee, getPersonAccountById } from '@hcengineering/server-notification'
|
||||
import { getContent, isAllowed, sendEmailNotification } from '@hcengineering/server-notification-resources'
|
||||
import { getContentByTemplate, isAllowed } from '@hcengineering/server-notification-resources'
|
||||
import gmail from '@hcengineering/gmail'
|
||||
import { sendEmailNotification } from '@hcengineering/server-gmail-resources'
|
||||
|
||||
async function getOldDepartment (
|
||||
currentTx: TxMixin<Employee, Staff> | TxUpdateDoc<Employee>,
|
||||
@ -248,12 +250,13 @@ export async function OnEmployeeDeactivate (tx: Tx, control: TriggerControl): Pr
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: why we need specific email notifications instead of using general flow?
|
||||
async function sendEmailNotifications (
|
||||
control: TriggerControl,
|
||||
sender: PersonAccount,
|
||||
doc: Request | PublicHoliday,
|
||||
space: Ref<Department>,
|
||||
type: Ref<NotificationType>
|
||||
typeId: Ref<NotificationType>
|
||||
): Promise<void> {
|
||||
const contacts = new Set<Ref<Contact>>()
|
||||
const departments = await buildHierarchy(space, control)
|
||||
@ -268,8 +271,14 @@ async function sendEmailNotifications (
|
||||
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, {
|
||||
person: { $in: Array.from(contacts.values()) as Ref<Employee>[] }
|
||||
})
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, { _id: typeId })
|
||||
if (type === undefined) return
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: gmail.providers.EmailNotificationProvider
|
||||
})
|
||||
if (provider === undefined) return
|
||||
for (const account of accounts) {
|
||||
const allowed = await isAllowed(control, account._id, type, notification.providers.EmailNotification)
|
||||
const allowed = await isAllowed(control, account._id, type, provider)
|
||||
if (!allowed) {
|
||||
contacts.delete(account.person)
|
||||
}
|
||||
@ -283,7 +292,7 @@ async function sendEmailNotifications (
|
||||
const senderPerson = (await control.findAll(contact.class.Person, { _id: sender.person }))[0]
|
||||
|
||||
const senderName = senderPerson !== undefined ? formatName(senderPerson.name, control.branding?.lastNameFirst) : ''
|
||||
const content = await getContent(doc, senderName, type, control, '')
|
||||
const content = await getContentByTemplate(doc, senderName, type._id, control, '')
|
||||
if (content === undefined) return
|
||||
|
||||
for (const channel of channels) {
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact, { Employee, Person, PersonAccount, formatName, getName } from '@hcengineering/contact'
|
||||
import contact, { Employee, Person, PersonAccount, getName, formatName } from '@hcengineering/contact'
|
||||
import core, {
|
||||
Account,
|
||||
Ref,
|
||||
@ -25,10 +25,7 @@ import core, {
|
||||
TxUpdateDoc,
|
||||
UserStatus
|
||||
} from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import { createPushNotification, isAllowed } from '@hcengineering/server-notification-resources'
|
||||
import love, {
|
||||
Invite,
|
||||
JoinRequest,
|
||||
@ -39,7 +36,10 @@ import love, {
|
||||
isOffice,
|
||||
loveId
|
||||
} from '@hcengineering/love'
|
||||
import { createPushNotification, isAllowed } from '@hcengineering/server-notification-resources'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
|
||||
export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxMixin<Person, Employee>
|
||||
@ -259,17 +259,18 @@ export async function OnKnock (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
const from = (await control.findAll(contact.class.Person, { _id: request.person }))[0]
|
||||
if (from === undefined) return []
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||
_id: love.ids.KnockNotification
|
||||
})
|
||||
if (type === undefined) return []
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: notification.providers.PushNotificationProvider
|
||||
})
|
||||
if (provider === undefined) return []
|
||||
for (const user of roomInfo.persons) {
|
||||
const userAcc = await control.modelDb.findOne(contact.class.PersonAccount, { person: user })
|
||||
if (userAcc === undefined) continue
|
||||
if (
|
||||
await isAllowed(
|
||||
control,
|
||||
userAcc._id,
|
||||
love.ids.KnockNotification,
|
||||
notification.providers.BrowserNotification
|
||||
)
|
||||
) {
|
||||
if (await isAllowed(control, userAcc._id, type, provider)) {
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||
const title = await translate(love.string.KnockingLabel, {})
|
||||
const body = await translate(love.string.IsKnocking, {
|
||||
@ -295,9 +296,15 @@ export async function OnInvite (tx: Tx, control: TriggerControl): Promise<Tx[]>
|
||||
const userAcc = await control.modelDb.findOne(contact.class.PersonAccount, { person: target._id })
|
||||
if (userAcc === undefined) return []
|
||||
const from = (await control.findAll(contact.class.Person, { _id: invite.from }))[0]
|
||||
if (
|
||||
await isAllowed(control, userAcc._id, love.ids.InviteNotification, notification.providers.BrowserNotification)
|
||||
) {
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||
_id: love.ids.InviteNotification
|
||||
})
|
||||
if (type === undefined) return []
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: notification.providers.PushNotificationProvider
|
||||
})
|
||||
if (provider === undefined) return []
|
||||
if (await isAllowed(control, userAcc._id, type, provider)) {
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||
const title = await translate(love.string.InivitingLabel, {})
|
||||
const body =
|
||||
|
@ -18,8 +18,6 @@ import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/acti
|
||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||
import contact, {
|
||||
type AvatarInfo,
|
||||
Employee,
|
||||
formatName,
|
||||
getAvatarProviderId,
|
||||
getGravatarUrl,
|
||||
Person,
|
||||
@ -74,7 +72,9 @@ import serverCore from '@hcengineering/server-core'
|
||||
import serverNotification, {
|
||||
getPersonAccount,
|
||||
getPersonAccountById,
|
||||
NOTIFICATION_BODY_SIZE
|
||||
NOTIFICATION_BODY_SIZE,
|
||||
UserInfo,
|
||||
NOTIFICATION_TITLE_SIZE
|
||||
} from '@hcengineering/server-notification'
|
||||
import serverView from '@hcengineering/server-view'
|
||||
import { stripTags } from '@hcengineering/text'
|
||||
@ -82,7 +82,7 @@ import { encodeObjectURI } from '@hcengineering/view'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import webpush, { WebPushError } from 'web-push'
|
||||
|
||||
import { Content, NotifyParams, NotifyResult, UserInfo } from './types'
|
||||
import { Content, NotifyParams, NotifyResult } from './types'
|
||||
import {
|
||||
getHTMLPresenter,
|
||||
getNotificationContent,
|
||||
@ -128,45 +128,47 @@ export async function getCommonNotificationTxes (
|
||||
notifyResult: NotifyResult,
|
||||
_class = notification.class.CommonInboxNotification
|
||||
): Promise<Tx[]> {
|
||||
if (notifyResult.size === 0 || !notifyResult.has(notification.providers.InboxNotificationProvider)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo })
|
||||
|
||||
if (notifyResult.allowed) {
|
||||
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo })
|
||||
const notificationTx = await pushInboxNotifications(
|
||||
control,
|
||||
res,
|
||||
receiver,
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
space,
|
||||
notifyContexts,
|
||||
data,
|
||||
_class,
|
||||
modifiedOn
|
||||
)
|
||||
|
||||
await pushInboxNotifications(
|
||||
control,
|
||||
res,
|
||||
receiver,
|
||||
if (notificationTx !== undefined) {
|
||||
const notificationData = TxProcessor.createDoc2Doc(notificationTx)
|
||||
await applyNotificationProviders(
|
||||
notificationData,
|
||||
notifyResult,
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
space,
|
||||
notifyContexts,
|
||||
data,
|
||||
_class,
|
||||
modifiedOn,
|
||||
sender,
|
||||
notifyResult.push
|
||||
control,
|
||||
res,
|
||||
doc,
|
||||
receiver,
|
||||
sender
|
||||
)
|
||||
}
|
||||
|
||||
if (notifyResult.emails.length === 0) {
|
||||
return res
|
||||
}
|
||||
|
||||
if (receiver.person !== undefined && control.hierarchy.isDerived(receiver.person._class, contact.mixin.Employee)) {
|
||||
const emp = receiver.person as Employee
|
||||
if (emp?.active) {
|
||||
for (const type of notifyResult.emails) {
|
||||
await notifyByEmail(control, type._id, doc, sender, receiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async function getTextPart (doc: Doc, control: TriggerControl): Promise<string | undefined> {
|
||||
const TextPresenter = getTextPresenter(doc._class, control.hierarchy)
|
||||
console.log({ _class: doc._class, presenter: TextPresenter })
|
||||
if (TextPresenter === undefined) return
|
||||
return await (
|
||||
await getResource(TextPresenter.presenter)
|
||||
@ -178,33 +180,52 @@ async function getHtmlPart (doc: Doc, control: TriggerControl): Promise<string |
|
||||
return HTMLPresenter != null ? await (await getResource(HTMLPresenter.presenter))(doc, control) : undefined
|
||||
}
|
||||
|
||||
function fillTemplate (template: string, sender: string, doc: string, data: string): string {
|
||||
function fillTemplate (
|
||||
template: string,
|
||||
sender: string,
|
||||
doc: string,
|
||||
data: string,
|
||||
params: Record<string, string> = {}
|
||||
): string {
|
||||
let res = replaceAll(template, '{sender}', sender)
|
||||
res = replaceAll(res, '{doc}', doc)
|
||||
res = replaceAll(res, '{data}', data)
|
||||
|
||||
for (const key in params) {
|
||||
res = replaceAll(res, `{${key}}`, params[key])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getContent (
|
||||
export async function getContentByTemplate (
|
||||
doc: Doc | undefined,
|
||||
sender: string,
|
||||
type: Ref<BaseNotificationType>,
|
||||
control: TriggerControl,
|
||||
data: string
|
||||
data: string,
|
||||
notificationData?: InboxNotification
|
||||
): Promise<Content | undefined> {
|
||||
if (doc === undefined) return
|
||||
const notificationType = control.modelDb.getObject(type)
|
||||
if (notificationType.templates === undefined) return
|
||||
|
||||
const textPart = await getTextPart(doc, control)
|
||||
if (textPart === undefined) return
|
||||
if (notificationType.templates === undefined) return
|
||||
const text = fillTemplate(notificationType.templates.textTemplate, sender, textPart, data)
|
||||
const params =
|
||||
notificationData !== undefined
|
||||
? await getTranslatedNotificationContent(notificationData, notificationData._class, control)
|
||||
: {}
|
||||
|
||||
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)
|
||||
const subject = fillTemplate(notificationType.templates.subjectTemplate, sender, textPart, data)
|
||||
const html = fillTemplate(notificationType.templates.htmlTemplate, sender, htmlPart ?? textPart, data, params)
|
||||
const subject = fillTemplate(notificationType.templates.subjectTemplate, sender, textPart, data, params)
|
||||
|
||||
if (subject === '') return
|
||||
|
||||
return {
|
||||
text,
|
||||
html,
|
||||
@ -212,59 +233,6 @@ export async function getContent (
|
||||
}
|
||||
}
|
||||
|
||||
async function notifyByEmail (
|
||||
control: TriggerControl,
|
||||
type: Ref<BaseNotificationType>,
|
||||
doc: Doc | undefined,
|
||||
sender: UserInfo,
|
||||
receiver: UserInfo,
|
||||
data: string = ''
|
||||
): Promise<void> {
|
||||
const account = receiver.account
|
||||
|
||||
if (account === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const senderPerson = sender.person
|
||||
const senderName = senderPerson !== undefined ? formatName(senderPerson.name, control.branding?.lastNameFirst) : ''
|
||||
|
||||
const content = await getContent(doc, senderName, type, control, data)
|
||||
|
||||
if (content !== undefined) {
|
||||
await sendEmailNotification(content.text, content.html, content.subject, account.email)
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendEmailNotification (
|
||||
text: string,
|
||||
html: string,
|
||||
subject: string,
|
||||
receiver: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
const sesURL = getMetadata(serverNotification.metadata.SesUrl)
|
||||
if (sesURL === undefined || sesURL === '') {
|
||||
console.log('Please provide email service url to enable email confirmations.')
|
||||
return
|
||||
}
|
||||
await fetch(concatLink(sesURL, '/send'), {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
html,
|
||||
subject,
|
||||
to: [receiver]
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
console.log('Could not send email notification', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function getValueCollaborators (value: any, attr: AnyAttribute, control: TriggerControl): Promise<Ref<Account>[]> {
|
||||
const hierarchy = control.hierarchy
|
||||
if (attr.type._class === core.class.RefTo) {
|
||||
@ -371,11 +339,8 @@ export async function pushInboxNotifications (
|
||||
data: Partial<Data<InboxNotification>>,
|
||||
_class: Ref<Class<InboxNotification>>,
|
||||
modifiedOn: Timestamp,
|
||||
sender: UserInfo,
|
||||
shouldPush: boolean,
|
||||
shouldUpdateTimestamp = true,
|
||||
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
||||
): Promise<void> {
|
||||
shouldUpdateTimestamp = true
|
||||
): Promise<TxCreateDoc<InboxNotification> | undefined> {
|
||||
const account = target.account
|
||||
|
||||
if (account === undefined) {
|
||||
@ -427,28 +392,14 @@ export async function pushInboxNotifications (
|
||||
}
|
||||
const notificationTx = control.txFactory.createTxCreateDoc(_class, space, notificationData)
|
||||
res.push(notificationTx)
|
||||
if (shouldPush) {
|
||||
// const now = Date.now()
|
||||
const pushTx = await createPushFromInbox(
|
||||
control,
|
||||
target,
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
notificationData,
|
||||
_class,
|
||||
sender,
|
||||
notificationTx.objectId,
|
||||
cache
|
||||
)
|
||||
// console.log('Push takes', Date.now() - now, 'ms')
|
||||
if (pushTx !== undefined) {
|
||||
res.push(pushTx)
|
||||
}
|
||||
}
|
||||
|
||||
return notificationTx
|
||||
}
|
||||
}
|
||||
|
||||
async function activityInboxNotificationToText (doc: Data<ActivityInboxNotification>): Promise<[string, string]> {
|
||||
async function activityInboxNotificationToText (
|
||||
doc: Data<ActivityInboxNotification>
|
||||
): Promise<{ title: string, body: string, [key: string]: string }> {
|
||||
let title: string = ''
|
||||
let body: string = ''
|
||||
|
||||
@ -466,10 +417,12 @@ async function activityInboxNotificationToText (doc: Data<ActivityInboxNotificat
|
||||
body = await translate(doc.body, params)
|
||||
}
|
||||
|
||||
return [title, body]
|
||||
return { ...params, title: title.substring(0, NOTIFICATION_TITLE_SIZE), body }
|
||||
}
|
||||
|
||||
async function commonInboxNotificationToText (doc: Data<CommonInboxNotification>): Promise<[string, string]> {
|
||||
async function commonInboxNotificationToText (
|
||||
doc: Data<CommonInboxNotification>
|
||||
): Promise<{ title: string, body: string, [key: string]: string }> {
|
||||
let title: string = ''
|
||||
let body: string = ''
|
||||
|
||||
@ -492,13 +445,13 @@ async function commonInboxNotificationToText (doc: Data<CommonInboxNotification>
|
||||
if (doc.message != null) {
|
||||
body = await translate(doc.message, params)
|
||||
}
|
||||
return [title, body]
|
||||
return { ...params, title, body }
|
||||
}
|
||||
|
||||
async function mentionInboxNotificationToText (
|
||||
doc: Data<MentionInboxNotification>,
|
||||
control: TriggerControl
|
||||
): Promise<[string, string]> {
|
||||
): Promise<{ title: string, body: string, [key: string]: string }> {
|
||||
let obj = (await control.findAll(doc.mentionedInClass, { _id: doc.mentionedIn }, { limit: 1 }))[0]
|
||||
if (obj !== undefined) {
|
||||
if (control.hierarchy.isDerived(obj._class, chunter.class.ChatMessage)) {
|
||||
@ -525,6 +478,22 @@ async function mentionInboxNotificationToText (
|
||||
return await commonInboxNotificationToText(doc)
|
||||
}
|
||||
|
||||
async function getTranslatedNotificationContent (
|
||||
data: Data<InboxNotification>,
|
||||
_class: Ref<Class<InboxNotification>>,
|
||||
control: TriggerControl
|
||||
): Promise<{ title: string, body: string, [key: string]: string }> {
|
||||
if (control.hierarchy.isDerived(_class, notification.class.ActivityInboxNotification)) {
|
||||
return await activityInboxNotificationToText(data as Data<ActivityInboxNotification>)
|
||||
} else if (control.hierarchy.isDerived(_class, notification.class.MentionInboxNotification)) {
|
||||
return await mentionInboxNotificationToText(data as Data<MentionInboxNotification>, control)
|
||||
} else if (control.hierarchy.isDerived(_class, notification.class.CommonInboxNotification)) {
|
||||
return await commonInboxNotificationToText(data as Data<CommonInboxNotification>)
|
||||
}
|
||||
|
||||
return { title: '', body: '' }
|
||||
}
|
||||
|
||||
export async function createPushFromInbox (
|
||||
control: TriggerControl,
|
||||
target: UserInfo,
|
||||
@ -536,15 +505,8 @@ export async function createPushFromInbox (
|
||||
_id: Ref<Doc>,
|
||||
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
||||
): Promise<Tx | undefined> {
|
||||
let title: string = ''
|
||||
let body: string = ''
|
||||
if (control.hierarchy.isDerived(_class, notification.class.ActivityInboxNotification)) {
|
||||
;[title, body] = await activityInboxNotificationToText(data as Data<ActivityInboxNotification>)
|
||||
} else if (control.hierarchy.isDerived(_class, notification.class.MentionInboxNotification)) {
|
||||
;[title, body] = await mentionInboxNotificationToText(data as Data<MentionInboxNotification>, control)
|
||||
} else if (control.hierarchy.isDerived(_class, notification.class.CommonInboxNotification)) {
|
||||
;[title, body] = await commonInboxNotificationToText(data as Data<CommonInboxNotification>)
|
||||
}
|
||||
const { title, body } = await getTranslatedNotificationContent(data, _class, control)
|
||||
|
||||
if (title === '' || body === '') {
|
||||
return
|
||||
}
|
||||
@ -637,7 +599,7 @@ async function sendPushToSubscription (
|
||||
try {
|
||||
await webpush.sendNotification(subscription, JSON.stringify(data))
|
||||
} catch (err) {
|
||||
console.log('Cannot send push notification to', targetUser, err)
|
||||
control.ctx.info('Cannot send push notification to', { user: targetUser, err })
|
||||
if (err instanceof WebPushError && err.body.includes('expired')) {
|
||||
const tx = control.txFactory.createTxRemoveDoc(subscription._class, subscription.space, subscription._id)
|
||||
await control.apply([tx])
|
||||
@ -656,39 +618,78 @@ export async function pushActivityInboxNotifications (
|
||||
sender: UserInfo,
|
||||
object: Doc,
|
||||
docNotifyContexts: DocNotifyContext[],
|
||||
activityMessages: ActivityMessage[],
|
||||
shouldUpdateTimestamp: boolean,
|
||||
shouldPush: boolean,
|
||||
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
||||
): Promise<void> {
|
||||
activityMessage: ActivityMessage,
|
||||
shouldUpdateTimestamp: boolean
|
||||
): Promise<TxCreateDoc<InboxNotification> | undefined> {
|
||||
if (target.account === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const activityMessage of activityMessages) {
|
||||
const content = await getNotificationContent(originTx, target.account, sender, object, control)
|
||||
const data: Partial<Data<ActivityInboxNotification>> = {
|
||||
...content,
|
||||
attachedTo: activityMessage._id,
|
||||
attachedToClass: activityMessage._class
|
||||
const content = await getNotificationContent(originTx, target.account, sender, object, control)
|
||||
const data: Partial<Data<ActivityInboxNotification>> = {
|
||||
...content,
|
||||
attachedTo: activityMessage._id,
|
||||
attachedToClass: activityMessage._class
|
||||
}
|
||||
|
||||
return await pushInboxNotifications(
|
||||
control,
|
||||
res,
|
||||
target,
|
||||
activityMessage.attachedTo,
|
||||
activityMessage.attachedToClass,
|
||||
activityMessage.space,
|
||||
docNotifyContexts,
|
||||
data,
|
||||
notification.class.ActivityInboxNotification,
|
||||
activityMessage.modifiedOn,
|
||||
shouldUpdateTimestamp
|
||||
)
|
||||
}
|
||||
|
||||
export async function applyNotificationProviders (
|
||||
data: InboxNotification,
|
||||
notifyResult: NotifyResult,
|
||||
attachedTo: Ref<Doc>,
|
||||
attachedToClass: Ref<Class<Doc>>,
|
||||
control: TriggerControl,
|
||||
res: Tx[],
|
||||
object: Doc,
|
||||
receiver: UserInfo,
|
||||
sender: UserInfo
|
||||
): Promise<void> {
|
||||
const resources = await control.modelDb.findAll(serverNotification.class.NotificationProviderResources, {})
|
||||
for (const [provider, types] of notifyResult.entries()) {
|
||||
if (provider === notification.providers.PushNotificationProvider) {
|
||||
// const now = Date.now()
|
||||
const pushTx = await createPushFromInbox(
|
||||
control,
|
||||
receiver,
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
data,
|
||||
notification.class.ActivityInboxNotification,
|
||||
sender,
|
||||
data._id
|
||||
)
|
||||
// console.log('Push takes', Date.now() - now, 'ms')
|
||||
if (pushTx !== undefined) {
|
||||
res.push(pushTx)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
await pushInboxNotifications(
|
||||
control,
|
||||
res,
|
||||
target,
|
||||
activityMessage.attachedTo,
|
||||
activityMessage.attachedToClass,
|
||||
activityMessage.space,
|
||||
docNotifyContexts,
|
||||
data,
|
||||
notification.class.ActivityInboxNotification,
|
||||
activityMessage.modifiedOn,
|
||||
sender,
|
||||
shouldPush,
|
||||
shouldUpdateTimestamp,
|
||||
cache
|
||||
)
|
||||
const resource = resources.find((it) => it.provider === provider)
|
||||
|
||||
if (resource === undefined) continue
|
||||
|
||||
const fn = await getResource(resource.fn)
|
||||
|
||||
const txes = await fn(control, types, object, data, receiver, sender)
|
||||
if (txes.length > 0) {
|
||||
res.push(...txes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -697,14 +698,13 @@ export async function getNotificationTxes (
|
||||
object: Doc,
|
||||
tx: TxCUD<Doc>,
|
||||
originTx: TxCUD<Doc>,
|
||||
target: UserInfo,
|
||||
receiver: UserInfo,
|
||||
sender: UserInfo,
|
||||
params: NotifyParams,
|
||||
docNotifyContexts: DocNotifyContext[],
|
||||
activityMessages: ActivityMessage[],
|
||||
cache: Map<Ref<Doc>, Doc>
|
||||
activityMessages: ActivityMessage[]
|
||||
): Promise<Tx[]> {
|
||||
if (target.account === undefined) {
|
||||
if (receiver.account === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
@ -717,39 +717,39 @@ export async function getNotificationTxes (
|
||||
tx,
|
||||
originTx,
|
||||
object,
|
||||
target.account,
|
||||
receiver.account,
|
||||
params.isOwn,
|
||||
params.isSpace,
|
||||
docMessage
|
||||
)
|
||||
|
||||
if (notifyResult.allowed) {
|
||||
await pushActivityInboxNotifications(
|
||||
if (notifyResult.has(notification.providers.InboxNotificationProvider)) {
|
||||
const notificationTx = await pushActivityInboxNotifications(
|
||||
originTx,
|
||||
control,
|
||||
res,
|
||||
target,
|
||||
receiver,
|
||||
sender,
|
||||
object,
|
||||
docNotifyContexts,
|
||||
[message],
|
||||
params.shouldUpdateTimestamp,
|
||||
notifyResult.push,
|
||||
cache
|
||||
message,
|
||||
params.shouldUpdateTimestamp
|
||||
)
|
||||
}
|
||||
|
||||
if (notifyResult.emails.length === 0) {
|
||||
continue
|
||||
}
|
||||
if (notificationTx !== undefined) {
|
||||
const notificationData = TxProcessor.createDoc2Doc(notificationTx)
|
||||
|
||||
if (target.person !== undefined && control.hierarchy.isDerived(target.person._class, contact.mixin.Employee)) {
|
||||
const emp = target.person as Employee
|
||||
|
||||
if (emp?.active) {
|
||||
for (const type of notifyResult.emails) {
|
||||
await notifyByEmail(control, type._id, object, sender, target)
|
||||
}
|
||||
await applyNotificationProviders(
|
||||
notificationData,
|
||||
notifyResult,
|
||||
message.attachedTo,
|
||||
message.attachedToClass,
|
||||
control,
|
||||
res,
|
||||
object,
|
||||
receiver,
|
||||
sender
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -768,12 +768,11 @@ export async function createCollabDocInfo (
|
||||
): Promise<Tx[]> {
|
||||
let res: Tx[] = []
|
||||
|
||||
if (originTx.space === core.space.DerivedTx || collaborators.length === 0) {
|
||||
if (originTx.space === core.space.DerivedTx) {
|
||||
return res
|
||||
}
|
||||
|
||||
const docMessages = activityMessages.filter((message) => message.attachedTo === object._id)
|
||||
|
||||
if (docMessages.length === 0) {
|
||||
return res
|
||||
}
|
||||
@ -788,6 +787,10 @@ export async function createCollabDocInfo (
|
||||
}
|
||||
}
|
||||
|
||||
if (targets.size === 0) {
|
||||
return res
|
||||
}
|
||||
|
||||
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, {
|
||||
attachedTo: object._id
|
||||
})
|
||||
@ -803,7 +806,7 @@ export async function createCollabDocInfo (
|
||||
if (info === undefined) continue
|
||||
|
||||
res = res.concat(
|
||||
await getNotificationTxes(control, object, tx, originTx, info, sender, params, notifyContexts, docMessages, cache)
|
||||
await getNotificationTxes(control, object, tx, originTx, info, sender, params, notifyContexts, docMessages)
|
||||
)
|
||||
}
|
||||
return res
|
||||
@ -908,8 +911,7 @@ async function updateCollaboratorsMixin (
|
||||
tx: TxMixin<Doc, Collaborators>,
|
||||
control: TriggerControl,
|
||||
activityMessages: ActivityMessage[],
|
||||
originTx: TxCUD<Doc>,
|
||||
cache: Map<Ref<Doc>, Doc>
|
||||
originTx: TxCUD<Doc>
|
||||
): Promise<Tx[]> {
|
||||
const { hierarchy } = control
|
||||
|
||||
@ -948,17 +950,23 @@ async function updateCollaboratorsMixin (
|
||||
prevCollabs = mixin !== undefined ? new Set(await getDocCollaborators(prevDoc, mixin, control)) : new Set()
|
||||
}
|
||||
|
||||
const type = await control.modelDb.findOne(notification.class.BaseNotificationType, {
|
||||
_id: notification.ids.CollaboratoAddNotification
|
||||
})
|
||||
|
||||
if (type === undefined) {
|
||||
return res
|
||||
}
|
||||
|
||||
const providers = await control.modelDb.findAll(notification.class.NotificationProvider, {})
|
||||
|
||||
for (const collab of tx.attributes.collaborators) {
|
||||
if (!prevCollabs.has(collab) && tx.modifiedBy !== collab) {
|
||||
if (
|
||||
await isAllowed(
|
||||
control,
|
||||
collab as Ref<PersonAccount>,
|
||||
notification.ids.CollaboratoAddNotification,
|
||||
notification.providers.PlatformNotification
|
||||
)
|
||||
) {
|
||||
newCollabs.push(collab)
|
||||
for (const provider of providers) {
|
||||
if (await isAllowed(control, collab as Ref<PersonAccount>, type, provider)) {
|
||||
newCollabs.push(collab)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -977,19 +985,19 @@ async function updateCollaboratorsMixin (
|
||||
|
||||
if (target === undefined) continue
|
||||
|
||||
await pushActivityInboxNotifications(
|
||||
originTx,
|
||||
control,
|
||||
res,
|
||||
target,
|
||||
sender,
|
||||
prevDoc,
|
||||
docNotifyContexts,
|
||||
activityMessages,
|
||||
true,
|
||||
false,
|
||||
cache
|
||||
)
|
||||
for (const message of activityMessages) {
|
||||
await pushActivityInboxNotifications(
|
||||
originTx,
|
||||
control,
|
||||
res,
|
||||
target,
|
||||
sender,
|
||||
prevDoc,
|
||||
docNotifyContexts,
|
||||
message,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1220,8 +1228,11 @@ export async function OnAttributeCreate (tx: Tx, control: TriggerControl): Promi
|
||||
objectClass,
|
||||
txClasses,
|
||||
hidden: false,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: false
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{doc} updated'
|
||||
},
|
||||
label: attribute.label
|
||||
}
|
||||
@ -1330,13 +1341,7 @@ export async function createCollaboratorNotifications (
|
||||
case core.class.TxMixin: {
|
||||
let res = await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
|
||||
res = res.concat(
|
||||
await updateCollaboratorsMixin(
|
||||
tx as TxMixin<Doc, Collaborators>,
|
||||
control,
|
||||
activityMessages,
|
||||
originTx ?? tx,
|
||||
cache
|
||||
)
|
||||
await updateCollaboratorsMixin(tx as TxMixin<Doc, Collaborators>, control, activityMessages, originTx ?? tx)
|
||||
)
|
||||
return await applyUserTxes(control, res)
|
||||
}
|
||||
|
@ -12,9 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { BaseNotificationType } from '@hcengineering/notification'
|
||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import { Account, Ref } from '@hcengineering/core'
|
||||
import { BaseNotificationType, NotificationProvider } from '@hcengineering/notification'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -28,20 +27,10 @@ export interface Content {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface NotifyResult {
|
||||
allowed: boolean
|
||||
push: boolean
|
||||
emails: BaseNotificationType[]
|
||||
}
|
||||
export type NotifyResult = Map<Ref<NotificationProvider>, BaseNotificationType[]>
|
||||
|
||||
export interface NotifyParams {
|
||||
isOwn: boolean
|
||||
isSpace: boolean
|
||||
shouldUpdateTimestamp: boolean
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
_id: Ref<Account>
|
||||
account?: PersonAccount
|
||||
person?: Person
|
||||
}
|
||||
|
@ -30,23 +30,26 @@ import core, {
|
||||
MixinUpdate,
|
||||
Ref,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxMixin,
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import serverNotification, {
|
||||
getPersonAccountById,
|
||||
HTMLPresenter,
|
||||
NotificationPresenter,
|
||||
TextPresenter
|
||||
TextPresenter,
|
||||
UserInfo
|
||||
} from '@hcengineering/server-notification'
|
||||
import { getResource, IntlString, translate } from '@hcengineering/platform'
|
||||
import contact, { formatName, PersonAccount } from '@hcengineering/contact'
|
||||
import { DocUpdateMessage } from '@hcengineering/activity'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
import { UserInfo, NotifyResult } from './types'
|
||||
import { NotifyResult } from './types'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -107,45 +110,65 @@ export async function shouldNotifyCommon (
|
||||
): Promise<NotifyResult> {
|
||||
const type = (await control.modelDb.findAll(notification.class.CommonNotificationType, { _id: typeId }))[0]
|
||||
|
||||
const emailTypes: BaseNotificationType[] = []
|
||||
let allowed = false
|
||||
let push = false
|
||||
|
||||
if (type === undefined) {
|
||||
return { allowed, emails: emailTypes, push }
|
||||
return new Map()
|
||||
}
|
||||
|
||||
if (await isAllowed(control, user as Ref<PersonAccount>, type._id, notification.providers.PlatformNotification)) {
|
||||
allowed = true
|
||||
}
|
||||
if (await isAllowed(control, user as Ref<PersonAccount>, type._id, notification.providers.BrowserNotification)) {
|
||||
push = true
|
||||
}
|
||||
if (await isAllowed(control, user as Ref<PersonAccount>, type._id, notification.providers.EmailNotification)) {
|
||||
emailTypes.push(type)
|
||||
const result = new Map<Ref<NotificationProvider>, BaseNotificationType[]>()
|
||||
const providers = await control.modelDb.findAll(notification.class.NotificationProvider, {})
|
||||
|
||||
for (const provider of providers) {
|
||||
const allowed = await isAllowed(control, user as Ref<PersonAccount>, type, provider)
|
||||
if (allowed) {
|
||||
const cur = result.get(provider._id) ?? []
|
||||
result.set(provider._id, [...cur, type])
|
||||
}
|
||||
}
|
||||
|
||||
return { allowed, push, emails: emailTypes }
|
||||
return result
|
||||
}
|
||||
|
||||
export async function isAllowed (
|
||||
control: TriggerControl,
|
||||
receiver: Ref<PersonAccount>,
|
||||
typeId: Ref<BaseNotificationType>,
|
||||
providerId: Ref<NotificationProvider>
|
||||
type: BaseNotificationType,
|
||||
provider: NotificationProvider
|
||||
): Promise<boolean> {
|
||||
const settings = await control.queryFind(notification.class.NotificationSetting, {})
|
||||
const setting = settings.find((p) => p.attachedTo === providerId && p.type === typeId && p.modifiedBy === receiver)
|
||||
const providersSettings = await control.queryFind(notification.class.NotificationProviderSetting, {})
|
||||
const providerSetting = providersSettings.find(
|
||||
({ attachedTo, modifiedBy }) => attachedTo === provider._id && modifiedBy === receiver
|
||||
)
|
||||
|
||||
if (providerSetting !== undefined && !providerSetting.enabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (providerSetting === undefined && !provider.defaultEnabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
const providerDefaults = await control.modelDb.findAll(notification.class.NotificationProviderDefaults, {})
|
||||
|
||||
if (providerDefaults.some((it) => it.provider === provider._id && it.ignoredTypes.includes(type._id))) {
|
||||
return false
|
||||
}
|
||||
|
||||
const typesSettings = await control.queryFind(notification.class.NotificationTypeSetting, {})
|
||||
const setting = typesSettings.find(
|
||||
(it) => it.attachedTo === provider._id && it.type === type._id && it.modifiedBy === receiver
|
||||
)
|
||||
|
||||
if (setting !== undefined) {
|
||||
return setting.enabled
|
||||
}
|
||||
const type = (
|
||||
await control.modelDb.findAll(notification.class.BaseNotificationType, {
|
||||
_id: typeId
|
||||
})
|
||||
)[0]
|
||||
|
||||
if (providerDefaults.some((it) => it.provider === provider._id && it.enabledTypes.includes(type._id))) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (type === undefined) return false
|
||||
return type.providers[providerId] ?? false
|
||||
|
||||
return type.defaultEnabled
|
||||
}
|
||||
|
||||
export async function isShouldNotifyTx (
|
||||
@ -158,10 +181,6 @@ export async function isShouldNotifyTx (
|
||||
isSpace: boolean,
|
||||
docUpdateMessage?: DocUpdateMessage
|
||||
): Promise<NotifyResult> {
|
||||
let allowed = false
|
||||
let push = false
|
||||
|
||||
const emailTypes: NotificationType[] = []
|
||||
const types = await getMatchedTypes(
|
||||
control,
|
||||
tx,
|
||||
@ -170,8 +189,9 @@ export async function isShouldNotifyTx (
|
||||
isSpace,
|
||||
docUpdateMessage?.attributeUpdates?.attrKey
|
||||
)
|
||||
|
||||
const modifiedAccount = await getPersonAccountById(tx.modifiedBy, control)
|
||||
const result = new Map<Ref<NotificationProvider>, BaseNotificationType[]>()
|
||||
const providers = await control.modelDb.findAll(notification.class.NotificationProvider, {})
|
||||
|
||||
for (const type of types) {
|
||||
if (
|
||||
@ -190,21 +210,17 @@ export async function isShouldNotifyTx (
|
||||
if (!res) continue
|
||||
}
|
||||
}
|
||||
if (await isAllowed(control, user._id, type._id, notification.providers.PlatformNotification)) {
|
||||
allowed = true
|
||||
}
|
||||
if (await isAllowed(control, user._id, type._id, notification.providers.BrowserNotification)) {
|
||||
push = true
|
||||
}
|
||||
if (await isAllowed(control, user._id, type._id, notification.providers.EmailNotification)) {
|
||||
emailTypes.push(type)
|
||||
for (const provider of providers) {
|
||||
const allowed = await isAllowed(control, user._id, type, provider)
|
||||
|
||||
if (allowed) {
|
||||
const cur = result.get(provider._id) ?? []
|
||||
result.set(provider._id, [...cur, type])
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
allowed,
|
||||
push,
|
||||
emails: emailTypes
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async function getMatchedTypes (
|
||||
@ -357,6 +373,24 @@ async function getFallbackNotificationFullfillment (
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if (originTx._class === core.class.TxCollectionCUD && tx._class === core.class.TxCreateDoc) {
|
||||
const createTx = tx as TxCreateDoc<Doc>
|
||||
const clazz = control.hierarchy.getClass(createTx.objectClass)
|
||||
const label = clazz.pluralLabel ?? clazz.label
|
||||
|
||||
if (label !== undefined) {
|
||||
intlParamsNotLocalized.collection = clazz.pluralLabel ?? clazz.label
|
||||
body = notification.string.CommonNotificationCollectionAdded
|
||||
}
|
||||
} else if (originTx._class === core.class.TxCollectionCUD && tx._class === core.class.TxRemoveDoc) {
|
||||
const createTx = tx as TxRemoveDoc<Doc>
|
||||
const clazz = control.hierarchy.getClass(createTx.objectClass)
|
||||
const label = clazz.pluralLabel ?? clazz.label
|
||||
|
||||
if (label !== undefined) {
|
||||
intlParamsNotLocalized.collection = clazz.pluralLabel ?? clazz.label
|
||||
body = notification.string.CommonNotificationCollectionRemoved
|
||||
}
|
||||
}
|
||||
|
||||
return { title, body, intlParams, intlParamsNotLocalized }
|
||||
|
@ -16,7 +16,13 @@
|
||||
|
||||
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
|
||||
import { Account, Class, Doc, Mixin, Ref, Tx, TxCUD } from '@hcengineering/core'
|
||||
import { NotificationContent, NotificationType } from '@hcengineering/notification'
|
||||
import {
|
||||
BaseNotificationType,
|
||||
InboxNotification,
|
||||
NotificationContent,
|
||||
NotificationProvider,
|
||||
NotificationType
|
||||
} from '@hcengineering/notification'
|
||||
import { Metadata, Plugin, Resource, plugin } from '@hcengineering/platform'
|
||||
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
|
||||
|
||||
@ -129,7 +135,28 @@ export interface NotificationPresenter extends Class<Doc> {
|
||||
presenter: Resource<NotificationContentProvider>
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
_id: Ref<Account>
|
||||
account?: PersonAccount
|
||||
person?: Person
|
||||
}
|
||||
|
||||
export type NotificationProviderFunc = (
|
||||
control: TriggerControl,
|
||||
types: BaseNotificationType[],
|
||||
object: Doc,
|
||||
data: InboxNotification,
|
||||
receiver: UserInfo,
|
||||
sender: UserInfo
|
||||
) => Promise<Tx[]>
|
||||
|
||||
export interface NotificationProviderResources extends Doc {
|
||||
provider: Ref<NotificationProvider>
|
||||
fn: Resource<NotificationProviderFunc>
|
||||
}
|
||||
|
||||
export const NOTIFICATION_BODY_SIZE = 50
|
||||
export const NOTIFICATION_TITLE_SIZE = 30
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -140,6 +167,9 @@ export default plugin(serverNotificationId, {
|
||||
PushPrivateKey: '' as Metadata<string>,
|
||||
PushSubject: '' as Metadata<string>
|
||||
},
|
||||
class: {
|
||||
NotificationProviderResources: '' as Ref<Class<NotificationProviderResources>>
|
||||
},
|
||||
mixin: {
|
||||
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
|
||||
TextPresenter: '' as Ref<Mixin<TextPresenter>>,
|
||||
|
@ -164,8 +164,7 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
|
||||
senderInfo,
|
||||
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true },
|
||||
notifyContexts,
|
||||
messages,
|
||||
new Map()
|
||||
messages
|
||||
)
|
||||
res.push(...txes)
|
||||
}
|
||||
|
@ -42,6 +42,7 @@
|
||||
"@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/task": "^0.6.20",
|
||||
"@hcengineering/tracker": "^0.6.24",
|
||||
|
@ -37,14 +37,14 @@ import type { TriggerControl } from '@hcengineering/server-core'
|
||||
import {
|
||||
getCommonNotificationTxes,
|
||||
getNotificationContent,
|
||||
isShouldNotifyTx,
|
||||
UserInfo
|
||||
isShouldNotifyTx
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
import task, { makeRank } from '@hcengineering/task'
|
||||
import { jsonToMarkup, nodeDoc, nodeParagraph, nodeText } from '@hcengineering/text'
|
||||
import tracker, { Issue, IssueStatus, Project, TimeSpendReport } from '@hcengineering/tracker'
|
||||
import serverTime, { OnToDo, ToDoFactory } from '@hcengineering/server-time'
|
||||
import time, { ProjectToDo, ToDo, ToDoPriority, TodoAutomationHelper, WorkSlot } from '@hcengineering/time'
|
||||
import { UserInfo } from '@hcengineering/server-notification'
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
@ -36,7 +36,7 @@ export class NotificationsPage {
|
||||
documents = (): Locator => this.page.getByRole('button', { name: 'Documents' })
|
||||
requests = (): Locator => this.page.getByRole('button', { name: 'Requests' })
|
||||
todos = (): Locator => this.page.getByRole('button', { name: "Todo's" })
|
||||
chatMessageToggle = (): Locator => this.page.locator('div:nth-child(7) > .flex-between > .toggle > .toggle-switch')
|
||||
chatMessageToggle = (): Locator => this.page.locator('.grid > div:nth-child(6)')
|
||||
|
||||
constructor (page: Page) {
|
||||
this.page = page
|
||||
|
Loading…
Reference in New Issue
Block a user