diff --git a/models/chunter/src/index.ts b/models/chunter/src/index.ts index c32793be8b..7d7b8003a4 100644 --- a/models/chunter/src/index.ts +++ b/models/chunter/src/index.ts @@ -184,10 +184,6 @@ export function createModel (builder: Builder, options = { addApplication: true } }) - builder.mixin(spaceClass, core.class.Class, notification.mixin.SpaceLastEdit, { - lastEditField: 'lastMessage' - }) - builder.mixin(spaceClass, core.class.Class, view.mixin.ObjectEditor, { editor: chunter.component.EditChannel }) @@ -197,12 +193,10 @@ export function createModel (builder: Builder, options = { addApplication: true getName: chunter.function.GetDmName }) - builder.mixin(chunter.class.Message, core.class.Class, notification.mixin.TrackedDoc, {}) builder.mixin(chunter.class.Message, core.class.Class, notification.mixin.ClassCollaborators, { fields: ['createdBy', 'replies'] }) - builder.mixin(chunter.class.ChunterSpace, core.class.Class, notification.mixin.TrackedDoc, {}) builder.mixin(chunter.class.DirectMessage, core.class.Class, notification.mixin.ClassCollaborators, { fields: ['members'] }) diff --git a/models/contact/src/index.ts b/models/contact/src/index.ts index 1c8e9a5749..fd172571be 100644 --- a/models/contact/src/index.ts +++ b/models/contact/src/index.ts @@ -324,10 +324,6 @@ export function createModel (builder: Builder): void { inlineEditor: contact.component.ContactArrayEditor }) - builder.mixin(contact.class.Contact, core.class.Class, notification.mixin.TrackedDoc, {}) - - builder.mixin(contact.class.Channel, core.class.Class, notification.mixin.TrackedDoc, {}) - builder.mixin(contact.class.Contact, core.class.Class, notification.mixin.ClassCollaborators, { fields: [] }) diff --git a/models/document/src/index.ts b/models/document/src/index.ts index 045fb2a312..492b65bf7f 100644 --- a/models/document/src/index.ts +++ b/models/document/src/index.ts @@ -45,7 +45,6 @@ import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core' import presentation from '@hcengineering/model-presentation' import view, { actionTemplates, createAction } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' -import notification from '@hcengineering/notification' import tags from '@hcengineering/tags' import document from './plugin' @@ -187,8 +186,6 @@ export function createModel (builder: Builder): void { component: document.component.CreateDocument }) - builder.mixin(document.class.Document, core.class.Class, notification.mixin.TrackedDoc, {}) - builder.mixin(document.class.Document, core.class.Class, view.mixin.ObjectPanel, { component: document.component.EditDoc }) diff --git a/models/inventory/src/index.ts b/models/inventory/src/index.ts index e5ee904fb8..9cae385fa3 100644 --- a/models/inventory/src/index.ts +++ b/models/inventory/src/index.ts @@ -20,7 +20,6 @@ import attachment from '@hcengineering/model-attachment' import core, { TAttachedDoc } from '@hcengineering/model-core' import { createAction } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' -import notification from '@hcengineering/notification' import setting from '@hcengineering/setting' import view, { Viewlet } from '@hcengineering/view' import inventory from './plugin' @@ -160,8 +159,6 @@ export function createModel (builder: Builder): void { inventory.category.Inventory ) - builder.mixin(inventory.class.Product, core.class.Class, notification.mixin.TrackedDoc, {}) - createAction(builder, { label: inventory.string.CreateSubcategory, icon: inventory.icon.Categories, diff --git a/models/notification/src/index.ts b/models/notification/src/index.ts index 7dffc599d0..40784297b6 100644 --- a/models/notification/src/index.ts +++ b/models/notification/src/index.ts @@ -37,7 +37,6 @@ import view, { createAction } from '@hcengineering/model-view' import { DocUpdates, EmailNotification, - LastView, Notification, NotificationGroup, notificationId, @@ -46,8 +45,7 @@ import { NotificationSetting, NotificationStatus, NotificationTemplate, - NotificationType, - SpaceLastEdit + NotificationType } from '@hcengineering/notification' import type { Asset, IntlString } from '@hcengineering/platform' import setting from '@hcengineering/setting' @@ -61,13 +59,6 @@ export { notification as default } export const DOMAIN_NOTIFICATION = 'notification' as Domain -@Model(notification.class.LastView, core.class.Doc, DOMAIN_NOTIFICATION) -export class TLastView extends TDoc implements LastView { - @Prop(TypeRef(core.class.Account), core.string.ModifiedBy) - @Index(IndexKind.Indexed) - user!: Ref -} - @Model(notification.class.Notification, core.class.AttachedDoc, DOMAIN_NOTIFICATION) export class TNotification extends TAttachedDoc implements Notification { @Prop(TypeRef(core.class.Tx), 'TX' as IntlString) @@ -137,19 +128,11 @@ export class TNotificationSetting extends TPreference implements NotificationSet enabled!: boolean } -@Mixin(notification.mixin.SpaceLastEdit, core.class.Class) -export class TSpaceLastEdit extends TClass implements SpaceLastEdit { - lastEditField!: string -} - @Mixin(notification.mixin.ClassCollaborators, core.class.Class) export class TClassCollaborators extends TClass { fields!: string[] } -@Mixin(notification.mixin.TrackedDoc, core.class.Class) -export class TTrackedDoc extends TClass {} - @Mixin(notification.mixin.Collaborators, core.class.Doc) @UX(notification.string.Collaborators) export class TCollaborators extends TDoc { @@ -175,23 +158,20 @@ export class TDocUpdates extends TDoc implements DocUpdates { hidden!: boolean attachedToClass!: Ref> - lastTx!: Ref> - lastTxTime!: Timestamp + lastTx?: Ref> + lastTxTime?: Timestamp txes!: [Ref>, Timestamp][] } export function createModel (builder: Builder): void { builder.createModel( - TLastView, TNotification, TEmaiNotification, TNotificationType, TNotificationProvider, TNotificationSetting, TNotificationGroup, - TSpaceLastEdit, TClassCollaborators, - TTrackedDoc, TCollaborators, TDocUpdates, TNotificationObjectPresenter diff --git a/models/notification/src/migration.ts b/models/notification/src/migration.ts index c46e91a842..9c3d510935 100644 --- a/models/notification/src/migration.ts +++ b/models/notification/src/migration.ts @@ -13,22 +13,9 @@ // limitations under the License. // -import core, { - Account, - AttachedDoc, - Class, - Collection, - Data, - Doc, - DOMAIN_TX, - generateId, - Ref, - TxCollectionCUD, - TxOperations, - TxRemoveDoc -} from '@hcengineering/core' +import core, { AttachedDoc, Class, Collection, Data, Doc, DOMAIN_TX, Ref, TxOperations } from '@hcengineering/core' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model' -import notification, { LastView, NotificationType } from '@hcengineering/notification' +import notification, { DocUpdates, NotificationType } from '@hcengineering/notification' import { DOMAIN_NOTIFICATION } from '.' async function fillNotificationText (client: MigrationClient): Promise { @@ -80,79 +67,6 @@ async function createSpace (client: MigrationUpgradeClient): Promise { } } -async function migrateLastView (client: MigrationClient): Promise { - // lets clear last view txes (it should be derived and shouldn't store in tx collection) - const txes = await client.find(DOMAIN_TX, { - objectClass: notification.class.LastView - }) - for (const tx of txes) { - await client.delete(DOMAIN_TX, tx._id) - } - - const h = client.hierarchy - const docClasses = h.getDescendants(core.class.Doc) - const trackedClasses = docClasses.filter((p) => h.hasMixin(h.getClass(p), notification.mixin.TrackedDoc)) - const allowedClasses = new Set>>() - trackedClasses.forEach((p) => h.getDescendants(p).forEach((a) => allowedClasses.add(a))) - - const removeTxes = await client.find>( - DOMAIN_TX, - { - _class: core.class.TxRemoveDoc - }, - { projection: { objectId: 1 } } - ) - - const removedDocs: Set> = new Set(removeTxes.map((p) => p.objectId)) - const removedCollectionTxes = await client.find>( - DOMAIN_TX, - { - _class: core.class.TxCollectionCUD, - 'tx._class': core.class.TxRemoveDoc - }, - { projection: { tx: 1 } } - ) - removedCollectionTxes.forEach((p) => p.tx.objectId) - - const newLastView: Map, LastView> = new Map() - let total = 0 - while (true) { - const lastViews = await client.find( - DOMAIN_NOTIFICATION, - { - _class: notification.class.LastView, - attachedTo: { $exists: true } - }, - { limit: 10000 } - ) - total += lastViews.length - console.log(`migrate ${total} notifications`) - if (lastViews.length === 0) break - for (const lastView of lastViews) { - if ( - lastView.user !== core.account.System && - allowedClasses.has(lastView.attachedToClass) && - !removedDocs.has(lastView.attachedTo) - ) { - const obj: LastView = newLastView.get(lastView.user) ?? { - user: lastView.user, - modifiedBy: lastView.user, - modifiedOn: Date.now(), - _id: generateId(), - space: notification.space.Notifications, - _class: notification.class.LastView - } - obj[lastView.attachedTo] = lastView.lastView - newLastView.set(lastView.user, obj) - } - } - await Promise.all(lastViews.map((p) => client.delete(DOMAIN_NOTIFICATION, p._id))) - } - for (const [, lastView] of newLastView) { - await client.create(DOMAIN_NOTIFICATION, lastView) - } -} - async function fillCollaborators (client: MigrationClient): Promise { const targetClasses = await client.model.findAll(notification.mixin.ClassCollaborators, {}) for (const targetClass of targetClasses) { @@ -227,6 +141,28 @@ async function createCustomFieldTypes (client: MigrationUpgradeClient): Promise< } } +async function changeDocUpdatesSpaces (client: MigrationUpgradeClient): Promise { + const txop = new TxOperations(client, core.account.System) + const docUpdates = await client.findAll(notification.class.DocUpdates, { space: notification.space.Notifications }) + const map = new Map>, Map, DocUpdates[]>>() + for (const docUpdate of docUpdates) { + const _class = map.get(docUpdate.attachedToClass) ?? new Map() + const arr = _class.get(docUpdate.attachedTo) ?? [] + arr.push(docUpdate) + _class.set(docUpdate.attachedTo, arr) + map.set(docUpdate.attachedToClass, _class) + } + for (const [_class, arr] of map) { + const ids = Array.from(arr.keys()) + const docs = await client.findAll(_class, { _id: { $in: ids } }) + for (const doc of docs) { + const updateDocs = arr.get(doc._id) + if (updateDocs === undefined) continue + await Promise.all(updateDocs.map(async (p) => await txop.update(p, { space: doc.space }))) + } + } +} + async function cleanOutdatedSettings (client: MigrationClient): Promise { const res = await client.find(DOMAIN_NOTIFICATION, { _class: notification.class.NotificationSetting @@ -240,7 +176,6 @@ export const notificationOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await removeSettings(client) await fillNotificationText(client) - await migrateLastView(client) await fillCollaborators(client) await fillDocUpdatesHidder(client) await cleanOutdatedSettings(client) @@ -248,5 +183,6 @@ export const notificationOperation: MigrateOperation = { async upgrade (client: MigrationUpgradeClient): Promise { await createSpace(client) await createCustomFieldTypes(client) + await changeDocUpdatesSpaces(client) } } diff --git a/models/notification/src/plugin.ts b/models/notification/src/plugin.ts index 8e0916b704..9c945a78e1 100644 --- a/models/notification/src/plugin.ts +++ b/models/notification/src/plugin.ts @@ -23,7 +23,6 @@ import { Application } from '@hcengineering/workbench' export default mergeIds(notificationId, notification, { string: { - LastView: '' as IntlString, PlatformNotification: '' as IntlString, BrowserNotification: '' as IntlString, EmailNotification: '' as IntlString, diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index eda08c050c..2afd3710d5 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -679,10 +679,6 @@ export function createModel (builder: Builder): void { recruit.viewlet.ApplicantDashboard ) - builder.mixin(recruit.class.Applicant, core.class.Class, notification.mixin.TrackedDoc, {}) - - builder.mixin(recruit.class.Vacancy, core.class.Class, notification.mixin.TrackedDoc, {}) - builder.mixin(recruit.class.Applicant, core.class.Class, task.mixin.KanbanCard, { card: recruit.component.KanbanCard }) diff --git a/models/server-notification/src/index.ts b/models/server-notification/src/index.ts index 98966531bc..928402609b 100644 --- a/models/server-notification/src/index.ts +++ b/models/server-notification/src/index.ts @@ -55,22 +55,10 @@ export function createModel (builder: Builder): void { trigger: serverNotification.trigger.OnBacklinkCreate }) - builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverNotification.trigger.UpdateLastView - }) - builder.createDoc(serverCore.class.Trigger, core.space.Model, { trigger: serverNotification.trigger.CollaboratorDocHandler }) - builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverNotification.trigger.OnUpdateLastView - }) - - builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverNotification.trigger.OnAddCollborator - }) - builder.createDoc(serverCore.class.Trigger, core.space.Model, { trigger: serverNotification.trigger.OnAttributeCreate }) diff --git a/models/task/src/index.ts b/models/task/src/index.ts index afdef8b63f..fbae6560bc 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -34,7 +34,7 @@ import { } from '@hcengineering/model' import core, { TAttachedDoc, TClass, TDoc, TSpace, TStatus } from '@hcengineering/model-core' import view, { createAction, template, actionTemplates as viewTemplates } from '@hcengineering/model-view' -import notification from '@hcengineering/notification' +import {} from '@hcengineering/notification' import { IntlString } from '@hcengineering/platform' import tags from '@hcengineering/tags' import { @@ -321,8 +321,6 @@ export function createModel (builder: Builder): void { editor: task.component.TaskHeader }) - builder.mixin(task.class.Task, core.class.Class, notification.mixin.TrackedDoc, {}) - builder.createDoc( view.class.ActionCategory, core.space.Model, diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 544e54b7ed..24b31ba582 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -954,8 +954,6 @@ export function createModel (builder: Builder): void { inlineEditor: tracker.component.ComponentStatusEditor }) - builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.TrackedDoc, {}) - builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AllValuesFunc, { func: tracker.function.GetAllPriority }) diff --git a/plugins/board-resources/src/components/presenters/NotificationPresenter.svelte b/plugins/board-resources/src/components/presenters/NotificationPresenter.svelte index 01866bc38b..66728199a3 100644 --- a/plugins/board-resources/src/components/presenters/NotificationPresenter.svelte +++ b/plugins/board-resources/src/components/presenters/NotificationPresenter.svelte @@ -22,9 +22,8 @@ export let size: IconSize = 'small' const notificationClient = NotificationClientImpl.getClient() - const lastViews = notificationClient.getLastViews() - $: lastView = $lastViews[object._id] - $: subscribed = lastView !== undefined && lastView !== -1 + const store = notificationClient.docUpdatesStore + $: subscribed = $store.get(object._id) !== undefined {#if subscribed} diff --git a/plugins/chunter-resources/src/components/Channel.svelte b/plugins/chunter-resources/src/components/Channel.svelte index 63b125e1a3..7dcf6b42a1 100644 --- a/plugins/chunter-resources/src/components/Channel.svelte +++ b/plugins/chunter-resources/src/components/Channel.svelte @@ -15,8 +15,8 @@ diff --git a/plugins/chunter-resources/src/index.ts b/plugins/chunter-resources/src/index.ts index 40e37b15bf..5d27dd7d58 100644 --- a/plugins/chunter-resources/src/index.ts +++ b/plugins/chunter-resources/src/index.ts @@ -67,12 +67,12 @@ export { CommentPopup, CommentsPresenter } async function MarkUnread (object: Message): Promise { const client = NotificationClientImpl.getClient() - await client.updateLastView(object.space, chunter.class.ChunterSpace, object.createOn - 1, true) + await client.forceRead(object.space, chunter.class.ChunterSpace) } async function MarkCommentUnread (object: ThreadMessage): Promise { const client = NotificationClientImpl.getClient() - await client.updateLastView(object.attachedTo, object.attachedToClass, object.createOn - 1, true) + await client.forceRead(object.attachedTo, object.attachedToClass) } async function SubscribeMessage (object: Message): Promise { @@ -117,14 +117,20 @@ async function UnsubscribeMessage (object: ChunterMessage): Promise { } } ) - await notificationClient.unsubscribe(object.attachedTo) + const docUpdate = notificationClient.docUpdatesMap.get(object.attachedTo) + if (docUpdate !== undefined) { + await client.remove(docUpdate) + } } else { await client.updateMixin(object._id, object._class, object.space, notification.mixin.Collaborators, { $pull: { collaborators: acc._id } }) - await notificationClient.unsubscribe(object._id) + const docUpdate = notificationClient.docUpdatesMap.get(object._id) + if (docUpdate !== undefined) { + await client.remove(docUpdate) + } } } diff --git a/plugins/contact-resources/src/components/ChannelsDropdown.svelte b/plugins/contact-resources/src/components/ChannelsDropdown.svelte index f00208800d..03f80ab870 100644 --- a/plugins/contact-resources/src/components/ChannelsDropdown.svelte +++ b/plugins/contact-resources/src/components/ChannelsDropdown.svelte @@ -17,7 +17,7 @@ import type { Channel, ChannelProvider } from '@hcengineering/contact' import contact from '@hcengineering/contact' import type { AttachedData, Doc, Ref } from '@hcengineering/core' - import notification, { LastView } from '@hcengineering/notification' + import notification, { DocUpdates } from '@hcengineering/notification' import { Asset, IntlString, getResource } from '@hcengineering/platform' import presentation from '@hcengineering/presentation' import { @@ -50,8 +50,8 @@ export let focusIndex = -1 export let restricted: Ref[] = [] - let lastViews: Writable = writable() - getResource(notification.function.GetNotificationClient).then((res) => (lastViews = res().getLastViews())) + let docUpdates: Writable, DocUpdates>> = writable(new Map()) + getResource(notification.function.GetNotificationClient).then((res) => (docUpdates = res().docUpdatesStore)) const dispatch = createEventDispatcher() interface Item { @@ -70,11 +70,11 @@ function getProvider ( item: AttachedData, map: Map, ChannelProvider>, - lastViews: LastView | undefined + docUpdates: Map, DocUpdates> ): Item | undefined { const provider = map.get(item.provider) if (provider) { - const notification = (item as Channel)._id !== undefined ? isNew(item as Channel, lastViews) : false + const notification = (item as Channel)._id !== undefined ? isNew(item as Channel, docUpdates) : false return { label: provider.label, icon: provider.icon as Asset, @@ -92,16 +92,14 @@ } } - function isNew (item: Channel, lastViews: LastView | undefined): boolean { - if (item.lastMessage === undefined) return false - const lastView = - (item as Channel)._id !== undefined && lastViews !== undefined ? lastViews[(item as Channel)._id] : undefined - return lastView ? lastView < item.lastMessage : (item.items ?? 0) > 0 + function isNew (item: Channel, docUpdates: Map, DocUpdates>): boolean { + const docUpdate = docUpdates.get(item._id) + return docUpdate ? docUpdate.txes.length > 0 : (item.items ?? 0) > 0 } async function update ( value: AttachedData[] | Channel | null, - lastViews: LastView | undefined, + docUpdates: Map, DocUpdates>, channelProviders: ChannelProvider[] ) { if (value == null) { @@ -112,13 +110,13 @@ const map = getChannelProviders(channelProviders) if (Array.isArray(value)) { for (const item of value) { - const provider = getProvider(item, map, lastViews) + const provider = getProvider(item, map, docUpdates) if (provider !== undefined) { result.push(provider) } } } else { - const provider = getProvider(value, map, lastViews) + const provider = getProvider(value, map, docUpdates) if (provider !== undefined) { result.push(provider) } @@ -127,7 +125,7 @@ updateMenu(displayItems, channelProviders) } - $: if (value) update(value, $lastViews, $channelProviders) + $: if (value) update(value, $docUpdates, $channelProviders) let displayItems: Item[] = [] let actions: Action[] = [] @@ -148,7 +146,7 @@ icon: pr.icon ?? contact.icon.SocialEdit, label: pr.label, action: async () => { - const provider = getProvider({ provider: pr._id, value: '' }, getChannelProviders(providers), $lastViews) + const provider = getProvider({ provider: pr._id, value: '' }, getChannelProviders(providers), $docUpdates) if (provider !== undefined) { if (_displayItems.filter((it) => it.provider === pr._id).length === 0) { displayItems = [..._displayItems, provider] diff --git a/plugins/contact-resources/src/components/ChannelsView.svelte b/plugins/contact-resources/src/components/ChannelsView.svelte index 5205608bf5..0563769487 100644 --- a/plugins/contact-resources/src/components/ChannelsView.svelte +++ b/plugins/contact-resources/src/components/ChannelsView.svelte @@ -16,8 +16,8 @@ {#if hasNotification} diff --git a/plugins/notification-resources/src/utils.ts b/plugins/notification-resources/src/utils.ts index 0916ea508c..fb4d06805f 100644 --- a/plugins/notification-resources/src/utils.ts +++ b/plugins/notification-resources/src/utils.ts @@ -14,44 +14,35 @@ // limitations under the License. // -import core, { Account, Class, Doc, getCurrentAccount, Ref, Timestamp } from '@hcengineering/core' -import notification, { DocUpdates, LastView, NotificationClient } from '@hcengineering/notification' +import { Account, Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core' +import notification, { Collaborators, DocUpdates, NotificationClient } from '@hcengineering/notification' import { createQuery, getClient } from '@hcengineering/presentation' -import { get, writable, Writable } from 'svelte/store' +import { writable } from 'svelte/store' /** * @public */ export class NotificationClientImpl implements NotificationClient { protected static _instance: NotificationClientImpl | undefined = undefined - private readonly lastViewsStore = writable() readonly docUpdatesStore = writable, DocUpdates>>(new Map()) + docUpdatesMap: Map, DocUpdates> = new Map() + readonly docUpdates = writable([]) private readonly docUpdatesQuery = createQuery(true) - private readonly lastViewQuery = createQuery() private readonly user: Ref private constructor () { this.user = getCurrentAccount()._id - this.lastViewQuery.query(notification.class.LastView, { user: this.user }, (result) => { - this.lastViewsStore.set(result[0]) - if (result[0] === undefined) { - const client = getClient() - const u = client.txFactory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, { - user: this.user - }) - u.space = core.space.DerivedTx - void client.tx(u) - } - }) this.docUpdatesQuery.query( notification.class.DocUpdates, { user: this.user }, (result) => { - this.docUpdatesStore.set(new Map(result.map((p) => [p.attachedTo, p]))) + this.docUpdates.set(result) + this.docUpdatesMap = new Map(result.map((p) => [p.attachedTo, p])) + this.docUpdatesStore.set(this.docUpdatesMap) } ) } @@ -67,46 +58,49 @@ export class NotificationClientImpl implements NotificationClient { return NotificationClientImpl._instance } - getLastViews (): Writable { - return this.lastViewsStore - } - - async updateLastView ( - _id: Ref, - _class: Ref>, - time?: Timestamp, - force: boolean = false - ): Promise { + async read (_id: Ref): Promise { const client = getClient() - const hierarchy = client.getHierarchy() - const mixin = hierarchy.classHierarchyMixin(_class, notification.mixin.TrackedDoc) - if (mixin === undefined) return - const lastView = time ?? new Date().getTime() - const obj = get(this.lastViewsStore) - if (obj !== undefined) { - const current = obj[_id] as Timestamp | undefined - if (current !== undefined || force) { - if (current === -1 && !force) return - if (force || (current ?? 0) < lastView) { - const u = client.txFactory.createTxUpdateDoc(obj._class, obj.space, obj._id, { - [_id]: lastView - }) - u.space = core.space.DerivedTx - await client.tx(u) - } - } + const docUpdate = this.docUpdatesMap.get(_id) + if (docUpdate !== undefined) { + await client.update(docUpdate, { txes: [] }) } } - async unsubscribe (_id: Ref): Promise { + async forceRead (_id: Ref, _class: Ref>): Promise { const client = getClient() - const obj = get(this.lastViewsStore) - if (obj !== undefined) { - const u = client.txFactory.createTxUpdateDoc(obj._class, obj.space, obj._id, { - [_id]: -1 - }) - u.space = core.space.DerivedTx - await client.tx(u) + const docUpdate = this.docUpdatesMap.get(_id) + if (docUpdate !== undefined) { + await client.update(docUpdate, { txes: [] }) + } else { + const doc = await client.findOne(_class, { _id }) + if (doc !== undefined) { + const hiearachy = client.getHierarchy() + const collab = hiearachy.as(doc, notification.mixin.Collaborators) + if (collab.collaborators === undefined) { + await client.createMixin( + collab._id, + collab._class, + collab.space, + notification.mixin.Collaborators, + { + collaborators: [this.user] + } + ) + } else if (!collab.collaborators.includes(this.user)) { + await client.updateMixin(collab._id, collab._class, collab.space, notification.mixin.Collaborators, { + $push: { + collaborators: this.user + } + }) + } + await client.createDoc(notification.class.DocUpdates, doc.space, { + attachedTo: _id, + attachedToClass: _class, + user: this.user, + hidden: true, + txes: [] + }) + } } } } diff --git a/plugins/notification/src/index.ts b/plugins/notification/src/index.ts index f4ec48eea3..85ef22d3a4 100644 --- a/plugins/notification/src/index.ts +++ b/plugins/notification/src/index.ts @@ -34,14 +34,6 @@ import { Writable } from './types' import { Preference } from '@hcengineering/preference' export * from './types' -/** - * @public - */ -export interface LastView extends Doc { - user: Ref - [key: string]: any -} - /** * @public */ @@ -135,20 +127,6 @@ export interface NotificationSetting extends Preference { enabled: boolean } -/** - * @public - */ -export interface SpaceLastEdit extends Class { - lastEditField: string -} - -/** - * @public - */ -export interface AnotherUserNotifications extends Class { - fields: string[] -} - /** * @public */ @@ -156,11 +134,6 @@ export interface ClassCollaborators extends Class { fields: string[] // Ref | Ref | Ref[] | Ref[] } -/** - * @public - */ -export interface TrackedDoc extends Class {} - /** * @public */ @@ -183,8 +156,8 @@ export interface DocUpdates extends Doc { attachedTo: Ref attachedToClass: Ref> hidden: boolean - lastTx: Ref> - lastTxTime: Timestamp + lastTx?: Ref> + lastTxTime?: Timestamp txes: [Ref>, Timestamp][] } @@ -198,9 +171,9 @@ export const notificationId = 'notification' as Plugin */ export interface NotificationClient { docUpdatesStore: Writable, DocUpdates>> - getLastViews: () => Writable - updateLastView: (_id: Ref, _class: Ref>, time?: Timestamp, force?: boolean) => Promise - unsubscribe: (_id: Ref) => Promise + docUpdates: Writable + read: (_id: Ref) => Promise + forceRead: (_id: Ref, _class: Ref>, space: Ref) => Promise } /** @@ -213,15 +186,11 @@ export type NotificationClientFactoy = () => NotificationClient */ const notification = plugin(notificationId, { mixin: { - SpaceLastEdit: '' as Ref>, - AnotherUserNotifications: '' as Ref>, ClassCollaborators: '' as Ref>, Collaborators: '' as Ref>, - TrackedDoc: '' as Ref>, NotificationObjectPresenter: '' as Ref> }, class: { - LastView: '' as Ref>, Notification: '' as Ref>, EmailNotification: '' as Ref>, NotificationType: '' as Ref>, diff --git a/plugins/recruit-resources/src/components/EditVacancy.svelte b/plugins/recruit-resources/src/components/EditVacancy.svelte index 9e6fd1e726..5e9c6fe8d6 100644 --- a/plugins/recruit-resources/src/components/EditVacancy.svelte +++ b/plugins/recruit-resources/src/components/EditVacancy.svelte @@ -44,7 +44,7 @@ const notificationClient = getResource(notification.function.GetNotificationClient).then((res) => res()) onDestroy(async () => { - notificationClient.then((client) => client.updateLastView(_id, recruit.class.Vacancy)) + notificationClient.then((client) => client.read(_id)) }) const client = getClient() @@ -57,7 +57,7 @@ const prev = lastId lastId = _id if (prev) { - notificationClient.then((client) => client.updateLastView(prev, recruit.class.Vacancy)) + notificationClient.then((client) => client.read(prev)) } query.query(recruit.class.Vacancy, { _id }, (result) => { object = result[0] as Required diff --git a/plugins/telegram-resources/src/components/Chat.svelte b/plugins/telegram-resources/src/components/Chat.svelte index 82b86da42e..d6d9650e69 100644 --- a/plugins/telegram-resources/src/components/Chat.svelte +++ b/plugins/telegram-resources/src/components/Chat.svelte @@ -110,7 +110,7 @@ (res) => { messages = res.reverse() if (channel !== undefined) { - notificationClient.updateLastView(channel._id, channel._class, undefined, true) + notificationClient.read(channel._id) } }, { @@ -171,7 +171,7 @@ } ) if (channel !== undefined) { - await notificationClient.updateLastView(channel._id, channel._class, channel.modifiedOn, true) + await notificationClient.read(channel._id) } clear() } diff --git a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte index 4fb2356750..a21b7d86f6 100644 --- a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte +++ b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte @@ -47,7 +47,6 @@ export let embedded = false let lastId: Ref = _id - let lastClass: Ref> = _class const queryClient = createQuery() const dispatch = createEventDispatcher() const client = getClient() @@ -66,15 +65,13 @@ function read (_id: Ref) { if (lastId !== _id) { const prev = lastId - const prevClass = lastClass lastId = _id - lastClass = _class - notificationClient.then((client) => client.updateLastView(prev, prevClass)) + notificationClient.then((client) => client.read(prev)) } } onDestroy(async () => { - notificationClient.then((client) => client.updateLastView(_id, _class)) + notificationClient.then((client) => client.read(_id)) }) $: _id && diff --git a/plugins/tracker-resources/src/components/templates/EditIssueTemplate.svelte b/plugins/tracker-resources/src/components/templates/EditIssueTemplate.svelte index e115376087..fcea810aea 100644 --- a/plugins/tracker-resources/src/components/templates/EditIssueTemplate.svelte +++ b/plugins/tracker-resources/src/components/templates/EditIssueTemplate.svelte @@ -46,7 +46,6 @@ export let _class: Ref> let lastId: Ref = _id - let lastClass: Ref> = _class const query = createQuery() const dispatch = createEventDispatcher() const client = getClient() @@ -65,15 +64,13 @@ function read (_id: Ref) { if (lastId !== _id) { const prev = lastId - const prevClass = lastClass lastId = _id - lastClass = _class - notificationClient.then((client) => client.updateLastView(prev, prevClass)) + notificationClient.then((client) => client.read(prev)) } } onDestroy(async () => { - notificationClient.then((client) => client.updateLastView(_id, _class)) + notificationClient.then((client) => client.read(_id)) }) $: _id && diff --git a/plugins/view-resources/src/components/EditDoc.svelte b/plugins/view-resources/src/components/EditDoc.svelte index 95b611bc52..b021e52967 100644 --- a/plugins/view-resources/src/components/EditDoc.svelte +++ b/plugins/view-resources/src/components/EditDoc.svelte @@ -44,7 +44,6 @@ let realObjectClass: Ref> = _class let lastId: Ref = _id - let lastClass: Ref> = _class let object: Doc const client = getClient() @@ -55,15 +54,13 @@ function read (_id: Ref) { if (lastId !== _id) { const prev = lastId - const prevClass = lastClass lastId = _id - lastClass = _class - notificationClient.then((client) => client.updateLastView(prev, prevClass)) + notificationClient.then((client) => client.read(prev)) } } onDestroy(async () => { - notificationClient.then((client) => client.updateLastView(_id, _class)) + notificationClient.then((client) => client.read(_id)) }) const query = createQuery() diff --git a/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte b/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte index 688e83f212..e2681558dc 100644 --- a/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte +++ b/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte @@ -15,7 +15,7 @@ @@ -102,7 +94,7 @@ icon={classIcon(client, space._class)} selected={currentSpace === space._id} actions={() => getActions(space)} - bold={isChanged(space, $lastViews)} + bold={isChanged(space, $docUpdates)} /> {/await} diff --git a/server-plugins/chunter-resources/src/index.ts b/server-plugins/chunter-resources/src/index.ts index 28666bacf6..8a4098a63e 100644 --- a/server-plugins/chunter-resources/src/index.ts +++ b/server-plugins/chunter-resources/src/index.ts @@ -26,7 +26,6 @@ import core, { Hierarchy, Ref, Tx, - TxCollectionCUD, TxCreateDoc, TxCUD, TxProcessor, @@ -192,84 +191,11 @@ async function ThreadMessageDelete (tx: Tx, control: TriggerControl): Promise { - const hierarchy = control.hierarchy - const actualTx = TxProcessor.extractTx(tx) - if (actualTx._class !== core.class.TxCreateDoc) return [] - const doc = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc) - if (!hierarchy.isDerived(doc._class, chunter.class.Message)) { - return [] - } - - const message = doc as Message - - const channel = ( - await control.findAll( - chunter.class.ChunterSpace, - { - _id: message.space - }, - { limit: 1 } - ) - )[0] - - if (channel?.lastMessage === undefined || channel.lastMessage < message.createOn) { - const res = control.txFactory.createTxUpdateDoc(channel._class, channel.space, channel._id, { - lastMessage: message.createOn - }) - return [res] - } - return [] -} - -async function MessageDelete (tx: Tx, control: TriggerControl): Promise { - const hierarchy = control.hierarchy - - const rmTx = TxProcessor.extractTx(tx) as TxCollectionCUD - if (rmTx._class !== core.class.TxRemoveDoc) return [] - if (!hierarchy.isDerived(rmTx.objectClass, chunter.class.Message)) { - return [] - } - - const message = control.removedMap.get(rmTx.objectId) as Message - - if (message === undefined) { - return [] - } - - const channel = ( - await control.findAll( - chunter.class.ChunterSpace, - { - _id: message.space - }, - { limit: 1 } - ) - )[0] - - if (channel?.lastMessage === message.createOn) { - const messages = await control.findAll(chunter.class.Message, { - attachedTo: channel._id - }) - const lastMessageDate = messages.reduce((maxDate, mess) => (mess.createOn > maxDate ? mess.createOn : maxDate), 0) - - const updateTx = control.txFactory.createTxUpdateDoc(channel._class, channel.space, channel._id, { - lastMessage: lastMessageDate > 0 ? lastMessageDate : undefined - }) - - return [updateTx] - } - - return [] -} - /** * @public */ export async function ChunterTrigger (tx: Tx, control: TriggerControl): Promise { const promises = [ - MessageCreate(tx, control), - MessageDelete(tx, control), ThreadMessageCreate(tx, control), ThreadMessageDelete(tx, control), CommentCreate(tx as TxCUD, control) diff --git a/server-plugins/gmail-resources/src/index.ts b/server-plugins/gmail-resources/src/index.ts index 879d9e898e..1b5c6bbdd2 100644 --- a/server-plugins/gmail-resources/src/index.ts +++ b/server-plugins/gmail-resources/src/index.ts @@ -100,7 +100,7 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise } if (docs.length === 0) { res.push( - control.txFactory.createTxCreateDoc(notification.class.DocUpdates, notification.space.Notifications, { + control.txFactory.createTxCreateDoc(notification.class.DocUpdates, channel.space, { user: tx.modifiedBy, attachedTo: channel._id, attachedToClass: channel._class, diff --git a/server-plugins/notification-resources/src/index.ts b/server-plugins/notification-resources/src/index.ts index 10bf804dfb..172fec3249 100644 --- a/server-plugins/notification-resources/src/index.ts +++ b/server-plugins/notification-resources/src/index.ts @@ -45,7 +45,6 @@ import notification, { Collaborators, DocUpdates, EmailNotification, - LastView, NotificationProvider, NotificationType } from '@hcengineering/notification' @@ -54,7 +53,6 @@ import type { TriggerControl } from '@hcengineering/server-core' import serverNotification, { HTMLPresenter, TextPresenter, - createLastViewTx, getEmployeeAccount, getEmployeeAccountById } from '@hcengineering/server-notification' @@ -254,62 +252,6 @@ async function getEmailNotificationTx ( } } -/** - * @public - */ -export async function UpdateLastView (tx: Tx, control: TriggerControl): Promise { - const actualTx = TxProcessor.extractTx(tx) - if (actualTx._class !== core.class.TxRemoveDoc) { - return [] - } - - if ((actualTx as TxCUD).objectClass === notification.class.LastView) { - return [] - } - - const result: Tx[] = [] - - const removeTx = actualTx as TxRemoveDoc - const lastViews = await control.findAll(notification.class.LastView, { [removeTx.objectId]: { $exists: true } }) - for (const lastView of lastViews) { - const clearTx = control.txFactory.createTxUpdateDoc(lastView._class, lastView.space, lastView._id, { - $unset: { - [removeTx.objectId]: '' - } - }) - result.push(clearTx) - } - return result -} - -/** - * @public - */ -export async function OnUpdateLastView (tx: Tx, control: TriggerControl): Promise { - const actualTx = TxProcessor.extractTx(tx) as TxUpdateDoc - if (actualTx._class !== core.class.TxUpdateDoc) return [] - if (actualTx.objectClass !== notification.class.LastView) return [] - const result: Tx[] = [] - const lastView = (await control.findAll(notification.class.LastView, { _id: actualTx.objectId }))[0] - if (lastView === undefined) return result - for (const key in actualTx.operations) { - const docs = await control.findAll(notification.class.DocUpdates, { - attachedTo: key as Ref, - user: lastView.user - }) - for (const doc of docs) { - const txes = doc.txes.filter((p) => p[1] > actualTx.operations[key]) - result.push( - control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, { - txes - }) - ) - } - } - - return result -} - function getBacklink (ptx: TxCollectionCUD): Backlink { return TxProcessor.createDoc2Doc(ptx.tx as TxCreateDoc) } @@ -493,7 +435,7 @@ async function getNotificationTxes ( const current = docUpdates.find((p) => p.user === target) if (current === undefined) { res.push( - control.txFactory.createTxCreateDoc(notification.class.DocUpdates, notification.space.Notifications, { + control.txFactory.createTxCreateDoc(notification.class.DocUpdates, object.space, { user: target, attachedTo: object._id, attachedToClass: object._class, @@ -709,6 +651,17 @@ function isMixinTx (tx: TxUpdateDoc | TxMixin): tx is TxMixin | TxMixin): Promise { + if (tx._class !== core.class.TxUpdateDoc) return [] + const ctx = tx as TxUpdateDoc + if (ctx.operations.space === undefined) return [] + const docUpdates = await control.findAll(notification.class.DocUpdates, { attachedTo: tx.objectId }) + + return docUpdates.map((value) => + control.txFactory.createTxUpdateDoc(value._class, value.space, value._id, { space: ctx.operations.space }) + ) +} + async function updateCollaboratorDoc ( tx: TxUpdateDoc | TxMixin, control: TriggerControl, @@ -750,47 +703,11 @@ async function updateCollaboratorDoc ( res = res.concat(await getSpaceCollabTxes(control, doc, tx, originTx)) + res = res.concat(await changeSpaceTxes(control, tx)) + return res } -/** - * @public - */ -export async function OnAddCollborator (tx: Tx, control: TriggerControl): Promise { - const result: Tx[] = [] - const actualTx = TxProcessor.extractTx(tx) as TxMixin - - if (actualTx._class !== core.class.TxMixin) return [] - if (actualTx.mixin !== notification.mixin.Collaborators) return [] - if (actualTx.attributes.collaborators !== undefined) { - for (const collab of actualTx.attributes.collaborators) { - const resTx = await createLastViewTx(control.findAll, actualTx.objectId, collab) - if (resTx !== undefined) { - result.push(resTx) - } - } - } - if (actualTx.attributes.$push?.collaborators !== undefined) { - const collab = actualTx.attributes.$push?.collaborators - if (typeof collab === 'object') { - if ('$each' in collab) { - for (const collaborator of collab.$each) { - const resTx = await createLastViewTx(control.findAll, actualTx.objectId, collaborator) - if (resTx !== undefined) { - result.push(resTx) - } - } - } - } else { - const resTx = await createLastViewTx(control.findAll, actualTx.objectId, collab) - if (resTx !== undefined) { - result.push(resTx) - } - } - } - return result -} - /** * @public */ @@ -897,9 +814,6 @@ export default async () => ({ trigger: { OnBacklinkCreate, CollaboratorDocHandler: collaboratorDocHandler, - OnUpdateLastView, - UpdateLastView, - OnAddCollborator, OnAttributeCreate, OnAttributeUpdate }, diff --git a/server-plugins/notification/src/index.ts b/server-plugins/notification/src/index.ts index 8b90548fcd..b4f3ea024e 100644 --- a/server-plugins/notification/src/index.ts +++ b/server-plugins/notification/src/index.ts @@ -15,8 +15,8 @@ // import contact, { Employee, EmployeeAccount } from '@hcengineering/contact' -import { Account, Class, Doc, Mixin, Ref, Tx, TxCreateDoc, TxFactory, TxUpdateDoc } from '@hcengineering/core' -import notification, { LastView, NotificationType } from '@hcengineering/notification' +import { Account, Class, Doc, Mixin, Ref, Tx } from '@hcengineering/core' +import { NotificationType } from '@hcengineering/notification' import { Plugin, Resource, plugin } from '@hcengineering/platform' import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core' @@ -25,42 +25,6 @@ import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core' */ export const serverNotificationId = 'server-notification' as Plugin -/** - * @public - */ -export async function getUpdateLastViewTx ( - findAll: TriggerControl['findAll'], - attachedTo: Ref, - lastView: number, - user: Ref -): Promise | TxCreateDoc | undefined> { - const current = ( - await findAll( - notification.class.LastView, - { - user - }, - { limit: 1 } - ) - )[0] - const factory = new TxFactory(user, true) - if (current !== undefined) { - if (current[attachedTo] === -1 || current[attachedTo] >= lastView) { - return - } - const u = factory.createTxUpdateDoc(current._class, current.space, current._id, { - [attachedTo]: lastView - }) - return u - } else { - const u = factory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, { - user, - [attachedTo]: lastView - }) - return u - } -} - /** * @public */ @@ -115,38 +79,6 @@ export async function getEmployee (employee: Ref, control: TriggerCont return account } -/** - * @public - */ -export async function createLastViewTx ( - findAll: TriggerControl['findAll'], - attachedTo: Ref, - user: Ref -): Promise | TxUpdateDoc | undefined> { - const current = ( - await findAll( - notification.class.LastView, - { - user - }, - { limit: 1 } - ) - )[0] - const factory = new TxFactory(user, true) - if (current === undefined) { - const u = factory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, { - user, - [attachedTo]: 1 - }) - return u - } else if (current[attachedTo] === undefined) { - const u = factory.createTxUpdateDoc(current._class, current.space, current._id, { - [attachedTo]: 1 - }) - return u - } -} - /** * @public */ @@ -191,10 +123,7 @@ export default plugin(serverNotificationId, { }, trigger: { OnBacklinkCreate: '' as Resource, - UpdateLastView: '' as Resource, - OnUpdateLastView: '' as Resource, CollaboratorDocHandler: '' as Resource, - OnAddCollborator: '' as Resource, OnAttributeCreate: '' as Resource, OnAttributeUpdate: '' as Resource }, diff --git a/server-plugins/telegram-resources/src/index.ts b/server-plugins/telegram-resources/src/index.ts index e5e2a46c00..3373fabb31 100644 --- a/server-plugins/telegram-resources/src/index.ts +++ b/server-plugins/telegram-resources/src/index.ts @@ -101,7 +101,7 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise } if (docs.length === 0) { res.push( - control.txFactory.createTxCreateDoc(notification.class.DocUpdates, notification.space.Notifications, { + control.txFactory.createTxCreateDoc(notification.class.DocUpdates, channel.space, { user: tx.modifiedBy, attachedTo: channel._id, attachedToClass: channel._class,