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'
|
} from '@hcengineering/model'
|
||||||
import { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
|
import { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
|
||||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||||
import view, { createAction } from '@hcengineering/model-view'
|
import view from '@hcengineering/model-view'
|
||||||
import notification from '@hcengineering/notification'
|
|
||||||
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||||
|
|
||||||
import activity from './plugin'
|
import activity from './plugin'
|
||||||
|
import { buildActions } from './actions'
|
||||||
|
import { buildNotifications } from './notification'
|
||||||
|
|
||||||
export { activityId } from '@hcengineering/activity'
|
export { activityId } from '@hcengineering/activity'
|
||||||
export { activityOperation, migrateMessagesSpace } from './migration'
|
export { activityOperation, migrateMessagesSpace } from './migration'
|
||||||
@ -335,22 +336,10 @@ export function createModel (builder: Builder): void {
|
|||||||
activity.ids.ReactionAddedActivityViewlet
|
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, {
|
builder.mixin(activity.class.ActivityMessage, core.class.Class, view.mixin.ObjectPanel, {
|
||||||
component: view.component.AttachedDocPanel
|
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>>(
|
builder.mixin<Class<DocUpdateMessage>, IndexingConfiguration<DocUpdateMessage>>(
|
||||||
activity.class.DocUpdateMessage,
|
activity.class.DocUpdateMessage,
|
||||||
core.class.Class,
|
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, {
|
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
||||||
domain: DOMAIN_ACTIVITY,
|
domain: DOMAIN_ACTIVITY,
|
||||||
indexes: [
|
indexes: [
|
||||||
@ -405,112 +376,8 @@ export function createModel (builder: Builder): void {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
createAction(
|
buildActions(builder)
|
||||||
builder,
|
buildNotifications(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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default activity
|
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}',
|
htmlTemplate: 'Reminder: {doc}',
|
||||||
subjectTemplate: 'Reminder: {doc}'
|
subjectTemplate: 'Reminder: {doc}'
|
||||||
},
|
},
|
||||||
providers: {
|
defaultEnabled: false
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.EmailNotification]: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
calendar.ids.ReminderNotification
|
calendar.ids.ReminderNotification
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||||
|
provider: notification.providers.InboxNotificationProvider,
|
||||||
|
ignoredTypes: [],
|
||||||
|
enabledTypes: [calendar.ids.ReminderNotification]
|
||||||
|
})
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
activity.class.DocUpdateMessageViewlet,
|
activity.class.DocUpdateMessageViewlet,
|
||||||
core.space.Model,
|
core.space.Model,
|
||||||
|
@ -376,15 +376,11 @@ export function createModel (builder: Builder): void {
|
|||||||
txClasses: [core.class.TxCreateDoc],
|
txClasses: [core.class.TxCreateDoc],
|
||||||
objectClass: chunter.class.ChatMessage,
|
objectClass: chunter.class.ChatMessage,
|
||||||
attachedToClass: chunter.class.DirectMessage,
|
attachedToClass: chunter.class.DirectMessage,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.EmailNotification]: false,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.PlatformNotification]: true
|
|
||||||
},
|
|
||||||
group: chunter.ids.ChunterNotificationGroup,
|
group: chunter.ids.ChunterNotificationGroup,
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: '{sender} has send you a message: {doc} {data}',
|
textTemplate: '{sender} has sent you a message: {doc} {message}',
|
||||||
htmlTemplate: '<p><b>{sender}</b> has send you a message {doc}</p> {data}',
|
htmlTemplate: '<p><b>{sender}</b> has sent you a message {doc}</p> {message}',
|
||||||
subjectTemplate: 'You have new direct message in {doc}'
|
subjectTemplate: 'You have new direct message in {doc}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -400,11 +396,14 @@ export function createModel (builder: Builder): void {
|
|||||||
hidden: false,
|
hidden: false,
|
||||||
txClasses: [core.class.TxCreateDoc],
|
txClasses: [core.class.TxCreateDoc],
|
||||||
objectClass: chunter.class.ChatMessage,
|
objectClass: chunter.class.ChatMessage,
|
||||||
providers: {
|
attachedToClass: chunter.class.Channel,
|
||||||
[notification.providers.PlatformNotification]: true,
|
defaultEnabled: false,
|
||||||
[notification.providers.BrowserNotification]: true
|
group: chunter.ids.ChunterNotificationGroup,
|
||||||
},
|
templates: {
|
||||||
group: chunter.ids.ChunterNotificationGroup
|
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
|
chunter.ids.ChannelNotification
|
||||||
)
|
)
|
||||||
@ -418,11 +417,13 @@ export function createModel (builder: Builder): void {
|
|||||||
hidden: false,
|
hidden: false,
|
||||||
txClasses: [core.class.TxCreateDoc],
|
txClasses: [core.class.TxCreateDoc],
|
||||||
objectClass: chunter.class.ThreadMessage,
|
objectClass: chunter.class.ThreadMessage,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.PlatformNotification]: true,
|
group: chunter.ids.ChunterNotificationGroup,
|
||||||
[notification.providers.BrowserNotification]: true
|
templates: {
|
||||||
},
|
textTemplate: '{body}',
|
||||||
group: chunter.ids.ChunterNotificationGroup
|
htmlTemplate: '<p>{body}</p>',
|
||||||
|
subjectTemplate: '{title}'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
chunter.ids.ThreadNotification
|
chunter.ids.ThreadNotification
|
||||||
)
|
)
|
||||||
@ -649,6 +650,18 @@ export function createModel (builder: Builder): void {
|
|||||||
filters: ['name', 'topic', 'private', 'archived', 'members'],
|
filters: ['name', 'topic', 'private', 'archived', 'members'],
|
||||||
strict: true
|
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
|
export default chunter
|
||||||
|
@ -34,6 +34,7 @@ import {
|
|||||||
import activity, { migrateMessagesSpace, DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
|
import activity, { migrateMessagesSpace, DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
|
||||||
import notification from '@hcengineering/notification'
|
import notification from '@hcengineering/notification'
|
||||||
import contactPlugin, { type PersonAccount } from '@hcengineering/contact'
|
import contactPlugin, { type PersonAccount } from '@hcengineering/contact'
|
||||||
|
import { DOMAIN_NOTIFICATION } from '@hcengineering/model-notification'
|
||||||
|
|
||||||
import chunter from './plugin'
|
import chunter from './plugin'
|
||||||
import { DOMAIN_CHUNTER } from './index'
|
import { DOMAIN_CHUNTER } from './index'
|
||||||
@ -187,6 +188,9 @@ async function removeOldClasses (client: MigrationClient): Promise<void> {
|
|||||||
|
|
||||||
for (const _class of classes) {
|
for (const _class of classes) {
|
||||||
await client.deleteMany(DOMAIN_CHUNTER, { _class })
|
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, { objectClass: _class })
|
||||||
await client.deleteMany(DOMAIN_TX, { '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) => {
|
func: async (client) => {
|
||||||
await removeOldClasses(client)
|
await removeOldClasses(client)
|
||||||
}
|
}
|
||||||
|
@ -902,9 +902,11 @@ export function defineNotifications (builder: Builder): void {
|
|||||||
field: 'content',
|
field: 'content',
|
||||||
txClasses: [core.class.TxUpdateDoc],
|
txClasses: [core.class.TxUpdateDoc],
|
||||||
objectClass: documents.class.ControlledDocument,
|
objectClass: documents.class.ControlledDocument,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.PlatformNotification]: true,
|
templates: {
|
||||||
[notification.providers.BrowserNotification]: false
|
textTemplate: '{body}',
|
||||||
|
htmlTemplate: '<p>{body}</p>',
|
||||||
|
subjectTemplate: '{title}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
documents.notification.ContentNotification
|
documents.notification.ContentNotification
|
||||||
@ -922,11 +924,7 @@ export function defineNotifications (builder: Builder): void {
|
|||||||
field: 'state',
|
field: 'state',
|
||||||
txClasses: [core.class.TxUpdateDoc],
|
txClasses: [core.class.TxUpdateDoc],
|
||||||
objectClass: documents.class.ControlledDocument,
|
objectClass: documents.class.ControlledDocument,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: false,
|
|
||||||
[notification.providers.EmailNotification]: false
|
|
||||||
},
|
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: '{sender} changed {doc} status',
|
textTemplate: '{sender} changed {doc} status',
|
||||||
htmlTemplate: '<p>{sender} changed {doc} status</p>',
|
htmlTemplate: '<p>{sender} changed {doc} status</p>',
|
||||||
@ -948,11 +946,7 @@ export function defineNotifications (builder: Builder): void {
|
|||||||
field: 'coAuthors',
|
field: 'coAuthors',
|
||||||
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
||||||
objectClass: documents.class.ControlledDocument,
|
objectClass: documents.class.ControlledDocument,
|
||||||
providers: {
|
defaultEnabled: true,
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.EmailNotification]: true
|
|
||||||
},
|
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: '{sender} assigned you as a co-author of {doc}',
|
textTemplate: '{sender} assigned you as a co-author of {doc}',
|
||||||
htmlTemplate: '<p>{sender} assigned you as a co-author of {doc}</p>',
|
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
|
documents.notification.CoAuthorsNotification
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||||
|
provider: notification.providers.InboxNotificationProvider,
|
||||||
|
ignoredTypes: [],
|
||||||
|
enabledTypes: [documents.notification.StateNotification, documents.notification.ContentNotification]
|
||||||
|
})
|
||||||
|
|
||||||
generateClassNotificationTypes(
|
generateClassNotificationTypes(
|
||||||
builder,
|
builder,
|
||||||
documents.class.ControlledDocument,
|
documents.class.ControlledDocument,
|
||||||
|
@ -461,14 +461,22 @@ function defineDocument (builder: Builder): void {
|
|||||||
field: 'content',
|
field: 'content',
|
||||||
txClasses: [core.class.TxUpdateDoc],
|
txClasses: [core.class.TxUpdateDoc],
|
||||||
objectClass: document.class.Document,
|
objectClass: document.class.Document,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.PlatformNotification]: true,
|
templates: {
|
||||||
[notification.providers.BrowserNotification]: false
|
textTemplate: '{body}',
|
||||||
|
htmlTemplate: '<p>{body}</p>',
|
||||||
|
subjectTemplate: '{title}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
document.ids.ContentNotification
|
document.ids.ContentNotification
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||||
|
provider: notification.providers.InboxNotificationProvider,
|
||||||
|
ignoredTypes: [],
|
||||||
|
enabledTypes: [document.ids.ContentNotification]
|
||||||
|
})
|
||||||
|
|
||||||
generateClassNotificationTypes(
|
generateClassNotificationTypes(
|
||||||
builder,
|
builder,
|
||||||
document.class.Document,
|
document.class.Document,
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"@hcengineering/model-core": "^0.6.0",
|
"@hcengineering/model-core": "^0.6.0",
|
||||||
"@hcengineering/contact": "^0.6.24",
|
"@hcengineering/contact": "^0.6.24",
|
||||||
"@hcengineering/model-contact": "^0.6.1",
|
"@hcengineering/model-contact": "^0.6.1",
|
||||||
|
"@hcengineering/model-love": "^0.6.0",
|
||||||
"@hcengineering/gmail": "^0.6.22",
|
"@hcengineering/gmail": "^0.6.22",
|
||||||
"@hcengineering/gmail-resources": "^0.6.0",
|
"@hcengineering/gmail-resources": "^0.6.0",
|
||||||
"@hcengineering/model-attachment": "^0.6.0",
|
"@hcengineering/model-attachment": "^0.6.0",
|
||||||
@ -43,6 +44,7 @@
|
|||||||
"@hcengineering/model-notification": "^0.6.0",
|
"@hcengineering/model-notification": "^0.6.0",
|
||||||
"@hcengineering/view": "^0.6.13",
|
"@hcengineering/view": "^0.6.13",
|
||||||
"@hcengineering/setting": "^0.6.17",
|
"@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 notification from '@hcengineering/model-notification'
|
||||||
import view, { createAction } from '@hcengineering/model-view'
|
import view, { createAction } from '@hcengineering/model-view'
|
||||||
import setting from '@hcengineering/setting'
|
import setting from '@hcengineering/setting'
|
||||||
|
import love from '@hcengineering/model-love'
|
||||||
|
|
||||||
import gmail from './plugin'
|
import gmail from './plugin'
|
||||||
|
|
||||||
export { gmailId } from '@hcengineering/gmail'
|
export { gmailId } from '@hcengineering/gmail'
|
||||||
@ -234,10 +236,7 @@ export function createModel (builder: Builder): void {
|
|||||||
objectClass: gmail.class.Message,
|
objectClass: gmail.class.Message,
|
||||||
group: gmail.ids.EmailNotificationGroup,
|
group: gmail.ids.EmailNotificationGroup,
|
||||||
allowedForAuthor: true,
|
allowedForAuthor: true,
|
||||||
providers: {
|
defaultEnabled: false
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
gmail.ids.EmailNotification
|
gmail.ids.EmailNotification
|
||||||
)
|
)
|
||||||
@ -258,4 +257,36 @@ export function createModel (builder: Builder): void {
|
|||||||
{ createdOn: -1 }
|
{ 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.
|
// 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 { gmailId } from '@hcengineering/gmail'
|
||||||
import {
|
import {
|
||||||
migrateSpace,
|
migrateSpace,
|
||||||
@ -23,6 +23,24 @@ import {
|
|||||||
type MigrationUpgradeClient
|
type MigrationUpgradeClient
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import { DOMAIN_GMAIL } from '.'
|
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 = {
|
export const gmailOperation: MigrateOperation = {
|
||||||
async migrate (client: MigrationClient): Promise<void> {
|
async migrate (client: MigrationClient): Promise<void> {
|
||||||
@ -32,6 +50,10 @@ export const gmailOperation: MigrateOperation = {
|
|||||||
func: async (client: MigrationClient) => {
|
func: async (client: MigrationClient) => {
|
||||||
await migrateSpace(client, 'gmail:space:Gmail' as Ref<Space>, core.space.Workspace, [DOMAIN_GMAIL])
|
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
|
// will be created with different trigger
|
||||||
txClasses: [],
|
txClasses: [],
|
||||||
objectClass: hr.class.Request,
|
objectClass: hr.class.Request,
|
||||||
providers: {
|
defaultEnabled: true,
|
||||||
[notification.providers.EmailNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: false,
|
|
||||||
[notification.providers.PlatformNotification]: true
|
|
||||||
},
|
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: 'New request: {doc}',
|
textTemplate: 'New request: {doc}',
|
||||||
htmlTemplate: 'New request: {doc}',
|
htmlTemplate: 'New request: {doc}',
|
||||||
@ -508,11 +504,7 @@ export function createModel (builder: Builder): void {
|
|||||||
// will be created with different trigger
|
// will be created with different trigger
|
||||||
txClasses: [],
|
txClasses: [],
|
||||||
objectClass: hr.class.Request,
|
objectClass: hr.class.Request,
|
||||||
providers: {
|
defaultEnabled: true,
|
||||||
[notification.providers.EmailNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.PlatformNotification]: true
|
|
||||||
},
|
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: 'Request updated: {doc}',
|
textTemplate: 'Request updated: {doc}',
|
||||||
htmlTemplate: 'Request updated: {doc}',
|
htmlTemplate: 'Request updated: {doc}',
|
||||||
@ -533,11 +525,7 @@ export function createModel (builder: Builder): void {
|
|||||||
// will be created with different trigger
|
// will be created with different trigger
|
||||||
txClasses: [],
|
txClasses: [],
|
||||||
objectClass: hr.class.Request,
|
objectClass: hr.class.Request,
|
||||||
providers: {
|
defaultEnabled: true,
|
||||||
[notification.providers.EmailNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.PlatformNotification]: true
|
|
||||||
},
|
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: 'Request removed: {doc}',
|
textTemplate: 'Request removed: {doc}',
|
||||||
htmlTemplate: 'Request removed: {doc}',
|
htmlTemplate: 'Request removed: {doc}',
|
||||||
@ -558,11 +546,7 @@ export function createModel (builder: Builder): void {
|
|||||||
// will be created with different trigger
|
// will be created with different trigger
|
||||||
txClasses: [],
|
txClasses: [],
|
||||||
objectClass: hr.class.PublicHoliday,
|
objectClass: hr.class.PublicHoliday,
|
||||||
providers: {
|
defaultEnabled: true,
|
||||||
[notification.providers.EmailNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.PlatformNotification]: true
|
|
||||||
},
|
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: 'New public holiday: {doc}',
|
textTemplate: 'New public holiday: {doc}',
|
||||||
htmlTemplate: '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>',
|
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
|
||||||
subjectTemplate: '{doc} was assigned to you'
|
subjectTemplate: '{doc} was assigned to you'
|
||||||
},
|
},
|
||||||
providers: {
|
defaultEnabled: true
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.EmailNotification]: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
lead.ids.AssigneeNotification
|
lead.ids.AssigneeNotification
|
||||||
)
|
)
|
||||||
@ -420,9 +416,11 @@ export function createModel (builder: Builder): void {
|
|||||||
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
||||||
objectClass: lead.class.Funnel,
|
objectClass: lead.class.Funnel,
|
||||||
spaceSubscribe: true,
|
spaceSubscribe: true,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.PlatformNotification]: false,
|
templates: {
|
||||||
[notification.providers.BrowserNotification]: false
|
textTemplate: '{body}',
|
||||||
|
htmlTemplate: '<p>{body}</p>',
|
||||||
|
subjectTemplate: '{title}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
lead.ids.LeadCreateNotification
|
lead.ids.LeadCreateNotification
|
||||||
|
@ -187,10 +187,7 @@ export function createModel (builder: Builder): void {
|
|||||||
group: love.ids.LoveNotificationGroup,
|
group: love.ids.LoveNotificationGroup,
|
||||||
txClasses: [core.class.TxCreateDoc],
|
txClasses: [core.class.TxCreateDoc],
|
||||||
objectClass: love.class.Invite,
|
objectClass: love.class.Invite,
|
||||||
providers: {
|
defaultEnabled: true
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
love.ids.InviteNotification
|
love.ids.InviteNotification
|
||||||
)
|
)
|
||||||
@ -205,14 +202,18 @@ export function createModel (builder: Builder): void {
|
|||||||
group: love.ids.LoveNotificationGroup,
|
group: love.ids.LoveNotificationGroup,
|
||||||
txClasses: [],
|
txClasses: [],
|
||||||
objectClass: love.class.JoinRequest,
|
objectClass: love.class.JoinRequest,
|
||||||
providers: {
|
defaultEnabled: true
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.SoundNotification]: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
love.ids.KnockNotification
|
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, {
|
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
||||||
domain: DOMAIN_LOVE,
|
domain: DOMAIN_LOVE,
|
||||||
disabled: [{ space: 1 }, { modifiedOn: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }]
|
disabled: [{ space: 1 }, { modifiedOn: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }]
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"@hcengineering/setting": "^0.6.17",
|
"@hcengineering/setting": "^0.6.17",
|
||||||
"@hcengineering/ui": "^0.6.15",
|
"@hcengineering/ui": "^0.6.15",
|
||||||
"@hcengineering/view": "^0.6.13",
|
"@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 NotificationObjectPresenter,
|
||||||
type NotificationPreferencesGroup,
|
type NotificationPreferencesGroup,
|
||||||
type NotificationPreview,
|
type NotificationPreview,
|
||||||
type NotificationProvider,
|
|
||||||
type NotificationSetting,
|
|
||||||
type NotificationStatus,
|
type NotificationStatus,
|
||||||
type NotificationTemplate,
|
type NotificationTemplate,
|
||||||
type NotificationType,
|
type NotificationType,
|
||||||
type PushSubscription,
|
type PushSubscription,
|
||||||
type PushSubscriptionKeys
|
type PushSubscriptionKeys,
|
||||||
|
type NotificationProvider,
|
||||||
|
type NotificationProviderSetting,
|
||||||
|
type NotificationTypeSetting,
|
||||||
|
type NotificationProviderDefaults
|
||||||
} from '@hcengineering/notification'
|
} 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 setting from '@hcengineering/setting'
|
||||||
import { type AnyComponent, type Location } from '@hcengineering/ui/src/types'
|
import { type AnyComponent, type Location } from '@hcengineering/ui/src/types'
|
||||||
|
|
||||||
@ -112,7 +114,7 @@ export class TBaseNotificationType extends TDoc implements BaseNotificationType
|
|||||||
generated!: boolean
|
generated!: boolean
|
||||||
label!: IntlString
|
label!: IntlString
|
||||||
group!: Ref<NotificationGroup>
|
group!: Ref<NotificationGroup>
|
||||||
providers!: Record<Ref<NotificationProvider>, boolean>
|
defaultEnabled!: boolean
|
||||||
hidden!: boolean
|
hidden!: boolean
|
||||||
templates?: NotificationTemplate
|
templates?: NotificationTemplate
|
||||||
}
|
}
|
||||||
@ -142,20 +144,19 @@ export class TNotificationPreferencesGroup extends TDoc implements NotificationP
|
|||||||
presenter!: AnyComponent
|
presenter!: AnyComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(notification.class.NotificationProvider, core.class.Doc, DOMAIN_MODEL)
|
@Model(notification.class.NotificationTypeSetting, preference.class.Preference)
|
||||||
export class TNotificationProvider extends TDoc implements NotificationProvider {
|
export class TNotificationTypeSetting extends TPreference implements NotificationTypeSetting {
|
||||||
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 {
|
|
||||||
declare attachedTo: Ref<TNotificationProvider>
|
declare attachedTo: Ref<TNotificationProvider>
|
||||||
type!: Ref<BaseNotificationType>
|
type!: Ref<BaseNotificationType>
|
||||||
enabled!: boolean
|
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)
|
@Mixin(notification.mixin.ClassCollaborators, core.class.Class)
|
||||||
export class TClassCollaborators extends TClass {
|
export class TClassCollaborators extends TClass {
|
||||||
fields!: string[]
|
fields!: string[]
|
||||||
@ -284,6 +285,26 @@ export class TActivityNotificationViewlet extends TDoc implements ActivityNotifi
|
|||||||
presenter!: AnyComponent
|
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({
|
export const notificationActionTemplates = template({
|
||||||
pinContext: {
|
pinContext: {
|
||||||
action: notification.actionImpl.PinDocNotifyContext,
|
action: notification.actionImpl.PinDocNotifyContext,
|
||||||
@ -311,8 +332,6 @@ export function createModel (builder: Builder): void {
|
|||||||
builder.createModel(
|
builder.createModel(
|
||||||
TBrowserNotification,
|
TBrowserNotification,
|
||||||
TNotificationType,
|
TNotificationType,
|
||||||
TNotificationProvider,
|
|
||||||
TNotificationSetting,
|
|
||||||
TNotificationGroup,
|
TNotificationGroup,
|
||||||
TNotificationPreferencesGroup,
|
TNotificationPreferencesGroup,
|
||||||
TClassCollaborators,
|
TClassCollaborators,
|
||||||
@ -328,44 +347,11 @@ export function createModel (builder: Builder): void {
|
|||||||
TBaseNotificationType,
|
TBaseNotificationType,
|
||||||
TCommonNotificationType,
|
TCommonNotificationType,
|
||||||
TMentionInboxNotification,
|
TMentionInboxNotification,
|
||||||
TPushSubscription
|
TPushSubscription,
|
||||||
)
|
TNotificationProvider,
|
||||||
|
TNotificationProviderSetting,
|
||||||
builder.createDoc(
|
TNotificationTypeSetting,
|
||||||
notification.class.NotificationProvider,
|
TNotificationProviderDefaults
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
@ -434,9 +420,7 @@ export function createModel (builder: Builder): void {
|
|||||||
group: notification.ids.NotificationGroup,
|
group: notification.ids.NotificationGroup,
|
||||||
txClasses: [],
|
txClasses: [],
|
||||||
objectClass: notification.mixin.Collaborators,
|
objectClass: notification.mixin.Collaborators,
|
||||||
providers: {
|
defaultEnabled: true
|
||||||
[notification.providers.PlatformNotification]: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
notification.ids.CollaboratoAddNotification
|
notification.ids.CollaboratoAddNotification
|
||||||
)
|
)
|
||||||
@ -560,14 +544,10 @@ export function createModel (builder: Builder): void {
|
|||||||
generated: false,
|
generated: false,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
group: notification.ids.NotificationGroup,
|
group: notification.ids.NotificationGroup,
|
||||||
providers: {
|
defaultEnabled: true,
|
||||||
[notification.providers.EmailNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.PlatformNotification]: true
|
|
||||||
},
|
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: '{sender} mentioned you in {doc} {data}',
|
textTemplate: '{sender} mentioned you in {doc} {message}',
|
||||||
htmlTemplate: '<p>{sender}</b> mentioned you in {doc}</p> {data}',
|
htmlTemplate: '<p>{sender}</b> mentioned you in {doc}</p> {message}',
|
||||||
subjectTemplate: 'You were mentioned in {doc}'
|
subjectTemplate: 'You were mentioned in {doc}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -654,6 +634,7 @@ export function createModel (builder: Builder): void {
|
|||||||
indexes: []
|
indexes: []
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.mixin<Class<InboxNotification>, IndexingConfiguration<InboxNotification>>(
|
builder.mixin<Class<InboxNotification>, IndexingConfiguration<InboxNotification>>(
|
||||||
notification.class.InboxNotification,
|
notification.class.InboxNotification,
|
||||||
core.class.Class,
|
core.class.Class,
|
||||||
@ -672,6 +653,73 @@ export function createModel (builder: Builder): void {
|
|||||||
indexes: []
|
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 (
|
export function generateClassNotificationTypes (
|
||||||
@ -687,6 +735,8 @@ export function generateClassNotificationTypes (
|
|||||||
hierarchy.isDerived(_class, core.class.AttachedDoc) ? core.class.AttachedDoc : core.class.Doc
|
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 filtered = Array.from(attributes.values()).filter((p) => p.hidden !== true && p.readonly !== true)
|
||||||
|
const enabledInboxTypes: Ref<BaseNotificationType>[] = []
|
||||||
|
|
||||||
for (const attribute of filtered) {
|
for (const attribute of filtered) {
|
||||||
if (ignoreKeys.includes(attribute.name)) continue
|
if (ignoreKeys.includes(attribute.name)) continue
|
||||||
const isCollection: boolean = core.class.Collection === attribute.type._class
|
const isCollection: boolean = core.class.Collection === attribute.type._class
|
||||||
@ -704,9 +754,11 @@ export function generateClassNotificationTypes (
|
|||||||
objectClass,
|
objectClass,
|
||||||
txClasses,
|
txClasses,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.PlatformNotification]: defaultEnabled.includes(attribute.name),
|
templates: {
|
||||||
[notification.providers.BrowserNotification]: false
|
textTemplate: '{body}',
|
||||||
|
htmlTemplate: '<p>{body}</p>',
|
||||||
|
subjectTemplate: '{doc} updated'
|
||||||
},
|
},
|
||||||
label: attribute.label
|
label: attribute.label
|
||||||
}
|
}
|
||||||
@ -715,5 +767,17 @@ export function generateClassNotificationTypes (
|
|||||||
}
|
}
|
||||||
const id = `${notification.class.NotificationType}_${_class}_${attribute.name}` as Ref<NotificationType>
|
const id = `${notification.class.NotificationType}_${_class}_${attribute.name}` as Ref<NotificationType>
|
||||||
builder.createDoc(notification.class.NotificationType, core.space.Model, data, id)
|
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
|
type MigrationUpgradeClient
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import notification, { notificationId, type DocNotifyContext } from '@hcengineering/notification'
|
import notification, { notificationId, type DocNotifyContext } from '@hcengineering/notification'
|
||||||
|
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
|
||||||
|
|
||||||
import { DOMAIN_NOTIFICATION } from './index'
|
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 = {
|
export const notificationOperation: MigrateOperation = {
|
||||||
async migrate (client: MigrationClient): Promise<void> {
|
async migrate (client: MigrationClient): Promise<void> {
|
||||||
await tryMigrate(client, notificationId, [
|
await tryMigrate(client, notificationId, [
|
||||||
@ -96,6 +123,10 @@ export const notificationOperation: MigrateOperation = {
|
|||||||
DOMAIN_NOTIFICATION
|
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, {
|
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, {
|
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>',
|
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
|
||||||
subjectTemplate: '{doc} was assigned to you'
|
subjectTemplate: '{doc} was assigned to you'
|
||||||
},
|
},
|
||||||
providers: {
|
defaultEnabled: true
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.EmailNotification]: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
recruit.ids.AssigneeNotification
|
recruit.ids.AssigneeNotification
|
||||||
)
|
)
|
||||||
@ -1354,9 +1350,11 @@ export function createModel (builder: Builder): void {
|
|||||||
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
||||||
objectClass: recruit.class.Applicant,
|
objectClass: recruit.class.Applicant,
|
||||||
spaceSubscribe: true,
|
spaceSubscribe: true,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.PlatformNotification]: false,
|
templates: {
|
||||||
[notification.providers.BrowserNotification]: false
|
textTemplate: '{body}',
|
||||||
|
htmlTemplate: '<p>{body}</p>',
|
||||||
|
subjectTemplate: '{title}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
recruit.ids.ApplicationCreateNotification
|
recruit.ids.ApplicationCreateNotification
|
||||||
|
@ -135,8 +135,11 @@ export function createReviewModel (builder: Builder): void {
|
|||||||
group: recruit.ids.ReviewNotificationGroup,
|
group: recruit.ids.ReviewNotificationGroup,
|
||||||
txClasses: [core.class.TxCreateDoc],
|
txClasses: [core.class.TxCreateDoc],
|
||||||
objectClass: recruit.class.Review,
|
objectClass: recruit.class.Review,
|
||||||
providers: {
|
defaultEnabled: true,
|
||||||
[notification.providers.PlatformNotification]: true
|
templates: {
|
||||||
|
textTemplate: '{body}',
|
||||||
|
htmlTemplate: '<p>{body}</p>',
|
||||||
|
subjectTemplate: '{title}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
recruit.ids.ReviewCreateNotification
|
recruit.ids.ReviewCreateNotification
|
||||||
|
@ -134,11 +134,7 @@ export function createModel (builder: Builder): void {
|
|||||||
group: request.ids.RequestNotificationGroup,
|
group: request.ids.RequestNotificationGroup,
|
||||||
label: request.string.Request,
|
label: request.string.Request,
|
||||||
allowedForAuthor: true,
|
allowedForAuthor: true,
|
||||||
providers: {
|
defaultEnabled: true,
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.EmailNotification]: true
|
|
||||||
},
|
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: '{sender} sent you a {doc}',
|
textTemplate: '{sender} sent you a {doc}',
|
||||||
htmlTemplate: '<p><b>{sender}</b> sent you a {doc}</p>',
|
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 serverCore from '@hcengineering/server-core'
|
||||||
import core from '@hcengineering/core/src/component'
|
import core from '@hcengineering/core/src/component'
|
||||||
import serverActivity from '@hcengineering/server-activity'
|
import serverActivity from '@hcengineering/server-activity'
|
||||||
|
import serverNotification from '@hcengineering/server-notification'
|
||||||
|
import activity from '@hcengineering/activity'
|
||||||
|
|
||||||
export { activityServerOperation } from './migration'
|
export { activityServerOperation } from './migration'
|
||||||
export { serverActivityId } from '@hcengineering/server-activity'
|
export { serverActivityId } from '@hcengineering/server-activity'
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
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, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverActivity.trigger.OnReactionChanged,
|
trigger: serverActivity.trigger.OnReactionChanged,
|
||||||
txMatch: {
|
txMatch: {
|
||||||
|
@ -24,14 +24,18 @@ import serverChunter from '@hcengineering/server-chunter'
|
|||||||
export { serverChunterId } from '@hcengineering/server-chunter'
|
export { serverChunterId } from '@hcengineering/server-chunter'
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
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
|
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
|
presenter: serverChunter.function.ChannelTextPresenter
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(chunter.class.ChatMessage, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||||
|
presenter: serverChunter.function.ChatMessageTextPresenter
|
||||||
|
})
|
||||||
|
|
||||||
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
||||||
chunter.class.ChatMessage,
|
chunter.class.ChatMessage,
|
||||||
core.class.Class,
|
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, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverGmail.trigger.OnMessageCreate,
|
trigger: serverGmail.trigger.OnMessageCreate,
|
||||||
txMatch: {
|
txMatch: {
|
||||||
|
@ -14,12 +14,12 @@
|
|||||||
// limitations under the License.
|
// 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 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 { 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 { type Resource } from '@hcengineering/platform'
|
||||||
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
|
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
|
||||||
import serverNotification, {
|
import serverNotification, {
|
||||||
@ -28,7 +28,9 @@ import serverNotification, {
|
|||||||
type Presenter,
|
type Presenter,
|
||||||
type TextPresenter,
|
type TextPresenter,
|
||||||
type TypeMatch,
|
type TypeMatch,
|
||||||
type NotificationContentProvider
|
type NotificationContentProvider,
|
||||||
|
type NotificationProviderResources,
|
||||||
|
type NotificationProviderFunc
|
||||||
} from '@hcengineering/server-notification'
|
} from '@hcengineering/server-notification'
|
||||||
|
|
||||||
export { serverNotificationId } 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 {
|
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, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverNotification.trigger.OnActivityNotificationViewed,
|
trigger: serverNotification.trigger.OnActivityNotificationViewed,
|
||||||
|
@ -566,10 +566,7 @@ export function createModel (builder: Builder): void {
|
|||||||
htmlTemplate: '<p>Integration with {doc} was disabled</p>',
|
htmlTemplate: '<p>Integration with {doc} was disabled</p>',
|
||||||
subjectTemplate: 'Integration with {doc} was disabled'
|
subjectTemplate: 'Integration with {doc} was disabled'
|
||||||
},
|
},
|
||||||
providers: {
|
defaultEnabled: true
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.EmailNotification]: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setting.ids.IntegrationDisabledNotification
|
setting.ids.IntegrationDisabledNotification
|
||||||
)
|
)
|
||||||
|
@ -199,13 +199,17 @@ export function createModel (builder: Builder): void {
|
|||||||
txClasses: [core.class.TxCreateDoc],
|
txClasses: [core.class.TxCreateDoc],
|
||||||
objectClass: telegram.class.Message,
|
objectClass: telegram.class.Message,
|
||||||
group: telegram.ids.NotificationGroup,
|
group: telegram.ids.NotificationGroup,
|
||||||
providers: {
|
defaultEnabled: false
|
||||||
[notification.providers.PlatformNotification]: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
telegram.ids.NewMessageNotification
|
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, {
|
builder.mixin(telegram.class.Message, core.class.Class, core.mixin.FullTextSearchContext, {
|
||||||
parentPropagate: false,
|
parentPropagate: false,
|
||||||
childProcessingAllowed: true
|
childProcessingAllowed: true
|
||||||
|
@ -369,13 +369,22 @@ export function createModel (builder: Builder): void {
|
|||||||
txClasses: [core.class.TxCreateDoc],
|
txClasses: [core.class.TxCreateDoc],
|
||||||
objectClass: time.class.ProjectToDo,
|
objectClass: time.class.ProjectToDo,
|
||||||
onlyOwn: true,
|
onlyOwn: true,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.PlatformNotification]: true
|
templates: {
|
||||||
|
textTemplate: '{body}',
|
||||||
|
htmlTemplate: '<p>{body}</p>',
|
||||||
|
subjectTemplate: '{title}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
time.ids.ToDoCreated
|
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, {
|
builder.mixin(time.class.ToDo, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||||
fields: ['user']
|
fields: ['user']
|
||||||
})
|
})
|
||||||
|
@ -149,11 +149,7 @@ function defineNotifications (builder: Builder): void {
|
|||||||
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
|
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
|
||||||
subjectTemplate: '{doc} was assigned to you'
|
subjectTemplate: '{doc} was assigned to you'
|
||||||
},
|
},
|
||||||
providers: {
|
defaultEnabled: true
|
||||||
[notification.providers.PlatformNotification]: true,
|
|
||||||
[notification.providers.BrowserNotification]: true,
|
|
||||||
[notification.providers.EmailNotification]: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
tracker.ids.AssigneeNotification
|
tracker.ids.AssigneeNotification
|
||||||
)
|
)
|
||||||
|
@ -587,10 +587,7 @@ function defineTrainingRequest (builder: Builder): void {
|
|||||||
group: training.notification.TrainingGroup,
|
group: training.notification.TrainingGroup,
|
||||||
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
|
||||||
objectClass: training.class.TrainingRequest,
|
objectClass: training.class.TrainingRequest,
|
||||||
providers: {
|
defaultEnabled: true,
|
||||||
[notification.providers.EmailNotification]: true,
|
|
||||||
[notification.providers.PlatformNotification]: true
|
|
||||||
},
|
|
||||||
templates: {
|
templates: {
|
||||||
textTemplate: '{sender} sent you a training request {doc}',
|
textTemplate: '{sender} sent you a training request {doc}',
|
||||||
htmlTemplate: '<p><b>{sender}</b> sent you a training request {doc}</p>',
|
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 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 { getClient } from '.'
|
||||||
import notification from '@hcengineering/notification'
|
import notification from '@hcengineering/notification'
|
||||||
|
|
||||||
const sounds = new Map<Asset, AudioBufferSourceNode>()
|
const sounds = new Map<Asset, AudioBufferSourceNode>()
|
||||||
const context = new AudioContext()
|
const context = new AudioContext()
|
||||||
|
|
||||||
export async function prepareSound (key: string, _class?: Ref<Class<Doc>>, loop = false, play = false): Promise<void> {
|
export async function prepareSound (key: string, _class?: Ref<Class<Doc>>, loop = false, play = false): Promise<void> {
|
||||||
const notificationType =
|
if (_class === undefined) return
|
||||||
_class !== undefined
|
|
||||||
? getClient().getModel().findAllSync(notification.class.NotificationType, { objectClass: _class })
|
const client = getClient()
|
||||||
: undefined
|
const notificationType = client
|
||||||
const notAllowed = notificationType?.[0].providers[notification.providers.SoundNotification] === false
|
.getModel()
|
||||||
if (notificationType === undefined || notAllowed) {
|
.findAllSync(notification.class.NotificationType, { objectClass: _class })[0]
|
||||||
return
|
|
||||||
}
|
if (notificationType === undefined) return
|
||||||
|
|
||||||
|
const isAllowedFn = await getResource(notification.function.IsNotificationAllowed)
|
||||||
|
const allowed: boolean = isAllowedFn(notificationType, notification.providers.SoundNotificationProvider)
|
||||||
|
|
||||||
|
if (!allowed) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const soundUrl = getMetadata(key as Asset) as string
|
const soundUrl = getMetadata(key as Asset) as string
|
||||||
const audioBuffer = await fetch(soundUrl)
|
const audioBuffer = await fetch(soundUrl)
|
||||||
|
@ -702,6 +702,7 @@ input.search {
|
|||||||
.w-32 { width: 8rem; }
|
.w-32 { width: 8rem; }
|
||||||
.w-60 { width: 15rem; }
|
.w-60 { width: 15rem; }
|
||||||
.w-85 { width: 21.25rem; }
|
.w-85 { width: 21.25rem; }
|
||||||
|
.w-120 { width: 30rem; }
|
||||||
.w-165 { width: 41.25rem; }
|
.w-165 { width: 41.25rem; }
|
||||||
.min-w-0 { min-width: 0; }
|
.min-w-0 { min-width: 0; }
|
||||||
.min-w-2 { min-width: .5rem; }
|
.min-w-2 { min-width: .5rem; }
|
||||||
|
@ -43,6 +43,8 @@
|
|||||||
"Thread": "Thread",
|
"Thread": "Thread",
|
||||||
"AddReaction": "Add reaction",
|
"AddReaction": "Add reaction",
|
||||||
"SaveForLater": "Save for later",
|
"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",
|
"Thread": "Tópico",
|
||||||
"AddReaction": "Adicionar Reacción",
|
"AddReaction": "Adicionar Reacción",
|
||||||
"SaveForLater": "Guardar para mas tarde",
|
"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",
|
"Thread": "Fil",
|
||||||
"AddReaction": "Ajouter une réaction",
|
"AddReaction": "Ajouter une réaction",
|
||||||
"SaveForLater": "Enregistrer pour plus tard",
|
"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",
|
"Thread": "Tópico",
|
||||||
"AddReaction": "Adicionar Reação",
|
"AddReaction": "Adicionar Reação",
|
||||||
"SaveForLater": "Guardar para mais tarde",
|
"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": "Обсуждение",
|
"Thread": "Обсуждение",
|
||||||
"AddReaction": "Добавить реакцию",
|
"AddReaction": "Добавить реакцию",
|
||||||
"SaveForLater": "Cохранить",
|
"SaveForLater": "Cохранить",
|
||||||
"RemoveFromLater": "Удалить из сохраненных"
|
"RemoveFromLater": "Удалить из сохраненных",
|
||||||
|
"ReactionNotificationTitle": "Реакция на {title}",
|
||||||
|
"ReactionNotificationBody": "{senderName}: {reaction}"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -43,6 +43,8 @@
|
|||||||
"Thread": "线程",
|
"Thread": "线程",
|
||||||
"AddReaction": "添加回应",
|
"AddReaction": "添加回应",
|
||||||
"SaveForLater": "稍后保存",
|
"SaveForLater": "稍后保存",
|
||||||
"RemoveFromLater": "从保存中移除"
|
"RemoveFromLater": "从保存中移除",
|
||||||
|
"ReactionNotificationTitle": "反應於 {title}",
|
||||||
|
"ReactionNotificationBody": "{senderName}: {reaction}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,7 +302,9 @@ export default plugin(activityId, {
|
|||||||
Mentions: '' as IntlString,
|
Mentions: '' as IntlString,
|
||||||
MentionedYouIn: '' as IntlString,
|
MentionedYouIn: '' as IntlString,
|
||||||
Messages: '' as IntlString,
|
Messages: '' as IntlString,
|
||||||
Thread: '' as IntlString
|
Thread: '' as IntlString,
|
||||||
|
ReactionNotificationTitle: '' as IntlString,
|
||||||
|
ReactionNotificationBody: '' as IntlString
|
||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
Activity: '' as AnyComponent,
|
Activity: '' as AnyComponent,
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
import IconAttachments from './icons/Attachments.svelte'
|
import IconAttachments from './icons/Attachments.svelte'
|
||||||
import UploadDuo from './icons/UploadDuo.svelte'
|
import UploadDuo from './icons/UploadDuo.svelte'
|
||||||
|
|
||||||
|
export let object: Doc | undefined = undefined
|
||||||
export let objectId: Ref<Doc>
|
export let objectId: Ref<Doc>
|
||||||
export let space: Ref<Space>
|
export let space: Ref<Space>
|
||||||
export let _class: Ref<Class<Doc>>
|
export let _class: Ref<Class<Doc>>
|
||||||
@ -57,7 +58,7 @@
|
|||||||
await createAttachments(
|
await createAttachments(
|
||||||
client,
|
client,
|
||||||
list,
|
list,
|
||||||
{ objectClass: _class, objectId, space },
|
{ objectClass: object?._class ?? _class, objectId, space },
|
||||||
attachmentClass,
|
attachmentClass,
|
||||||
attachmentClassOptions
|
attachmentClassOptions
|
||||||
)
|
)
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
"NewIncomingMessage": "Sent you a new email",
|
"NewIncomingMessage": "Sent you a new email",
|
||||||
"ConfigLabel": "Email",
|
"ConfigLabel": "Email",
|
||||||
"ConfigDescription": "Extension for Gmail email integration",
|
"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",
|
"NewIncomingMessage": "Te ha enviado un nuevo correo electrónico",
|
||||||
"ConfigLabel": "Correo Electrónico",
|
"ConfigLabel": "Correo Electrónico",
|
||||||
"ConfigDescription": "Extensión para la integración de correo electrónico de Gmail",
|
"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",
|
"NewIncomingMessage": "Vous a envoyé un nouvel email",
|
||||||
"ConfigLabel": "Email",
|
"ConfigLabel": "Email",
|
||||||
"ConfigDescription": "Extension pour l'intégration des emails Gmail",
|
"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",
|
"NewIncomingMessage": "Recebeu um novo email",
|
||||||
"ConfigLabel": "Email",
|
"ConfigLabel": "Email",
|
||||||
"ConfigDescription": "Extensão para a integração de email do Gmail",
|
"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": "Прислал вам новое сообщение",
|
"NewIncomingMessage": "Прислал вам новое сообщение",
|
||||||
"ConfigLabel": "Электронная почта",
|
"ConfigLabel": "Электронная почта",
|
||||||
"ConfigDescription": "Расширение по работе с Gmail электронной почтой",
|
"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": "给您发送了一封新邮件",
|
"NewIncomingMessage": "给您发送了一封新邮件",
|
||||||
"ConfigLabel": "电子邮件",
|
"ConfigLabel": "电子邮件",
|
||||||
"ConfigDescription": "Gmail 邮件集成扩展",
|
"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 { ChannelItem } from '@hcengineering/contact'
|
||||||
import type { Account, AttachedDoc, Class, Doc, Ref, Timestamp } from '@hcengineering/core'
|
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 type { IntlString, Plugin } from '@hcengineering/platform'
|
||||||
import { Metadata, plugin } from '@hcengineering/platform'
|
import { Metadata, plugin } from '@hcengineering/platform'
|
||||||
import type { Handler, IntegrationType } from '@hcengineering/setting'
|
import type { Handler, IntegrationType } from '@hcengineering/setting'
|
||||||
@ -89,7 +89,8 @@ export default plugin(gmailId, {
|
|||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
From: '' as IntlString,
|
From: '' as IntlString,
|
||||||
To: '' as IntlString
|
To: '' as IntlString,
|
||||||
|
EmailNotificationsDescription: '' as IntlString
|
||||||
},
|
},
|
||||||
integrationType: {
|
integrationType: {
|
||||||
Gmail: '' as Ref<IntegrationType>
|
Gmail: '' as Ref<IntegrationType>
|
||||||
@ -108,5 +109,8 @@ export default plugin(gmailId, {
|
|||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
GmailURL: '' as Metadata<string>
|
GmailURL: '' as Metadata<string>
|
||||||
|
},
|
||||||
|
providers: {
|
||||||
|
EmailNotificationProvider: '' as Ref<NotificationProvider>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -49,6 +49,12 @@
|
|||||||
"Unreads": "Unreads",
|
"Unreads": "Unreads",
|
||||||
"EnablePush": "Enable push notifications",
|
"EnablePush": "Enable push notifications",
|
||||||
"NotificationBlockedInBrowser": "Notifications are blocked in your browser. Please enable notifications in your browser settings",
|
"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",
|
"Push": "Push",
|
||||||
"EnablePush": "Habilitar notificaciones 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.",
|
"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",
|
"Unreads": "Non lus",
|
||||||
"EnablePush": "Activer les notifications push",
|
"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",
|
"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",
|
"Push": "Push",
|
||||||
"EnablePush": "Ativar notificações push",
|
"EnablePush": "Ativar notificações push",
|
||||||
"NotificationBlockedInBrowser": "Notificações bloqueadas no navegador. Por favor habilite las notificaciones en la configuración de su navegador.",
|
"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": "Непрочитанные",
|
"Unreads": "Непрочитанные",
|
||||||
"EnablePush": "Включить Push-уведомления",
|
"EnablePush": "Включить Push-уведомления",
|
||||||
"NotificationBlockedInBrowser": "Уведомления заблокированы в вашем браузере. Пожалуйста, включите уведомления в настройках браузера",
|
"NotificationBlockedInBrowser": "Уведомления заблокированы в вашем браузере. Пожалуйста, включите уведомления в настройках браузера",
|
||||||
"Sound": "Звук"
|
"General": "Основное",
|
||||||
|
"InboxNotificationsDescription": "Получайте персональные уведомления на свой почтовый ящик Huly.",
|
||||||
|
"PushNotificationsDescription": "Получайте персональные уведомления на рабочий стол.",
|
||||||
|
"Sound": "Звук",
|
||||||
|
"SoundNotificationsDescription": "Получайте звуковые уведомления о событиях.",
|
||||||
|
"CommonNotificationCollectionAdded": "{senderName} добавил {collection}",
|
||||||
|
"CommonNotificationCollectionRemoved": "{senderName} удалил {collection}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,12 @@
|
|||||||
"Unreads": "未读",
|
"Unreads": "未读",
|
||||||
"EnablePush": "启用推送通知",
|
"EnablePush": "启用推送通知",
|
||||||
"NotificationBlockedInBrowser": "通知在您的浏览器中被阻止。请在浏览器设置中启用通知",
|
"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,
|
BaseNotificationType,
|
||||||
NotificationGroup,
|
NotificationGroup,
|
||||||
NotificationPreferencesGroup,
|
NotificationPreferencesGroup,
|
||||||
NotificationSetting
|
NotificationTypeSetting
|
||||||
} from '@hcengineering/notification'
|
} from '@hcengineering/notification'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
Location,
|
|
||||||
Scroller,
|
|
||||||
getCurrentResolvedLocation,
|
|
||||||
navigate,
|
|
||||||
resolvedLocationStore,
|
|
||||||
Header,
|
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
defineSeparators,
|
defineSeparators,
|
||||||
settingsSeparators,
|
getCurrentResolvedLocation,
|
||||||
Separator,
|
Header,
|
||||||
|
Loading,
|
||||||
|
Location,
|
||||||
|
navigate,
|
||||||
NavItem,
|
NavItem,
|
||||||
Loading
|
resolvedLocationStore,
|
||||||
|
Scroller,
|
||||||
|
Separator,
|
||||||
|
settingsSeparators
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import notification from '../plugin'
|
|
||||||
|
import notification from '../../plugin'
|
||||||
import NotificationGroupSetting from './NotificationGroupSetting.svelte'
|
import NotificationGroupSetting from './NotificationGroupSetting.svelte'
|
||||||
|
import { providersSettings, typesSettings } from '../../utils'
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const groups: NotificationGroup[] = client.getModel().findAllSync(notification.class.NotificationGroup, {})
|
const groups: NotificationGroup[] = client.getModel().findAllSync(notification.class.NotificationGroup, {})
|
||||||
@ -46,13 +48,14 @@
|
|||||||
.getModel()
|
.getModel()
|
||||||
.findAllSync(notification.class.NotificationPreferencesGroup, {})
|
.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()
|
settings = new Map()
|
||||||
for (const value of res) {
|
for (const value of res) {
|
||||||
const arr = settings.get(value.type) ?? []
|
const arr = settings.get(value.type) ?? []
|
||||||
@ -60,7 +63,11 @@
|
|||||||
settings.set(value.type, arr)
|
settings.set(value.type, arr)
|
||||||
}
|
}
|
||||||
settings = settings
|
settings = settings
|
||||||
loading = false
|
isTypeSettingLoading = false
|
||||||
|
})
|
||||||
|
|
||||||
|
const unsubscribeProviderSetting = providersSettings.subscribe(() => {
|
||||||
|
isProviderSettingLoading = false
|
||||||
})
|
})
|
||||||
|
|
||||||
let group: Ref<NotificationGroup> | undefined = undefined
|
let group: Ref<NotificationGroup> | undefined = undefined
|
||||||
@ -74,14 +81,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(
|
const unsubscribeLocation = resolvedLocationStore.subscribe((loc) => {
|
||||||
resolvedLocationStore.subscribe((loc) => {
|
void (async (loc: Location): Promise<void> => {
|
||||||
void (async (loc: Location): Promise<void> => {
|
group = loc.path[4] as Ref<NotificationGroup>
|
||||||
group = loc.path[4] as Ref<NotificationGroup>
|
currentPreferenceGroup = undefined
|
||||||
currentPreferenceGroup = undefined
|
})(loc)
|
||||||
})(loc)
|
})
|
||||||
})
|
|
||||||
)
|
onDestroy(() => {
|
||||||
|
unsubscribeLocation()
|
||||||
|
unsubscribeTypeSetting()
|
||||||
|
unsubscribeProviderSetting()
|
||||||
|
})
|
||||||
defineSeparators('notificationSettings', settingsSeparators)
|
defineSeparators('notificationSettings', settingsSeparators)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -132,7 +143,7 @@
|
|||||||
<div class="antiNav-space" />
|
<div class="antiNav-space" />
|
||||||
</Scroller>
|
</Scroller>
|
||||||
</div>
|
</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">
|
<div class="hulyComponent-content__column content">
|
||||||
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
|
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
|
||||||
<div class="hulyComponent-content">
|
<div class="hulyComponent-content">
|
@ -17,7 +17,7 @@
|
|||||||
import { type Resources } from '@hcengineering/platform'
|
import { type Resources } from '@hcengineering/platform'
|
||||||
|
|
||||||
import Inbox from './components/inbox/Inbox.svelte'
|
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 NotificationPresenter from './components/NotificationPresenter.svelte'
|
||||||
import DocNotifyContextPresenter from './components/DocNotifyContextPresenter.svelte'
|
import DocNotifyContextPresenter from './components/DocNotifyContextPresenter.svelte'
|
||||||
import CollaboratorsChanged from './components/activity/CollaboratorsChanged.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 CommonInboxNotificationPresenter from './components/inbox/CommonInboxNotificationPresenter.svelte'
|
||||||
import NotificationCollaboratorsChanged from './components/NotificationCollaboratorsChanged.svelte'
|
import NotificationCollaboratorsChanged from './components/NotificationCollaboratorsChanged.svelte'
|
||||||
import ReactionNotificationPresenter from './components/ReactionNotificationPresenter.svelte'
|
import ReactionNotificationPresenter from './components/ReactionNotificationPresenter.svelte'
|
||||||
|
import GeneralPreferencesGroup from './components/settings/GeneralPreferencesGroup.svelte'
|
||||||
import {
|
import {
|
||||||
unsubscribe,
|
unsubscribe,
|
||||||
resolveLocation,
|
resolveLocation,
|
||||||
@ -42,7 +43,8 @@ import {
|
|||||||
readAll,
|
readAll,
|
||||||
unreadAll,
|
unreadAll,
|
||||||
checkPermission,
|
checkPermission,
|
||||||
unarchiveContextNotifications
|
unarchiveContextNotifications,
|
||||||
|
isNotificationAllowed
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
||||||
@ -63,7 +65,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
ActivityInboxNotificationPresenter,
|
ActivityInboxNotificationPresenter,
|
||||||
CommonInboxNotificationPresenter,
|
CommonInboxNotificationPresenter,
|
||||||
NotificationCollaboratorsChanged,
|
NotificationCollaboratorsChanged,
|
||||||
ReactionNotificationPresenter
|
ReactionNotificationPresenter,
|
||||||
|
GeneralPreferencesGroup
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
@ -73,7 +76,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
CanReadNotifyContext: canReadNotifyContext,
|
CanReadNotifyContext: canReadNotifyContext,
|
||||||
CanUnReadNotifyContext: canUnReadNotifyContext,
|
CanUnReadNotifyContext: canUnReadNotifyContext,
|
||||||
HasInboxNotifications: hasInboxNotifications,
|
HasInboxNotifications: hasInboxNotifications,
|
||||||
CheckPushPermission: checkPermission
|
CheckPushPermission: checkPermission,
|
||||||
|
IsNotificationAllowed: isNotificationAllowed
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
Unsubscribe: unsubscribe,
|
Unsubscribe: unsubscribe,
|
||||||
|
@ -43,9 +43,13 @@ import notification, {
|
|||||||
type DisplayInboxNotification,
|
type DisplayInboxNotification,
|
||||||
type DocNotifyContext,
|
type DocNotifyContext,
|
||||||
type InboxNotification,
|
type InboxNotification,
|
||||||
type MentionInboxNotification
|
type MentionInboxNotification,
|
||||||
|
type BaseNotificationType,
|
||||||
|
type NotificationProvider,
|
||||||
|
type NotificationProviderSetting,
|
||||||
|
type NotificationTypeSetting
|
||||||
} from '@hcengineering/notification'
|
} from '@hcengineering/notification'
|
||||||
import { MessageBox, getClient } from '@hcengineering/presentation'
|
import { MessageBox, getClient, createQuery } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
getCurrentLocation,
|
getCurrentLocation,
|
||||||
getLocation,
|
getLocation,
|
||||||
@ -65,6 +69,23 @@ import { getObjectLinkId } from '@hcengineering/view-resources'
|
|||||||
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
import { InboxNotificationsClientImpl } from './inboxNotificationsClient'
|
||||||
import { type InboxData, type InboxNotificationsFilter } from './types'
|
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> {
|
export async function hasDocNotifyContextPinAction (docNotifyContext: DocNotifyContext): Promise<boolean> {
|
||||||
if (docNotifyContext.hidden) {
|
if (docNotifyContext.hidden) {
|
||||||
return false
|
return false
|
||||||
@ -777,3 +798,39 @@ export function notificationsComparator (notifications1: InboxNotification, noti
|
|||||||
|
|
||||||
return 0
|
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??)
|
// allowed to change setting (probably we should show it, but disable toggle??)
|
||||||
hidden: boolean
|
hidden: boolean
|
||||||
group: Ref<NotificationGroup>
|
group: Ref<NotificationGroup>
|
||||||
// allowed providers and default value for it
|
defaultEnabled: boolean
|
||||||
providers: Record<Ref<NotificationProvider>, boolean>
|
|
||||||
// templates for email (and browser/push?)
|
// templates for email (and browser/push?)
|
||||||
templates?: NotificationTemplate
|
templates?: NotificationTemplate
|
||||||
}
|
}
|
||||||
@ -152,19 +151,30 @@ export interface NotificationType extends BaseNotificationType {
|
|||||||
|
|
||||||
export interface CommonNotificationType extends BaseNotificationType {}
|
export interface CommonNotificationType extends BaseNotificationType {}
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface NotificationProvider extends Doc {
|
export interface NotificationProvider extends Doc {
|
||||||
label: IntlString
|
label: IntlString
|
||||||
|
description: IntlString
|
||||||
|
icon: Asset
|
||||||
|
defaultEnabled: boolean
|
||||||
depends?: Ref<NotificationProvider>
|
depends?: Ref<NotificationProvider>
|
||||||
onChange?: Resource<(value: boolean) => Promise<boolean>>
|
canDisable: boolean
|
||||||
|
ignoreAll?: boolean
|
||||||
|
order: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface NotificationProviderDefaults extends Doc {
|
||||||
* @public
|
provider: Ref<NotificationProvider>
|
||||||
*/
|
excludeIgnore?: Ref<BaseNotificationType>[]
|
||||||
export interface NotificationSetting extends Preference {
|
ignoredTypes: Ref<BaseNotificationType>[]
|
||||||
|
enabledTypes: Ref<BaseNotificationType>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationProviderSetting extends Preference {
|
||||||
|
attachedTo: Ref<NotificationProvider>
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationTypeSetting extends Preference {
|
||||||
attachedTo: Ref<NotificationProvider>
|
attachedTo: Ref<NotificationProvider>
|
||||||
type: Ref<BaseNotificationType>
|
type: Ref<BaseNotificationType>
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
@ -329,8 +339,6 @@ const notification = plugin(notificationId, {
|
|||||||
BaseNotificationType: '' as Ref<Class<BaseNotificationType>>,
|
BaseNotificationType: '' as Ref<Class<BaseNotificationType>>,
|
||||||
NotificationType: '' as Ref<Class<NotificationType>>,
|
NotificationType: '' as Ref<Class<NotificationType>>,
|
||||||
CommonNotificationType: '' as Ref<Class<CommonNotificationType>>,
|
CommonNotificationType: '' as Ref<Class<CommonNotificationType>>,
|
||||||
NotificationProvider: '' as Ref<Class<NotificationProvider>>,
|
|
||||||
NotificationSetting: '' as Ref<Class<NotificationSetting>>,
|
|
||||||
NotificationGroup: '' as Ref<Class<NotificationGroup>>,
|
NotificationGroup: '' as Ref<Class<NotificationGroup>>,
|
||||||
NotificationPreferencesGroup: '' as Ref<Class<NotificationPreferencesGroup>>,
|
NotificationPreferencesGroup: '' as Ref<Class<NotificationPreferencesGroup>>,
|
||||||
DocNotifyContext: '' as Ref<Class<DocNotifyContext>>,
|
DocNotifyContext: '' as Ref<Class<DocNotifyContext>>,
|
||||||
@ -338,7 +346,11 @@ const notification = plugin(notificationId, {
|
|||||||
ActivityInboxNotification: '' as Ref<Class<ActivityInboxNotification>>,
|
ActivityInboxNotification: '' as Ref<Class<ActivityInboxNotification>>,
|
||||||
CommonInboxNotification: '' as Ref<Class<CommonInboxNotification>>,
|
CommonInboxNotification: '' as Ref<Class<CommonInboxNotification>>,
|
||||||
ActivityNotificationViewlet: '' as Ref<Class<ActivityNotificationViewlet>>,
|
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: {
|
ids: {
|
||||||
NotificationSettings: '' as Ref<Doc>,
|
NotificationSettings: '' as Ref<Doc>,
|
||||||
@ -350,10 +362,9 @@ const notification = plugin(notificationId, {
|
|||||||
PushPublicKey: '' as Metadata<string>
|
PushPublicKey: '' as Metadata<string>
|
||||||
},
|
},
|
||||||
providers: {
|
providers: {
|
||||||
PlatformNotification: '' as Ref<NotificationProvider>,
|
InboxNotificationProvider: '' as Ref<NotificationProvider>,
|
||||||
BrowserNotification: '' as Ref<NotificationProvider>,
|
PushNotificationProvider: '' as Ref<NotificationProvider>,
|
||||||
EmailNotification: '' as Ref<NotificationProvider>,
|
SoundNotificationProvider: '' as Ref<NotificationProvider>
|
||||||
SoundNotification: '' as Ref<NotificationProvider>
|
|
||||||
},
|
},
|
||||||
integrationType: {
|
integrationType: {
|
||||||
MobileApp: '' as Ref<IntegrationType>
|
MobileApp: '' as Ref<IntegrationType>
|
||||||
@ -364,7 +375,8 @@ const notification = plugin(notificationId, {
|
|||||||
CollaboratorsChanged: '' as AnyComponent,
|
CollaboratorsChanged: '' as AnyComponent,
|
||||||
DocNotifyContextPresenter: '' as AnyComponent,
|
DocNotifyContextPresenter: '' as AnyComponent,
|
||||||
NotificationCollaboratorsChanged: '' as AnyComponent,
|
NotificationCollaboratorsChanged: '' as AnyComponent,
|
||||||
ReactionNotificationPresenter: '' as AnyComponent
|
ReactionNotificationPresenter: '' as AnyComponent,
|
||||||
|
GeneralPreferencesGroup: '' as AnyComponent
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
PinDocNotifyContext: '' as Ref<Action>,
|
PinDocNotifyContext: '' as Ref<Action>,
|
||||||
@ -402,6 +414,12 @@ const notification = plugin(notificationId, {
|
|||||||
YouAddedCollaborators: '' as IntlString,
|
YouAddedCollaborators: '' as IntlString,
|
||||||
YouRemovedCollaborators: '' as IntlString,
|
YouRemovedCollaborators: '' as IntlString,
|
||||||
Push: '' 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
|
Sound: '' as IntlString
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
@ -410,6 +428,9 @@ const notification = plugin(notificationId, {
|
|||||||
GetInboxNotificationsClient: '' as Resource<InboxNotificationsClientFactory>,
|
GetInboxNotificationsClient: '' as Resource<InboxNotificationsClientFactory>,
|
||||||
HasInboxNotifications: '' as Resource<
|
HasInboxNotifications: '' as Resource<
|
||||||
(notificationsByContext: Map<Ref<DocNotifyContext>, InboxNotification[]>) => Promise<boolean>
|
(notificationsByContext: Map<Ref<DocNotifyContext>, InboxNotification[]>) => Promise<boolean>
|
||||||
|
>,
|
||||||
|
IsNotificationAllowed: '' as Resource<
|
||||||
|
(type: BaseNotificationType, providerId: Ref<NotificationProvider>) => boolean
|
||||||
>
|
>
|
||||||
},
|
},
|
||||||
resolver: {
|
resolver: {
|
||||||
|
@ -36,9 +36,12 @@ import type { TriggerControl } from '@hcengineering/server-core'
|
|||||||
import {
|
import {
|
||||||
createCollabDocInfo,
|
createCollabDocInfo,
|
||||||
createCollaboratorNotifications,
|
createCollaboratorNotifications,
|
||||||
|
getTextPresenter,
|
||||||
removeDocInboxNotifications
|
removeDocInboxNotifications
|
||||||
} from '@hcengineering/server-notification-resources'
|
} from '@hcengineering/server-notification-resources'
|
||||||
import { PersonAccount } from '@hcengineering/contact'
|
import { PersonAccount } from '@hcengineering/contact'
|
||||||
|
import { NotificationContent } from '@hcengineering/notification'
|
||||||
|
import { getResource, translate } from '@hcengineering/platform'
|
||||||
|
|
||||||
import { getDocUpdateAction, getTxAttributesUpdates } from './utils'
|
import { getDocUpdateAction, getTxAttributesUpdates } from './utils'
|
||||||
import { ReferenceTrigger } from './references'
|
import { ReferenceTrigger } from './references'
|
||||||
@ -48,11 +51,16 @@ export async function OnReactionChanged (originTx: Tx, control: TriggerControl):
|
|||||||
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Reaction>
|
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Reaction>
|
||||||
|
|
||||||
if (innerTx._class === core.class.TxCreateDoc) {
|
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) {
|
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 []
|
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))
|
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'
|
export * from './references'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
@ -420,5 +458,8 @@ export default async () => ({
|
|||||||
ActivityMessagesHandler,
|
ActivityMessagesHandler,
|
||||||
OnDocRemoved,
|
OnDocRemoved,
|
||||||
OnReactionChanged
|
OnReactionChanged
|
||||||
|
},
|
||||||
|
function: {
|
||||||
|
ReactionNotificationContentProvider
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -21,6 +21,7 @@ import core, {
|
|||||||
CollaborativeDoc,
|
CollaborativeDoc,
|
||||||
Data,
|
Data,
|
||||||
Doc,
|
Doc,
|
||||||
|
generateId,
|
||||||
Hierarchy,
|
Hierarchy,
|
||||||
Ref,
|
Ref,
|
||||||
Space,
|
Space,
|
||||||
@ -35,7 +36,7 @@ import core, {
|
|||||||
TxUpdateDoc,
|
TxUpdateDoc,
|
||||||
Type
|
Type
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, { MentionInboxNotification } from '@hcengineering/notification'
|
import notification, { CommonInboxNotification, MentionInboxNotification } from '@hcengineering/notification'
|
||||||
import {
|
import {
|
||||||
extractReferences,
|
extractReferences,
|
||||||
markupToPmNode,
|
markupToPmNode,
|
||||||
@ -52,7 +53,8 @@ import {
|
|||||||
shouldNotifyCommon,
|
shouldNotifyCommon,
|
||||||
isShouldNotifyTx,
|
isShouldNotifyTx,
|
||||||
NotifyResult,
|
NotifyResult,
|
||||||
createPushFromInbox
|
applyNotificationProviders,
|
||||||
|
getNotificationContent
|
||||||
} from '@hcengineering/server-notification-resources'
|
} from '@hcengineering/server-notification-resources'
|
||||||
|
|
||||||
async function getPersonAccount (person: Ref<Person>, control: TriggerControl): Promise<PersonAccount | undefined> {
|
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 notifyResult = await shouldNotifyCommon(control, receiver._id, notification.ids.MentionCommonNotificationType)
|
||||||
const messageNotifyResult = await getMessageNotifyResult(reference, receiver, control, originTx, doc)
|
const messageNotifyResult = await getMessageNotifyResult(reference, receiver, control, originTx, doc)
|
||||||
|
|
||||||
if (messageNotifyResult.allowed) {
|
for (const [provider] of messageNotifyResult.entries()) {
|
||||||
notifyResult.allowed = false
|
if (notifyResult.has(provider)) {
|
||||||
}
|
notifyResult.delete(provider)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,17 +328,17 @@ async function getMessageNotifyResult (
|
|||||||
reference.attachedDocId === undefined ||
|
reference.attachedDocId === undefined ||
|
||||||
tx._class !== core.class.TxCreateDoc
|
tx._class !== core.class.TxCreateDoc
|
||||||
) {
|
) {
|
||||||
return { allowed: false, emails: [], push: false }
|
return new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
const mixin = control.hierarchy.as(doc, notification.mixin.Collaborators)
|
const mixin = control.hierarchy.as(doc, notification.mixin.Collaborators)
|
||||||
|
|
||||||
if (mixin === undefined || !mixin.collaborators.includes(account._id)) {
|
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)) {
|
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)
|
return await isShouldNotifyTx(control, tx, originTx, doc, account, false, false, undefined)
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import { Plugin, Resource, plugin } from '@hcengineering/platform'
|
import { Plugin, Resource, plugin } from '@hcengineering/platform'
|
||||||
import type { TriggerFunc } from '@hcengineering/server-core'
|
import type { TriggerFunc } from '@hcengineering/server-core'
|
||||||
|
import { NotificationContentProvider } from '@hcengineering/server-notification'
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
@ -33,5 +34,8 @@ export default plugin(serverActivityId, {
|
|||||||
OnDocRemoved: '' as Resource<TriggerFunc>,
|
OnDocRemoved: '' as Resource<TriggerFunc>,
|
||||||
OnReactionChanged: '' as Resource<TriggerFunc>,
|
OnReactionChanged: '' as Resource<TriggerFunc>,
|
||||||
ReferenceTrigger: '' as Resource<TriggerFunc>
|
ReferenceTrigger: '' as Resource<TriggerFunc>
|
||||||
|
},
|
||||||
|
function: {
|
||||||
|
ReactionNotificationContentProvider: '' as Resource<NotificationContentProvider>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -37,17 +37,18 @@ import core, {
|
|||||||
TxUpdateDoc
|
TxUpdateDoc
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, { Collaborators, NotificationContent } from '@hcengineering/notification'
|
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 serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||||
import {
|
import {
|
||||||
createCollaboratorNotifications,
|
createCollaboratorNotifications,
|
||||||
getDocCollaborators,
|
getDocCollaborators,
|
||||||
getMixinTx
|
getMixinTx
|
||||||
} from '@hcengineering/server-notification-resources'
|
} from '@hcengineering/server-notification-resources'
|
||||||
import { stripTags } from '@hcengineering/text'
|
import { markupToText, stripTags } from '@hcengineering/text'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
|
|
||||||
import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
|
import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
|
||||||
|
import { encodeObjectURI } from '@hcengineering/view'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -55,9 +56,10 @@ import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
|
|||||||
export async function channelHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
export async function channelHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||||
const channel = doc as ChunterSpace
|
const channel = doc as ChunterSpace
|
||||||
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
|
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)
|
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> {
|
export async function channelTextPresenter (doc: Doc): Promise<string> {
|
||||||
const channel = doc as ChunterSpace
|
const channel = doc as ChunterSpace
|
||||||
|
|
||||||
|
if (channel._class === chunter.class.DirectMessage) {
|
||||||
|
return await translate(chunter.string.Direct, {})
|
||||||
|
}
|
||||||
|
|
||||||
return `${channel.name}`
|
return `${channel.name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function ChatMessageTextPresenter (doc: ChatMessage): Promise<string> {
|
||||||
|
return markupToText(doc.message)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -456,6 +467,7 @@ export default async () => ({
|
|||||||
CommentRemove,
|
CommentRemove,
|
||||||
ChannelHTMLPresenter: channelHTMLPresenter,
|
ChannelHTMLPresenter: channelHTMLPresenter,
|
||||||
ChannelTextPresenter: channelTextPresenter,
|
ChannelTextPresenter: channelTextPresenter,
|
||||||
ChunterNotificationContentProvider: getChunterNotificationContent
|
ChunterNotificationContentProvider: getChunterNotificationContent,
|
||||||
|
ChatMessageTextPresenter
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -37,6 +37,7 @@ export default plugin(serverChunterId, {
|
|||||||
CommentRemove: '' as Resource<ObjectDDParticipantFunc>,
|
CommentRemove: '' as Resource<ObjectDDParticipantFunc>,
|
||||||
ChannelHTMLPresenter: '' as Resource<Presenter>,
|
ChannelHTMLPresenter: '' as Resource<Presenter>,
|
||||||
ChannelTextPresenter: '' 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/core": "^0.6.32",
|
||||||
"@hcengineering/platform": "^0.6.11",
|
"@hcengineering/platform": "^0.6.11",
|
||||||
"@hcengineering/server-core": "^0.6.1",
|
"@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/notification": "^0.6.23",
|
||||||
"@hcengineering/contact": "^0.6.24",
|
"@hcengineering/contact": "^0.6.24",
|
||||||
"@hcengineering/gmail": "^0.6.22"
|
"@hcengineering/gmail": "^0.6.22"
|
||||||
|
@ -13,10 +13,11 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import contact, { Channel } from '@hcengineering/contact'
|
import contact, { Channel, formatName } from '@hcengineering/contact'
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
Class,
|
Class,
|
||||||
|
concatLink,
|
||||||
Doc,
|
Doc,
|
||||||
DocumentQuery,
|
DocumentQuery,
|
||||||
FindOptions,
|
FindOptions,
|
||||||
@ -29,7 +30,10 @@ import {
|
|||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import gmail, { Message } from '@hcengineering/gmail'
|
import gmail, { Message } from '@hcengineering/gmail'
|
||||||
import { TriggerControl } from '@hcengineering/server-core'
|
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
|
* @public
|
||||||
@ -131,6 +135,94 @@ export async function IsIncomingMessage (
|
|||||||
return message.incoming && message.sendOn > (doc.createdOn ?? doc.modifiedOn)
|
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
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export default async () => ({
|
export default async () => ({
|
||||||
trigger: {
|
trigger: {
|
||||||
@ -138,6 +230,7 @@ export default async () => ({
|
|||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
IsIncomingMessage,
|
IsIncomingMessage,
|
||||||
FindMessages
|
FindMessages,
|
||||||
|
SendEmailNotifications
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
import { ObjectDDParticipantFunc, TriggerFunc } from '@hcengineering/server-core'
|
import { ObjectDDParticipantFunc, TriggerFunc } from '@hcengineering/server-core'
|
||||||
import { TypeMatchFunc } from '@hcengineering/server-notification'
|
import { NotificationProviderFunc, TypeMatchFunc } from '@hcengineering/server-notification'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -33,6 +33,7 @@ export default plugin(serverGmailId, {
|
|||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
IsIncomingMessage: '' as TypeMatchFunc,
|
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/contact": "^0.6.24",
|
||||||
"@hcengineering/server-notification": "^0.6.1",
|
"@hcengineering/server-notification": "^0.6.1",
|
||||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
"@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/notification": "^0.6.23",
|
||||||
"@hcengineering/hr": "^0.6.19"
|
"@hcengineering/hr": "^0.6.19"
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,9 @@ import notification, { NotificationType } from '@hcengineering/notification'
|
|||||||
import { translate } from '@hcengineering/platform'
|
import { translate } from '@hcengineering/platform'
|
||||||
import { TriggerControl } from '@hcengineering/server-core'
|
import { TriggerControl } from '@hcengineering/server-core'
|
||||||
import { getEmployee, getPersonAccountById } from '@hcengineering/server-notification'
|
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 (
|
async function getOldDepartment (
|
||||||
currentTx: TxMixin<Employee, Staff> | TxUpdateDoc<Employee>,
|
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 (
|
async function sendEmailNotifications (
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
sender: PersonAccount,
|
sender: PersonAccount,
|
||||||
doc: Request | PublicHoliday,
|
doc: Request | PublicHoliday,
|
||||||
space: Ref<Department>,
|
space: Ref<Department>,
|
||||||
type: Ref<NotificationType>
|
typeId: Ref<NotificationType>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const contacts = new Set<Ref<Contact>>()
|
const contacts = new Set<Ref<Contact>>()
|
||||||
const departments = await buildHierarchy(space, control)
|
const departments = await buildHierarchy(space, control)
|
||||||
@ -268,8 +271,14 @@ async function sendEmailNotifications (
|
|||||||
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, {
|
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, {
|
||||||
person: { $in: Array.from(contacts.values()) as Ref<Employee>[] }
|
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) {
|
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) {
|
if (!allowed) {
|
||||||
contacts.delete(account.person)
|
contacts.delete(account.person)
|
||||||
}
|
}
|
||||||
@ -283,7 +292,7 @@ async function sendEmailNotifications (
|
|||||||
const senderPerson = (await control.findAll(contact.class.Person, { _id: sender.person }))[0]
|
const senderPerson = (await control.findAll(contact.class.Person, { _id: sender.person }))[0]
|
||||||
|
|
||||||
const senderName = senderPerson !== undefined ? formatName(senderPerson.name, control.branding?.lastNameFirst) : ''
|
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
|
if (content === undefined) return
|
||||||
|
|
||||||
for (const channel of channels) {
|
for (const channel of channels) {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// 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, {
|
import core, {
|
||||||
Account,
|
Account,
|
||||||
Ref,
|
Ref,
|
||||||
@ -25,10 +25,7 @@ import core, {
|
|||||||
TxUpdateDoc,
|
TxUpdateDoc,
|
||||||
UserStatus
|
UserStatus
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification from '@hcengineering/notification'
|
|
||||||
import { translate } from '@hcengineering/platform'
|
|
||||||
import { TriggerControl } from '@hcengineering/server-core'
|
import { TriggerControl } from '@hcengineering/server-core'
|
||||||
import { createPushNotification, isAllowed } from '@hcengineering/server-notification-resources'
|
|
||||||
import love, {
|
import love, {
|
||||||
Invite,
|
Invite,
|
||||||
JoinRequest,
|
JoinRequest,
|
||||||
@ -39,7 +36,10 @@ import love, {
|
|||||||
isOffice,
|
isOffice,
|
||||||
loveId
|
loveId
|
||||||
} from '@hcengineering/love'
|
} from '@hcengineering/love'
|
||||||
|
import { createPushNotification, isAllowed } from '@hcengineering/server-notification-resources'
|
||||||
|
import notification from '@hcengineering/notification'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
|
import { translate } from '@hcengineering/platform'
|
||||||
|
|
||||||
export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||||
const actualTx = TxProcessor.extractTx(tx) as TxMixin<Person, Employee>
|
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 res: Tx[] = []
|
||||||
const from = (await control.findAll(contact.class.Person, { _id: request.person }))[0]
|
const from = (await control.findAll(contact.class.Person, { _id: request.person }))[0]
|
||||||
if (from === undefined) return []
|
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) {
|
for (const user of roomInfo.persons) {
|
||||||
const userAcc = await control.modelDb.findOne(contact.class.PersonAccount, { person: user })
|
const userAcc = await control.modelDb.findOne(contact.class.PersonAccount, { person: user })
|
||||||
if (userAcc === undefined) continue
|
if (userAcc === undefined) continue
|
||||||
if (
|
if (await isAllowed(control, userAcc._id, type, provider)) {
|
||||||
await isAllowed(
|
|
||||||
control,
|
|
||||||
userAcc._id,
|
|
||||||
love.ids.KnockNotification,
|
|
||||||
notification.providers.BrowserNotification
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||||
const title = await translate(love.string.KnockingLabel, {})
|
const title = await translate(love.string.KnockingLabel, {})
|
||||||
const body = await translate(love.string.IsKnocking, {
|
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 })
|
const userAcc = await control.modelDb.findOne(contact.class.PersonAccount, { person: target._id })
|
||||||
if (userAcc === undefined) return []
|
if (userAcc === undefined) return []
|
||||||
const from = (await control.findAll(contact.class.Person, { _id: invite.from }))[0]
|
const from = (await control.findAll(contact.class.Person, { _id: invite.from }))[0]
|
||||||
if (
|
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||||
await isAllowed(control, userAcc._id, love.ids.InviteNotification, notification.providers.BrowserNotification)
|
_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 path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||||
const title = await translate(love.string.InivitingLabel, {})
|
const title = await translate(love.string.InivitingLabel, {})
|
||||||
const body =
|
const body =
|
||||||
|
@ -18,8 +18,6 @@ import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/acti
|
|||||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||||
import contact, {
|
import contact, {
|
||||||
type AvatarInfo,
|
type AvatarInfo,
|
||||||
Employee,
|
|
||||||
formatName,
|
|
||||||
getAvatarProviderId,
|
getAvatarProviderId,
|
||||||
getGravatarUrl,
|
getGravatarUrl,
|
||||||
Person,
|
Person,
|
||||||
@ -74,7 +72,9 @@ import serverCore from '@hcengineering/server-core'
|
|||||||
import serverNotification, {
|
import serverNotification, {
|
||||||
getPersonAccount,
|
getPersonAccount,
|
||||||
getPersonAccountById,
|
getPersonAccountById,
|
||||||
NOTIFICATION_BODY_SIZE
|
NOTIFICATION_BODY_SIZE,
|
||||||
|
UserInfo,
|
||||||
|
NOTIFICATION_TITLE_SIZE
|
||||||
} from '@hcengineering/server-notification'
|
} from '@hcengineering/server-notification'
|
||||||
import serverView from '@hcengineering/server-view'
|
import serverView from '@hcengineering/server-view'
|
||||||
import { stripTags } from '@hcengineering/text'
|
import { stripTags } from '@hcengineering/text'
|
||||||
@ -82,7 +82,7 @@ import { encodeObjectURI } from '@hcengineering/view'
|
|||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
import webpush, { WebPushError } from 'web-push'
|
import webpush, { WebPushError } from 'web-push'
|
||||||
|
|
||||||
import { Content, NotifyParams, NotifyResult, UserInfo } from './types'
|
import { Content, NotifyParams, NotifyResult } from './types'
|
||||||
import {
|
import {
|
||||||
getHTMLPresenter,
|
getHTMLPresenter,
|
||||||
getNotificationContent,
|
getNotificationContent,
|
||||||
@ -128,45 +128,47 @@ export async function getCommonNotificationTxes (
|
|||||||
notifyResult: NotifyResult,
|
notifyResult: NotifyResult,
|
||||||
_class = notification.class.CommonInboxNotification
|
_class = notification.class.CommonInboxNotification
|
||||||
): Promise<Tx[]> {
|
): Promise<Tx[]> {
|
||||||
|
if (notifyResult.size === 0 || !notifyResult.has(notification.providers.InboxNotificationProvider)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const res: Tx[] = []
|
const res: Tx[] = []
|
||||||
|
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo })
|
||||||
|
|
||||||
if (notifyResult.allowed) {
|
const notificationTx = await pushInboxNotifications(
|
||||||
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo })
|
control,
|
||||||
|
res,
|
||||||
|
receiver,
|
||||||
|
attachedTo,
|
||||||
|
attachedToClass,
|
||||||
|
space,
|
||||||
|
notifyContexts,
|
||||||
|
data,
|
||||||
|
_class,
|
||||||
|
modifiedOn
|
||||||
|
)
|
||||||
|
|
||||||
await pushInboxNotifications(
|
if (notificationTx !== undefined) {
|
||||||
control,
|
const notificationData = TxProcessor.createDoc2Doc(notificationTx)
|
||||||
res,
|
await applyNotificationProviders(
|
||||||
receiver,
|
notificationData,
|
||||||
|
notifyResult,
|
||||||
attachedTo,
|
attachedTo,
|
||||||
attachedToClass,
|
attachedToClass,
|
||||||
space,
|
control,
|
||||||
notifyContexts,
|
res,
|
||||||
data,
|
doc,
|
||||||
_class,
|
receiver,
|
||||||
modifiedOn,
|
sender
|
||||||
sender,
|
|
||||||
notifyResult.push
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getTextPart (doc: Doc, control: TriggerControl): Promise<string | undefined> {
|
async function getTextPart (doc: Doc, control: TriggerControl): Promise<string | undefined> {
|
||||||
const TextPresenter = getTextPresenter(doc._class, control.hierarchy)
|
const TextPresenter = getTextPresenter(doc._class, control.hierarchy)
|
||||||
|
console.log({ _class: doc._class, presenter: TextPresenter })
|
||||||
if (TextPresenter === undefined) return
|
if (TextPresenter === undefined) return
|
||||||
return await (
|
return await (
|
||||||
await getResource(TextPresenter.presenter)
|
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
|
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)
|
let res = replaceAll(template, '{sender}', sender)
|
||||||
res = replaceAll(res, '{doc}', doc)
|
res = replaceAll(res, '{doc}', doc)
|
||||||
res = replaceAll(res, '{data}', data)
|
res = replaceAll(res, '{data}', data)
|
||||||
|
|
||||||
|
for (const key in params) {
|
||||||
|
res = replaceAll(res, `{${key}}`, params[key])
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function getContent (
|
export async function getContentByTemplate (
|
||||||
doc: Doc | undefined,
|
doc: Doc | undefined,
|
||||||
sender: string,
|
sender: string,
|
||||||
type: Ref<BaseNotificationType>,
|
type: Ref<BaseNotificationType>,
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
data: string
|
data: string,
|
||||||
|
notificationData?: InboxNotification
|
||||||
): Promise<Content | undefined> {
|
): Promise<Content | undefined> {
|
||||||
if (doc === undefined) return
|
if (doc === undefined) return
|
||||||
const notificationType = control.modelDb.getObject(type)
|
const notificationType = control.modelDb.getObject(type)
|
||||||
|
if (notificationType.templates === undefined) return
|
||||||
|
|
||||||
const textPart = await getTextPart(doc, control)
|
const textPart = await getTextPart(doc, control)
|
||||||
if (textPart === undefined) return
|
if (textPart === undefined) return
|
||||||
if (notificationType.templates === undefined) return
|
const params =
|
||||||
const text = fillTemplate(notificationType.templates.textTemplate, sender, textPart, data)
|
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 htmlPart = await getHtmlPart(doc, control)
|
||||||
const html = fillTemplate(notificationType.templates.htmlTemplate, sender, htmlPart ?? textPart, data)
|
const html = fillTemplate(notificationType.templates.htmlTemplate, sender, htmlPart ?? textPart, data, params)
|
||||||
const subject = fillTemplate(notificationType.templates.subjectTemplate, sender, textPart, data)
|
const subject = fillTemplate(notificationType.templates.subjectTemplate, sender, textPart, data, params)
|
||||||
|
|
||||||
|
if (subject === '') return
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text,
|
text,
|
||||||
html,
|
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>[]> {
|
async function getValueCollaborators (value: any, attr: AnyAttribute, control: TriggerControl): Promise<Ref<Account>[]> {
|
||||||
const hierarchy = control.hierarchy
|
const hierarchy = control.hierarchy
|
||||||
if (attr.type._class === core.class.RefTo) {
|
if (attr.type._class === core.class.RefTo) {
|
||||||
@ -371,11 +339,8 @@ export async function pushInboxNotifications (
|
|||||||
data: Partial<Data<InboxNotification>>,
|
data: Partial<Data<InboxNotification>>,
|
||||||
_class: Ref<Class<InboxNotification>>,
|
_class: Ref<Class<InboxNotification>>,
|
||||||
modifiedOn: Timestamp,
|
modifiedOn: Timestamp,
|
||||||
sender: UserInfo,
|
shouldUpdateTimestamp = true
|
||||||
shouldPush: boolean,
|
): Promise<TxCreateDoc<InboxNotification> | undefined> {
|
||||||
shouldUpdateTimestamp = true,
|
|
||||||
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
|
||||||
): Promise<void> {
|
|
||||||
const account = target.account
|
const account = target.account
|
||||||
|
|
||||||
if (account === undefined) {
|
if (account === undefined) {
|
||||||
@ -427,28 +392,14 @@ export async function pushInboxNotifications (
|
|||||||
}
|
}
|
||||||
const notificationTx = control.txFactory.createTxCreateDoc(_class, space, notificationData)
|
const notificationTx = control.txFactory.createTxCreateDoc(_class, space, notificationData)
|
||||||
res.push(notificationTx)
|
res.push(notificationTx)
|
||||||
if (shouldPush) {
|
|
||||||
// const now = Date.now()
|
return notificationTx
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 title: string = ''
|
||||||
let body: string = ''
|
let body: string = ''
|
||||||
|
|
||||||
@ -466,10 +417,12 @@ async function activityInboxNotificationToText (doc: Data<ActivityInboxNotificat
|
|||||||
body = await translate(doc.body, params)
|
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 title: string = ''
|
||||||
let body: string = ''
|
let body: string = ''
|
||||||
|
|
||||||
@ -492,13 +445,13 @@ async function commonInboxNotificationToText (doc: Data<CommonInboxNotification>
|
|||||||
if (doc.message != null) {
|
if (doc.message != null) {
|
||||||
body = await translate(doc.message, params)
|
body = await translate(doc.message, params)
|
||||||
}
|
}
|
||||||
return [title, body]
|
return { ...params, title, body }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mentionInboxNotificationToText (
|
async function mentionInboxNotificationToText (
|
||||||
doc: Data<MentionInboxNotification>,
|
doc: Data<MentionInboxNotification>,
|
||||||
control: TriggerControl
|
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]
|
let obj = (await control.findAll(doc.mentionedInClass, { _id: doc.mentionedIn }, { limit: 1 }))[0]
|
||||||
if (obj !== undefined) {
|
if (obj !== undefined) {
|
||||||
if (control.hierarchy.isDerived(obj._class, chunter.class.ChatMessage)) {
|
if (control.hierarchy.isDerived(obj._class, chunter.class.ChatMessage)) {
|
||||||
@ -525,6 +478,22 @@ async function mentionInboxNotificationToText (
|
|||||||
return await commonInboxNotificationToText(doc)
|
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 (
|
export async function createPushFromInbox (
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
target: UserInfo,
|
target: UserInfo,
|
||||||
@ -536,15 +505,8 @@ export async function createPushFromInbox (
|
|||||||
_id: Ref<Doc>,
|
_id: Ref<Doc>,
|
||||||
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
||||||
): Promise<Tx | undefined> {
|
): Promise<Tx | undefined> {
|
||||||
let title: string = ''
|
const { title, body } = await getTranslatedNotificationContent(data, _class, control)
|
||||||
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>)
|
|
||||||
}
|
|
||||||
if (title === '' || body === '') {
|
if (title === '' || body === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -637,7 +599,7 @@ async function sendPushToSubscription (
|
|||||||
try {
|
try {
|
||||||
await webpush.sendNotification(subscription, JSON.stringify(data))
|
await webpush.sendNotification(subscription, JSON.stringify(data))
|
||||||
} catch (err) {
|
} 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')) {
|
if (err instanceof WebPushError && err.body.includes('expired')) {
|
||||||
const tx = control.txFactory.createTxRemoveDoc(subscription._class, subscription.space, subscription._id)
|
const tx = control.txFactory.createTxRemoveDoc(subscription._class, subscription.space, subscription._id)
|
||||||
await control.apply([tx])
|
await control.apply([tx])
|
||||||
@ -656,39 +618,78 @@ export async function pushActivityInboxNotifications (
|
|||||||
sender: UserInfo,
|
sender: UserInfo,
|
||||||
object: Doc,
|
object: Doc,
|
||||||
docNotifyContexts: DocNotifyContext[],
|
docNotifyContexts: DocNotifyContext[],
|
||||||
activityMessages: ActivityMessage[],
|
activityMessage: ActivityMessage,
|
||||||
shouldUpdateTimestamp: boolean,
|
shouldUpdateTimestamp: boolean
|
||||||
shouldPush: boolean,
|
): Promise<TxCreateDoc<InboxNotification> | undefined> {
|
||||||
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
|
||||||
): Promise<void> {
|
|
||||||
if (target.account === undefined) {
|
if (target.account === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const activityMessage of activityMessages) {
|
const content = await getNotificationContent(originTx, target.account, sender, object, control)
|
||||||
const content = await getNotificationContent(originTx, target.account, sender, object, control)
|
const data: Partial<Data<ActivityInboxNotification>> = {
|
||||||
const data: Partial<Data<ActivityInboxNotification>> = {
|
...content,
|
||||||
...content,
|
attachedTo: activityMessage._id,
|
||||||
attachedTo: activityMessage._id,
|
attachedToClass: activityMessage._class
|
||||||
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(
|
const resource = resources.find((it) => it.provider === provider)
|
||||||
control,
|
|
||||||
res,
|
if (resource === undefined) continue
|
||||||
target,
|
|
||||||
activityMessage.attachedTo,
|
const fn = await getResource(resource.fn)
|
||||||
activityMessage.attachedToClass,
|
|
||||||
activityMessage.space,
|
const txes = await fn(control, types, object, data, receiver, sender)
|
||||||
docNotifyContexts,
|
if (txes.length > 0) {
|
||||||
data,
|
res.push(...txes)
|
||||||
notification.class.ActivityInboxNotification,
|
}
|
||||||
activityMessage.modifiedOn,
|
|
||||||
sender,
|
|
||||||
shouldPush,
|
|
||||||
shouldUpdateTimestamp,
|
|
||||||
cache
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -697,14 +698,13 @@ export async function getNotificationTxes (
|
|||||||
object: Doc,
|
object: Doc,
|
||||||
tx: TxCUD<Doc>,
|
tx: TxCUD<Doc>,
|
||||||
originTx: TxCUD<Doc>,
|
originTx: TxCUD<Doc>,
|
||||||
target: UserInfo,
|
receiver: UserInfo,
|
||||||
sender: UserInfo,
|
sender: UserInfo,
|
||||||
params: NotifyParams,
|
params: NotifyParams,
|
||||||
docNotifyContexts: DocNotifyContext[],
|
docNotifyContexts: DocNotifyContext[],
|
||||||
activityMessages: ActivityMessage[],
|
activityMessages: ActivityMessage[]
|
||||||
cache: Map<Ref<Doc>, Doc>
|
|
||||||
): Promise<Tx[]> {
|
): Promise<Tx[]> {
|
||||||
if (target.account === undefined) {
|
if (receiver.account === undefined) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,39 +717,39 @@ export async function getNotificationTxes (
|
|||||||
tx,
|
tx,
|
||||||
originTx,
|
originTx,
|
||||||
object,
|
object,
|
||||||
target.account,
|
receiver.account,
|
||||||
params.isOwn,
|
params.isOwn,
|
||||||
params.isSpace,
|
params.isSpace,
|
||||||
docMessage
|
docMessage
|
||||||
)
|
)
|
||||||
|
|
||||||
if (notifyResult.allowed) {
|
if (notifyResult.has(notification.providers.InboxNotificationProvider)) {
|
||||||
await pushActivityInboxNotifications(
|
const notificationTx = await pushActivityInboxNotifications(
|
||||||
originTx,
|
originTx,
|
||||||
control,
|
control,
|
||||||
res,
|
res,
|
||||||
target,
|
receiver,
|
||||||
sender,
|
sender,
|
||||||
object,
|
object,
|
||||||
docNotifyContexts,
|
docNotifyContexts,
|
||||||
[message],
|
message,
|
||||||
params.shouldUpdateTimestamp,
|
params.shouldUpdateTimestamp
|
||||||
notifyResult.push,
|
|
||||||
cache
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if (notifyResult.emails.length === 0) {
|
if (notificationTx !== undefined) {
|
||||||
continue
|
const notificationData = TxProcessor.createDoc2Doc(notificationTx)
|
||||||
}
|
|
||||||
|
|
||||||
if (target.person !== undefined && control.hierarchy.isDerived(target.person._class, contact.mixin.Employee)) {
|
await applyNotificationProviders(
|
||||||
const emp = target.person as Employee
|
notificationData,
|
||||||
|
notifyResult,
|
||||||
if (emp?.active) {
|
message.attachedTo,
|
||||||
for (const type of notifyResult.emails) {
|
message.attachedToClass,
|
||||||
await notifyByEmail(control, type._id, object, sender, target)
|
control,
|
||||||
}
|
res,
|
||||||
|
object,
|
||||||
|
receiver,
|
||||||
|
sender
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -768,12 +768,11 @@ export async function createCollabDocInfo (
|
|||||||
): Promise<Tx[]> {
|
): Promise<Tx[]> {
|
||||||
let res: Tx[] = []
|
let res: Tx[] = []
|
||||||
|
|
||||||
if (originTx.space === core.space.DerivedTx || collaborators.length === 0) {
|
if (originTx.space === core.space.DerivedTx) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const docMessages = activityMessages.filter((message) => message.attachedTo === object._id)
|
const docMessages = activityMessages.filter((message) => message.attachedTo === object._id)
|
||||||
|
|
||||||
if (docMessages.length === 0) {
|
if (docMessages.length === 0) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -788,6 +787,10 @@ export async function createCollabDocInfo (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (targets.size === 0) {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, {
|
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, {
|
||||||
attachedTo: object._id
|
attachedTo: object._id
|
||||||
})
|
})
|
||||||
@ -803,7 +806,7 @@ export async function createCollabDocInfo (
|
|||||||
if (info === undefined) continue
|
if (info === undefined) continue
|
||||||
|
|
||||||
res = res.concat(
|
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
|
return res
|
||||||
@ -908,8 +911,7 @@ async function updateCollaboratorsMixin (
|
|||||||
tx: TxMixin<Doc, Collaborators>,
|
tx: TxMixin<Doc, Collaborators>,
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
activityMessages: ActivityMessage[],
|
activityMessages: ActivityMessage[],
|
||||||
originTx: TxCUD<Doc>,
|
originTx: TxCUD<Doc>
|
||||||
cache: Map<Ref<Doc>, Doc>
|
|
||||||
): Promise<Tx[]> {
|
): Promise<Tx[]> {
|
||||||
const { hierarchy } = control
|
const { hierarchy } = control
|
||||||
|
|
||||||
@ -948,17 +950,23 @@ async function updateCollaboratorsMixin (
|
|||||||
prevCollabs = mixin !== undefined ? new Set(await getDocCollaborators(prevDoc, mixin, control)) : new Set()
|
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) {
|
for (const collab of tx.attributes.collaborators) {
|
||||||
if (!prevCollabs.has(collab) && tx.modifiedBy !== collab) {
|
if (!prevCollabs.has(collab) && tx.modifiedBy !== collab) {
|
||||||
if (
|
for (const provider of providers) {
|
||||||
await isAllowed(
|
if (await isAllowed(control, collab as Ref<PersonAccount>, type, provider)) {
|
||||||
control,
|
newCollabs.push(collab)
|
||||||
collab as Ref<PersonAccount>,
|
break
|
||||||
notification.ids.CollaboratoAddNotification,
|
}
|
||||||
notification.providers.PlatformNotification
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
newCollabs.push(collab)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -977,19 +985,19 @@ async function updateCollaboratorsMixin (
|
|||||||
|
|
||||||
if (target === undefined) continue
|
if (target === undefined) continue
|
||||||
|
|
||||||
await pushActivityInboxNotifications(
|
for (const message of activityMessages) {
|
||||||
originTx,
|
await pushActivityInboxNotifications(
|
||||||
control,
|
originTx,
|
||||||
res,
|
control,
|
||||||
target,
|
res,
|
||||||
sender,
|
target,
|
||||||
prevDoc,
|
sender,
|
||||||
docNotifyContexts,
|
prevDoc,
|
||||||
activityMessages,
|
docNotifyContexts,
|
||||||
true,
|
message,
|
||||||
false,
|
true
|
||||||
cache
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1220,8 +1228,11 @@ export async function OnAttributeCreate (tx: Tx, control: TriggerControl): Promi
|
|||||||
objectClass,
|
objectClass,
|
||||||
txClasses,
|
txClasses,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
providers: {
|
defaultEnabled: false,
|
||||||
[notification.providers.PlatformNotification]: false
|
templates: {
|
||||||
|
textTemplate: '{body}',
|
||||||
|
htmlTemplate: '<p>{body}</p>',
|
||||||
|
subjectTemplate: '{doc} updated'
|
||||||
},
|
},
|
||||||
label: attribute.label
|
label: attribute.label
|
||||||
}
|
}
|
||||||
@ -1330,13 +1341,7 @@ export async function createCollaboratorNotifications (
|
|||||||
case core.class.TxMixin: {
|
case core.class.TxMixin: {
|
||||||
let res = await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
|
let res = await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
|
||||||
res = res.concat(
|
res = res.concat(
|
||||||
await updateCollaboratorsMixin(
|
await updateCollaboratorsMixin(tx as TxMixin<Doc, Collaborators>, control, activityMessages, originTx ?? tx)
|
||||||
tx as TxMixin<Doc, Collaborators>,
|
|
||||||
control,
|
|
||||||
activityMessages,
|
|
||||||
originTx ?? tx,
|
|
||||||
cache
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return await applyUserTxes(control, res)
|
return await applyUserTxes(control, res)
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
import { BaseNotificationType } from '@hcengineering/notification'
|
import { BaseNotificationType, NotificationProvider } from '@hcengineering/notification'
|
||||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { Account, Ref } from '@hcengineering/core'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -28,20 +27,10 @@ export interface Content {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface NotifyResult {
|
export type NotifyResult = Map<Ref<NotificationProvider>, BaseNotificationType[]>
|
||||||
allowed: boolean
|
|
||||||
push: boolean
|
|
||||||
emails: BaseNotificationType[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NotifyParams {
|
export interface NotifyParams {
|
||||||
isOwn: boolean
|
isOwn: boolean
|
||||||
isSpace: boolean
|
isSpace: boolean
|
||||||
shouldUpdateTimestamp: boolean
|
shouldUpdateTimestamp: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserInfo {
|
|
||||||
_id: Ref<Account>
|
|
||||||
account?: PersonAccount
|
|
||||||
person?: Person
|
|
||||||
}
|
|
||||||
|
@ -30,23 +30,26 @@ import core, {
|
|||||||
MixinUpdate,
|
MixinUpdate,
|
||||||
Ref,
|
Ref,
|
||||||
Tx,
|
Tx,
|
||||||
|
TxCreateDoc,
|
||||||
TxCUD,
|
TxCUD,
|
||||||
TxMixin,
|
TxMixin,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
|
TxRemoveDoc,
|
||||||
TxUpdateDoc
|
TxUpdateDoc
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import serverNotification, {
|
import serverNotification, {
|
||||||
getPersonAccountById,
|
getPersonAccountById,
|
||||||
HTMLPresenter,
|
HTMLPresenter,
|
||||||
NotificationPresenter,
|
NotificationPresenter,
|
||||||
TextPresenter
|
TextPresenter,
|
||||||
|
UserInfo
|
||||||
} from '@hcengineering/server-notification'
|
} from '@hcengineering/server-notification'
|
||||||
import { getResource, IntlString, translate } from '@hcengineering/platform'
|
import { getResource, IntlString, translate } from '@hcengineering/platform'
|
||||||
import contact, { formatName, PersonAccount } from '@hcengineering/contact'
|
import contact, { formatName, PersonAccount } from '@hcengineering/contact'
|
||||||
import { DocUpdateMessage } from '@hcengineering/activity'
|
import { DocUpdateMessage } from '@hcengineering/activity'
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
|
|
||||||
import { UserInfo, NotifyResult } from './types'
|
import { NotifyResult } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -107,45 +110,65 @@ export async function shouldNotifyCommon (
|
|||||||
): Promise<NotifyResult> {
|
): Promise<NotifyResult> {
|
||||||
const type = (await control.modelDb.findAll(notification.class.CommonNotificationType, { _id: typeId }))[0]
|
const type = (await control.modelDb.findAll(notification.class.CommonNotificationType, { _id: typeId }))[0]
|
||||||
|
|
||||||
const emailTypes: BaseNotificationType[] = []
|
|
||||||
let allowed = false
|
|
||||||
let push = false
|
|
||||||
|
|
||||||
if (type === undefined) {
|
if (type === undefined) {
|
||||||
return { allowed, emails: emailTypes, push }
|
return new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await isAllowed(control, user as Ref<PersonAccount>, type._id, notification.providers.PlatformNotification)) {
|
const result = new Map<Ref<NotificationProvider>, BaseNotificationType[]>()
|
||||||
allowed = true
|
const providers = await control.modelDb.findAll(notification.class.NotificationProvider, {})
|
||||||
}
|
|
||||||
if (await isAllowed(control, user as Ref<PersonAccount>, type._id, notification.providers.BrowserNotification)) {
|
for (const provider of providers) {
|
||||||
push = true
|
const allowed = await isAllowed(control, user as Ref<PersonAccount>, type, provider)
|
||||||
}
|
if (allowed) {
|
||||||
if (await isAllowed(control, user as Ref<PersonAccount>, type._id, notification.providers.EmailNotification)) {
|
const cur = result.get(provider._id) ?? []
|
||||||
emailTypes.push(type)
|
result.set(provider._id, [...cur, type])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { allowed, push, emails: emailTypes }
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isAllowed (
|
export async function isAllowed (
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
receiver: Ref<PersonAccount>,
|
receiver: Ref<PersonAccount>,
|
||||||
typeId: Ref<BaseNotificationType>,
|
type: BaseNotificationType,
|
||||||
providerId: Ref<NotificationProvider>
|
provider: NotificationProvider
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const settings = await control.queryFind(notification.class.NotificationSetting, {})
|
const providersSettings = await control.queryFind(notification.class.NotificationProviderSetting, {})
|
||||||
const setting = settings.find((p) => p.attachedTo === providerId && p.type === typeId && p.modifiedBy === receiver)
|
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) {
|
if (setting !== undefined) {
|
||||||
return setting.enabled
|
return setting.enabled
|
||||||
}
|
}
|
||||||
const type = (
|
|
||||||
await control.modelDb.findAll(notification.class.BaseNotificationType, {
|
if (providerDefaults.some((it) => it.provider === provider._id && it.enabledTypes.includes(type._id))) {
|
||||||
_id: typeId
|
return true
|
||||||
})
|
}
|
||||||
)[0]
|
|
||||||
if (type === undefined) return false
|
if (type === undefined) return false
|
||||||
return type.providers[providerId] ?? false
|
|
||||||
|
return type.defaultEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isShouldNotifyTx (
|
export async function isShouldNotifyTx (
|
||||||
@ -158,10 +181,6 @@ export async function isShouldNotifyTx (
|
|||||||
isSpace: boolean,
|
isSpace: boolean,
|
||||||
docUpdateMessage?: DocUpdateMessage
|
docUpdateMessage?: DocUpdateMessage
|
||||||
): Promise<NotifyResult> {
|
): Promise<NotifyResult> {
|
||||||
let allowed = false
|
|
||||||
let push = false
|
|
||||||
|
|
||||||
const emailTypes: NotificationType[] = []
|
|
||||||
const types = await getMatchedTypes(
|
const types = await getMatchedTypes(
|
||||||
control,
|
control,
|
||||||
tx,
|
tx,
|
||||||
@ -170,8 +189,9 @@ export async function isShouldNotifyTx (
|
|||||||
isSpace,
|
isSpace,
|
||||||
docUpdateMessage?.attributeUpdates?.attrKey
|
docUpdateMessage?.attributeUpdates?.attrKey
|
||||||
)
|
)
|
||||||
|
|
||||||
const modifiedAccount = await getPersonAccountById(tx.modifiedBy, control)
|
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) {
|
for (const type of types) {
|
||||||
if (
|
if (
|
||||||
@ -190,21 +210,17 @@ export async function isShouldNotifyTx (
|
|||||||
if (!res) continue
|
if (!res) continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (await isAllowed(control, user._id, type._id, notification.providers.PlatformNotification)) {
|
for (const provider of providers) {
|
||||||
allowed = true
|
const allowed = await isAllowed(control, user._id, type, provider)
|
||||||
}
|
|
||||||
if (await isAllowed(control, user._id, type._id, notification.providers.BrowserNotification)) {
|
if (allowed) {
|
||||||
push = true
|
const cur = result.get(provider._id) ?? []
|
||||||
}
|
result.set(provider._id, [...cur, type])
|
||||||
if (await isAllowed(control, user._id, type._id, notification.providers.EmailNotification)) {
|
}
|
||||||
emailTypes.push(type)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
allowed,
|
return result
|
||||||
push,
|
|
||||||
emails: emailTypes
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMatchedTypes (
|
async function getMatchedTypes (
|
||||||
@ -357,6 +373,24 @@ async function getFallbackNotificationFullfillment (
|
|||||||
}
|
}
|
||||||
break
|
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 }
|
return { title, body, intlParams, intlParamsNotLocalized }
|
||||||
|
@ -16,7 +16,13 @@
|
|||||||
|
|
||||||
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
|
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
|
||||||
import { Account, Class, Doc, Mixin, Ref, Tx, TxCUD } from '@hcengineering/core'
|
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 { Metadata, Plugin, Resource, plugin } from '@hcengineering/platform'
|
||||||
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
|
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
|
||||||
|
|
||||||
@ -129,7 +135,28 @@ export interface NotificationPresenter extends Class<Doc> {
|
|||||||
presenter: Resource<NotificationContentProvider>
|
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_BODY_SIZE = 50
|
||||||
|
export const NOTIFICATION_TITLE_SIZE = 30
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -140,6 +167,9 @@ export default plugin(serverNotificationId, {
|
|||||||
PushPrivateKey: '' as Metadata<string>,
|
PushPrivateKey: '' as Metadata<string>,
|
||||||
PushSubject: '' as Metadata<string>
|
PushSubject: '' as Metadata<string>
|
||||||
},
|
},
|
||||||
|
class: {
|
||||||
|
NotificationProviderResources: '' as Ref<Class<NotificationProviderResources>>
|
||||||
|
},
|
||||||
mixin: {
|
mixin: {
|
||||||
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
|
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
|
||||||
TextPresenter: '' as Ref<Mixin<TextPresenter>>,
|
TextPresenter: '' as Ref<Mixin<TextPresenter>>,
|
||||||
|
@ -164,8 +164,7 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
|
|||||||
senderInfo,
|
senderInfo,
|
||||||
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true },
|
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true },
|
||||||
notifyContexts,
|
notifyContexts,
|
||||||
messages,
|
messages
|
||||||
new Map()
|
|
||||||
)
|
)
|
||||||
res.push(...txes)
|
res.push(...txes)
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"@hcengineering/notification": "^0.6.23",
|
"@hcengineering/notification": "^0.6.23",
|
||||||
"@hcengineering/platform": "^0.6.11",
|
"@hcengineering/platform": "^0.6.11",
|
||||||
"@hcengineering/server-core": "^0.6.1",
|
"@hcengineering/server-core": "^0.6.1",
|
||||||
|
"@hcengineering/server-notification": "^0.6.1",
|
||||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||||
"@hcengineering/task": "^0.6.20",
|
"@hcengineering/task": "^0.6.20",
|
||||||
"@hcengineering/tracker": "^0.6.24",
|
"@hcengineering/tracker": "^0.6.24",
|
||||||
|
@ -37,14 +37,14 @@ import type { TriggerControl } from '@hcengineering/server-core'
|
|||||||
import {
|
import {
|
||||||
getCommonNotificationTxes,
|
getCommonNotificationTxes,
|
||||||
getNotificationContent,
|
getNotificationContent,
|
||||||
isShouldNotifyTx,
|
isShouldNotifyTx
|
||||||
UserInfo
|
|
||||||
} from '@hcengineering/server-notification-resources'
|
} from '@hcengineering/server-notification-resources'
|
||||||
import task, { makeRank } from '@hcengineering/task'
|
import task, { makeRank } from '@hcengineering/task'
|
||||||
import { jsonToMarkup, nodeDoc, nodeParagraph, nodeText } from '@hcengineering/text'
|
import { jsonToMarkup, nodeDoc, nodeParagraph, nodeText } from '@hcengineering/text'
|
||||||
import tracker, { Issue, IssueStatus, Project, TimeSpendReport } from '@hcengineering/tracker'
|
import tracker, { Issue, IssueStatus, Project, TimeSpendReport } from '@hcengineering/tracker'
|
||||||
import serverTime, { OnToDo, ToDoFactory } from '@hcengineering/server-time'
|
import serverTime, { OnToDo, ToDoFactory } from '@hcengineering/server-time'
|
||||||
import time, { ProjectToDo, ToDo, ToDoPriority, TodoAutomationHelper, WorkSlot } from '@hcengineering/time'
|
import time, { ProjectToDo, ToDo, ToDoPriority, TodoAutomationHelper, WorkSlot } from '@hcengineering/time'
|
||||||
|
import { UserInfo } from '@hcengineering/server-notification'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
|
@ -36,7 +36,7 @@ export class NotificationsPage {
|
|||||||
documents = (): Locator => this.page.getByRole('button', { name: 'Documents' })
|
documents = (): Locator => this.page.getByRole('button', { name: 'Documents' })
|
||||||
requests = (): Locator => this.page.getByRole('button', { name: 'Requests' })
|
requests = (): Locator => this.page.getByRole('button', { name: 'Requests' })
|
||||||
todos = (): Locator => this.page.getByRole('button', { name: "Todo's" })
|
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) {
|
constructor (page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
Loading…
Reference in New Issue
Block a user