// // Copyright © 2020, 2021 Anticrm Platform Contributors. // Copyright © 2021, 2022 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. // import activity from '@hcengineering/activity' import chunter from '@hcengineering/chunter' import { type Account, type AttachedDoc, type Class, type Collection, type Data, type Doc, type Domain, DOMAIN_MODEL, Hierarchy, IndexKind, type Ref, type Timestamp, type Tx, type TxCUD } from '@hcengineering/core' import { ArrOf, type Builder, Index, Mixin, Model, Prop, TypeMarkup, TypeRef, TypeString, UX, Collection as PropCollection, TypeBoolean, TypeDate } from '@hcengineering/model' import core, { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core' import preference, { TPreference } from '@hcengineering/model-preference' import view, { createAction } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' import { type ActivityMessage, type ActivityMessageExtension, type ActivityMessageExtensionKind, type ChatMessage, type ChatMessageViewlet, type DocUpdateAction, type DocAttributeUpdates, type DocUpdateMessage, type DocUpdateMessageViewlet, type DocUpdateMessageViewletAttributesConfig, type DocUpdates, type DocUpdateTx, inboxId, type InboxNotification, type DocNotifyContext, type Notification, type NotificationGroup, type NotificationObjectPresenter, type NotificationPreferencesGroup, type NotificationPreview, type NotificationProvider, type NotificationSetting, type NotificationStatus, type NotificationTemplate, type NotificationType, type ActivityMessagesFilter, type ActivityDoc, type NotificationObjectPreposition, notificationId } from '@hcengineering/notification' import { type Asset, type IntlString, type Resource } from '@hcengineering/platform' import setting from '@hcengineering/setting' import { type AnyComponent } from '@hcengineering/ui/src/types' import attachment from '@hcengineering/model-attachment' import { type NotificationAttributePresenter } from '@hcengineering/view' import notification from './plugin' import { buildActivityMessages } from './activityMessages' export { notificationId } from '@hcengineering/notification' export { notificationOperation } from './migration' export { notification as default } export const DOMAIN_NOTIFICATION = 'notification' as Domain @Model(notification.class.Notification, core.class.AttachedDoc, DOMAIN_NOTIFICATION) export class TNotification extends TAttachedDoc implements Notification { @Prop(TypeRef(core.class.Tx), 'TX' as IntlString) tx!: Ref> @Prop(TypeString(), 'Status' as IntlString) status!: NotificationStatus text!: string type!: Ref } @Model(notification.class.NotificationType, core.class.Doc, DOMAIN_MODEL) export class TNotificationType extends TDoc implements NotificationType { generated!: boolean label!: IntlString group!: Ref txClasses!: Ref>[] providers!: Record, boolean> objectClass!: Ref> hidden!: boolean templates?: NotificationTemplate onlyOwn?: boolean } @Model(notification.class.NotificationGroup, core.class.Doc, DOMAIN_MODEL) export class TNotificationGroup extends TDoc implements NotificationGroup { label!: IntlString icon!: Asset // using for autogenerated settings objectClass?: Ref> } @Model(notification.class.NotificationPreferencesGroup, core.class.Doc, DOMAIN_MODEL) export class TNotificationPreferencesGroup extends TDoc implements NotificationPreferencesGroup { label!: IntlString icon!: Asset presenter!: AnyComponent } @Model(notification.class.NotificationProvider, core.class.Doc, DOMAIN_MODEL) export class TNotificationProvider extends TDoc implements NotificationProvider { label!: IntlString default!: boolean } @Model(notification.class.NotificationSetting, preference.class.Preference) export class TNotificationSetting extends TPreference implements NotificationSetting { declare attachedTo: Ref type!: Ref enabled!: boolean } @Mixin(notification.mixin.ClassCollaborators, core.class.Class) export class TClassCollaborators extends TClass { fields!: string[] } @Mixin(notification.mixin.Collaborators, core.class.Doc) @UX(notification.string.Collaborators) export class TCollaborators extends TDoc { @Prop(ArrOf(TypeRef(core.class.Account)), notification.string.Collaborators) @Index(IndexKind.Indexed) collaborators!: Ref[] } @Mixin(notification.mixin.NotificationObjectPresenter, core.class.Class) export class TNotificationObjectPresenter extends TClass implements NotificationObjectPresenter { presenter!: AnyComponent } @Mixin(notification.mixin.NotificationPreview, core.class.Class) export class TNotificationPreview extends TClass implements NotificationPreview { presenter!: AnyComponent } @Mixin(notification.mixin.ActivityDoc, core.class.Class) export class TActivityDoc extends TClass implements ActivityDoc { ignoreCollections?: string[] } @Mixin(notification.mixin.NotificationObjectPreposition, core.class.Class) export class TNotificationObjectPreposition extends TClass implements NotificationObjectPreposition { preposition!: IntlString } @Mixin(notification.mixin.NotificationAttributePresenter, core.class.Class) export class TNotificationAttributePresenter extends TClass implements NotificationAttributePresenter { presenter!: AnyComponent } @Model(notification.class.DocUpdates, core.class.Doc, DOMAIN_NOTIFICATION) export class TDocUpdates extends TDoc implements DocUpdates { @Index(IndexKind.Indexed) user!: Ref @Index(IndexKind.Indexed) attachedTo!: Ref @Index(IndexKind.Indexed) hidden!: boolean attachedToClass!: Ref> lastTxTime?: Timestamp txes!: DocUpdateTx[] } @Model(notification.class.DocNotifyContext, core.class.Doc, DOMAIN_NOTIFICATION) export class TDocNotifyContext extends TDoc implements DocNotifyContext { @Prop(TypeRef(core.class.Account), core.string.Account) @Index(IndexKind.Indexed) user!: Ref @Prop(TypeRef(core.class.Doc), core.string.AttachedTo) @Index(IndexKind.Indexed) attachedTo!: Ref @Prop(TypeRef(core.class.Class), core.string.AttachedToClass) @Index(IndexKind.Indexed) attachedToClass!: Ref> @Prop(TypeBoolean(), core.string.Archived) @Index(IndexKind.Indexed) hidden!: boolean @Prop(TypeDate(), core.string.Date) @Index(IndexKind.Indexed) lastViewedTimestamp?: Timestamp @Prop(TypeDate(), core.string.Date) @Index(IndexKind.Indexed) lastUpdateTimestamp?: Timestamp } @Model(notification.class.ActivityMessage, core.class.AttachedDoc, DOMAIN_NOTIFICATION) export class TActivityMessage extends TAttachedDoc implements ActivityMessage { @Prop(TypeBoolean(), notification.string.Pinned) @Index(IndexKind.Indexed) isPinned?: boolean @Prop(PropCollection(chunter.class.Reaction), chunter.string.Reactions) reactions?: number } @Model(notification.class.DocUpdateMessage, notification.class.ActivityMessage, DOMAIN_NOTIFICATION) export class TDocUpdateMessage extends TActivityMessage implements DocUpdateMessage { @Prop(TypeRef(core.class.Doc), core.string.Object) @Index(IndexKind.Indexed) objectId!: Ref @Prop(TypeRef(core.class.Class), core.string.Class) @Index(IndexKind.Indexed) objectClass!: Ref> @Prop(TypeRef(core.class.TxCUD), core.string.Object) @Index(IndexKind.Indexed) txId!: Ref> action!: DocUpdateAction updateCollection?: string attributeUpdates?: DocAttributeUpdates } @Model(notification.class.ChatMessage, notification.class.ActivityMessage, DOMAIN_NOTIFICATION) export class TChatMessage extends TActivityMessage implements ChatMessage { @Prop(TypeMarkup(), chunter.string.Message) @Index(IndexKind.FullText) message!: string @Prop(PropCollection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) attachments?: number @Prop(TypeBoolean(), core.string.Boolean) @Index(IndexKind.Indexed) isEdited?: boolean } @Model(notification.class.InboxNotification, core.class.Doc, DOMAIN_NOTIFICATION) export class TInboxNotification extends TDoc implements InboxNotification { @Prop(TypeRef(notification.class.ActivityMessage), core.string.AttachedTo) @Index(IndexKind.Indexed) attachedTo!: Ref @Prop(TypeRef(notification.class.ActivityMessage), core.string.AttachedToClass) @Index(IndexKind.Indexed) attachedToClass!: Ref> @Prop(TypeRef(notification.class.DocNotifyContext), core.string.AttachedTo) @Index(IndexKind.Indexed) docNotifyContext!: Ref @Prop(TypeRef(core.class.Account), core.string.Account) @Index(IndexKind.Indexed) user!: Ref @Prop(TypeBoolean(), core.string.Boolean) @Index(IndexKind.Indexed) isViewed!: boolean } @Model(notification.class.DocUpdateMessageViewlet, core.class.Doc, DOMAIN_MODEL) export class TDocUpdateMessageViewlet extends TDoc implements DocUpdateMessageViewlet { @Prop(TypeRef(core.class.Doc), core.string.Class) @Index(IndexKind.Indexed) objectClass!: Ref> @Prop(TypeString(), core.string.String) @Index(IndexKind.Indexed) action!: DocUpdateAction label?: IntlString labelComponent?: AnyComponent valueAttr?: string icon?: Asset component?: AnyComponent config?: DocUpdateMessageViewletAttributesConfig hideIfRemoved?: boolean onlyWithParent?: boolean } @Model(notification.class.ChatMessageViewlet, core.class.Doc, DOMAIN_MODEL) export class TChatMessageViewlet extends TDoc implements ChatMessageViewlet { @Prop(TypeRef(core.class.Doc), core.string.Class) @Index(IndexKind.Indexed) objectClass!: Ref> label?: IntlString hidden?: boolean onlyWithParent?: boolean } @Model(notification.class.ActivityMessageExtension, core.class.Doc, DOMAIN_MODEL) export class TActivityMessageExtension extends TDoc implements ActivityMessageExtension { @Prop(TypeRef(notification.class.ActivityMessage), core.string.Class) @Index(IndexKind.Indexed) ofMessage!: Ref> components!: { kind: ActivityMessageExtensionKind, component: AnyComponent }[] } @Model(notification.class.ActivityMessagesFilter, core.class.Doc, DOMAIN_MODEL) export class TActivityMessagesFilter extends TDoc implements ActivityMessagesFilter { label!: IntlString filter!: Resource<(message: ActivityMessage, _class?: Ref) => boolean> } export function createModel (builder: Builder): void { builder.createModel( TNotification, TNotificationType, TNotificationProvider, TNotificationSetting, TNotificationGroup, TNotificationPreferencesGroup, TClassCollaborators, TCollaborators, TDocUpdates, TNotificationObjectPresenter, TNotificationPreview, TDocNotifyContext, TDocUpdateMessage, TChatMessage, TActivityMessage, TInboxNotification, TDocUpdateMessageViewlet, TActivityMessageExtension, TChatMessageViewlet, TActivityMessagesFilter, TActivityDoc, TNotificationObjectPreposition, TNotificationAttributePresenter ) // Temporarily disabled, we should think about it // builder.createDoc( // notification.class.NotificationProvider, // core.space.Model, // { // label: notification.string.BrowserNotification, // default: true // }, // notification.ids.BrowserNotification // ) builder.createDoc( notification.class.NotificationProvider, core.space.Model, { label: notification.string.Inbox }, notification.providers.PlatformNotification ) builder.createDoc( notification.class.NotificationProvider, core.space.Model, { label: notification.string.EmailNotification }, notification.providers.EmailNotification ) builder.createDoc( setting.class.SettingsCategory, core.space.Model, { name: 'notifications', label: notification.string.Notifications, icon: notification.icon.Notifications, component: notification.component.NotificationSettings, group: 'settings', secured: false, order: 2500 }, notification.ids.NotificationSettings ) builder.createDoc( workbench.class.Application, core.space.Model, { label: notification.string.Inbox, icon: notification.icon.Notifications, alias: notificationId, hidden: true, component: notification.component.Inbox, aside: chunter.component.ThreadView }, notification.app.Notification ) builder.createDoc( workbench.class.Application, core.space.Model, { label: notification.string.Inbox, icon: notification.icon.Notifications, alias: inboxId, hidden: true, locationResolver: notification.resolver.Location, navigatorModel: { aside: notification.component.InboxAside, spaces: [], specials: [ { id: 'all', component: notification.component.NewInbox, icon: notification.icon.Activity, label: notification.string.AllActivity, componentProps: { type: 'all', label: notification.string.AllActivity } }, { id: 'reactions', component: notification.component.NewInbox, icon: notification.icon.Emoji, label: notification.string.Reactions, componentProps: { _class: chunter.class.Reaction, label: notification.string.Reactions } } ] } }, notification.app.Inbox ) createAction( builder, { action: notification.actionImpl.MarkAsUnread, actionProps: {}, label: notification.string.MarkAsUnread, icon: notification.icon.Track, input: 'focus', visibilityTester: notification.function.HasntNotifications, category: notification.category.Notification, target: notification.class.DocUpdates, context: { mode: 'context', application: notification.app.Notification, group: 'edit' } }, notification.action.MarkAsUnread ) createAction( builder, { action: notification.actionImpl.Hide, actionProps: {}, label: notification.string.Archive, icon: view.icon.Archive, input: 'focus', keyBinding: ['Backspace'], category: notification.category.Notification, target: notification.class.DocUpdates, context: { mode: ['context', 'browser'], group: 'edit' } }, notification.action.Hide ) createAction( builder, { action: notification.actionImpl.Unsubscribe, actionProps: {}, label: notification.string.DontTrack, icon: notification.icon.Hide, input: 'focus', category: notification.category.Notification, target: notification.class.DocUpdates, context: { mode: 'context', application: notification.app.Notification, group: 'edit' } }, notification.action.Unsubscribe ) builder.mixin(notification.class.DocUpdates, core.class.Class, view.mixin.IgnoreActions, { actions: [view.action.Delete, view.action.Open] }) createAction(builder, { action: workbench.actionImpl.Navigate, actionProps: { mode: 'app', application: notificationId, special: notificationId }, label: notification.string.Inbox, icon: view.icon.ArrowRight, input: 'none', category: view.category.Navigation, target: core.class.Doc, context: { mode: ['workbench', 'browser', 'editor', 'panel', 'popup'] } }) builder.createDoc( notification.class.NotificationGroup, core.space.Model, { label: notification.string.Notifications, icon: notification.icon.Notifications }, notification.ids.NotificationGroup ) builder.createDoc( notification.class.NotificationType, core.space.Model, { hidden: false, generated: false, label: notification.string.Collaborators, group: notification.ids.NotificationGroup, txClasses: [], objectClass: notification.mixin.Collaborators, providers: { [notification.providers.PlatformNotification]: true } }, notification.ids.CollaboratoAddNotification ) builder.createDoc( activity.class.TxViewlet, core.space.Model, { objectClass: notification.mixin.Collaborators, icon: notification.icon.Notifications, txClass: core.class.TxMixin, component: notification.activity.TxCollaboratorsChange, display: 'inline', editable: false, hideOnRemove: true }, notification.ids.TxCollaboratorsChange ) builder.createDoc( activity.class.TxViewlet, core.space.Model, { objectClass: chunter.class.DirectMessage, icon: chunter.icon.Chunter, txClass: core.class.TxCreateDoc, component: notification.activity.TxDmCreation, display: 'inline', editable: false, hideOnRemove: true }, notification.ids.TxDmCreation ) buildActivityMessages(builder) } export function generateClassNotificationTypes ( builder: Builder, _class: Ref>, group: Ref, ignoreKeys: string[] = [], defaultEnabled: string[] = [] ): void { const txes = builder.getTxes() const hierarchy = new Hierarchy() for (const tx of txes) { hierarchy.tx(tx) } const attributes = hierarchy.getAllAttributes( _class, 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) for (const attribute of filtered) { if (ignoreKeys.includes(attribute.name)) continue const isCollection: boolean = core.class.Collection === attribute.type._class const objectClass = !isCollection ? _class : (attribute.type as Collection).of const txClasses = !isCollection ? hierarchy.isMixin(attribute.attributeOf) ? [core.class.TxMixin] : [core.class.TxUpdateDoc] : [core.class.TxCreateDoc, core.class.TxRemoveDoc] const data: Data = { attribute: attribute._id, field: attribute.name, group, generated: true, objectClass, txClasses, hidden: false, providers: { [notification.providers.PlatformNotification]: defaultEnabled.includes(attribute.name) }, label: attribute.label } if (isCollection) { data.attachedToClass = _class } const id = `${notification.class.NotificationType}_${_class}_${attribute.name}` as Ref builder.createDoc(notification.class.NotificationType, core.space.Model, data, id) } }