diff --git a/models/chunter/src/index.ts b/models/chunter/src/index.ts index f570e4cd0a..769a294793 100644 --- a/models/chunter/src/index.ts +++ b/models/chunter/src/index.ts @@ -23,8 +23,7 @@ import { type ChunterSpace, type ObjectChatPanel, type ThreadMessage, - type ChatInfo, - type ChannelInfo, + type ChatSyncInfo, type InlineButton, type TypingInfo, type InlineButtonAction @@ -52,12 +51,10 @@ import { TypeRef, TypeString, TypeTimestamp, - UX, - Hidden + UX } from '@hcengineering/model' import attachment from '@hcengineering/model-attachment' import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@hcengineering/model-core' -import notification, { TDocNotifyContext } from '@hcengineering/model-notification' import view from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' import { type IntlString, type Resource } from '@hcengineering/platform' @@ -152,14 +149,8 @@ export class TObjectChatPanel extends TClass implements ObjectChatPanel { ignoreKeys!: string[] } -@Mixin(chunter.mixin.ChannelInfo, notification.class.DocNotifyContext) -export class TChannelInfo extends TDocNotifyContext implements ChannelInfo { - @Hidden() - hidden!: boolean -} - -@Model(chunter.class.ChatInfo, core.class.Doc, DOMAIN_CHUNTER) -export class TChatInfo extends TDoc implements ChatInfo { +@Model(chunter.class.ChatSyncInfo, core.class.Doc, DOMAIN_CHUNTER) +export class TChatSyncInfo extends TDoc implements ChatSyncInfo { user!: Ref hidden!: Ref[] timestamp!: Timestamp @@ -190,8 +181,7 @@ export function createModel (builder: Builder): void { TThreadMessage, TChatMessageViewlet, TObjectChatPanel, - TChatInfo, - TChannelInfo, + TChatSyncInfo, TInlineButton, TTypingInfo ) diff --git a/models/chunter/src/migration.ts b/models/chunter/src/migration.ts index 5ab8d328c6..14e1638d62 100644 --- a/models/chunter/src/migration.ts +++ b/models/chunter/src/migration.ts @@ -67,6 +67,7 @@ export async function createDocNotifyContexts ( objectId, objectClass, objectSpace, + hidden: false, isPinned: false }) } @@ -332,6 +333,19 @@ export const chunterOperation: MigrateOperation = { await removeWrongActivity(client) } }, + { + state: 'remove-chat-info-v1', + func: async (client) => { + await client.deleteMany(DOMAIN_CHUNTER, { _class: 'chunter:class:ChatInfo' as Ref> }) + await client.deleteMany(DOMAIN_TX, { objectClass: 'chunter:class:ChatInfo' }) + await client.update( + DOMAIN_DOC_NOTIFY, + { 'chunter:mixin:ChannelInfo': { $exists: true } }, + { $unset: { 'chunter:mixin:ChannelInfo': true } } + ) + await client.deleteMany(DOMAIN_TX, { mixin: 'chunter:mixin:ChannelInfo' }) + } + }, { state: 'remove-duplicated-directs-v1', func: async (client) => { diff --git a/models/notification/src/index.ts b/models/notification/src/index.ts index 7894bcc583..8ede6fbe79 100644 --- a/models/notification/src/index.ts +++ b/models/notification/src/index.ts @@ -218,6 +218,9 @@ export class TDocNotifyContext extends TDoc implements DocNotifyContext { @Prop(TypeBoolean(), notification.string.Pinned) isPinned!: boolean + @Prop(TypeBoolean(), view.string.Hide) + hidden!: boolean + tx?: Ref> } diff --git a/models/notification/src/migration.ts b/models/notification/src/migration.ts index 43eb4f450a..c65195816b 100644 --- a/models/notification/src/migration.ts +++ b/models/notification/src/migration.ts @@ -383,6 +383,16 @@ export const notificationOperation: MigrateOperation = { { state: 'migrate-duplicated-contexts-v1', func: migrateDuplicateContexts + }, + { + state: 'set-default-hidden', + func: async () => { + await client.update( + DOMAIN_DOC_NOTIFY, + { _class: notification.class.DocNotifyContext, hidden: { $exists: false } }, + { hidden: false } + ) + } } ]) diff --git a/models/server-chunter/src/index.ts b/models/server-chunter/src/index.ts index b74fa8fa47..5887285e75 100644 --- a/models/server-chunter/src/index.ts +++ b/models/server-chunter/src/index.ts @@ -75,14 +75,6 @@ export function createModel (builder: Builder): void { } ) - builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverChunter.trigger.OnContextUpdate, - txMatch: { - _class: core.class.TxUpdateDoc, - objectClass: notification.class.DocNotifyContext - } - }) - builder.createDoc(serverCore.class.Trigger, core.space.Model, { trigger: serverChunter.trigger.OnChatMessageRemoved, txMatch: { diff --git a/plugins/chunter-resources/src/components/chat/create/CreateDirectChat.svelte b/plugins/chunter-resources/src/components/chat/create/CreateDirectChat.svelte index e777e90381..7be56685e3 100644 --- a/plugins/chunter-resources/src/components/chat/create/CreateDirectChat.svelte +++ b/plugins/chunter-resources/src/components/chat/create/CreateDirectChat.svelte @@ -97,9 +97,12 @@ }) if (context !== undefined) { + if (context.hidden) { + await client.updateDoc(context._class, context.space, context._id, { hidden: false }) + } + dispatch('close') openChannel(dmId, chunter.class.DirectMessage) - return } @@ -110,6 +113,7 @@ objectId: dmId, objectClass: chunter.class.DirectMessage, objectSpace: core.space.Space, + hidden: false, isPinned: false }) diff --git a/plugins/chunter-resources/src/components/chat/navigator/ChatNavGroup.svelte b/plugins/chunter-resources/src/components/chat/navigator/ChatNavGroup.svelte index 63807eedb5..e174e3426c 100644 --- a/plugins/chunter-resources/src/components/chat/navigator/ChatNavGroup.svelte +++ b/plugins/chunter-resources/src/components/chat/navigator/ChatNavGroup.svelte @@ -50,7 +50,9 @@ let sections: Section[] = [] - $: contexts = $contextsStore.filter(({ objectClass, isPinned }) => { + $: contexts = $contextsStore.filter((it) => { + const { objectClass, isPinned, hidden } = it + if (hidden) return false if (model.isPinned !== isPinned) return false if (model._class !== undefined && model._class !== objectClass) return false if (model.skipClasses !== undefined && model.skipClasses.includes(objectClass)) return false diff --git a/plugins/chunter-resources/src/components/chat/utils.ts b/plugins/chunter-resources/src/components/chat/utils.ts index 41fffc19a5..377cd196d8 100644 --- a/plugins/chunter-resources/src/components/chat/utils.ts +++ b/plugins/chunter-resources/src/components/chat/utils.ts @@ -357,7 +357,7 @@ function archiveActivityChannels (contexts: DocNotifyContext[]): void { label: chunter.string.ArchiveActivityConfirmationTitle, message: chunter.string.ArchiveActivityConfirmationMessage, action: async () => { - await removeActivityChannels(contexts) + await hideActivityChannels(contexts) } }, 'top' @@ -385,19 +385,12 @@ export function loadSavedAttachments (): void { } } -export async function removeActivityChannels (contexts: DocNotifyContext[]): Promise { - const ops = getClient().apply(undefined, 'removeActivityChannels') +export async function hideActivityChannels (contexts: DocNotifyContext[]): Promise { + const ops = getClient().apply(undefined, 'hideActivityChannels') try { for (const context of contexts) { - await ops.createMixin(context._id, context._class, context.space, chunter.mixin.ChannelInfo, { hidden: true }) - } - const hidden = contexts.map(({ _id }) => _id) - const account = getCurrentAccount() as PersonAccount - const chatInfo = await ops.findOne(chunter.class.ChatInfo, { user: account.person }) - - if (chatInfo !== undefined) { - await ops.update(chatInfo, { hidden: chatInfo.hidden.concat(hidden) }) + await ops.update(context, { hidden: true }) } } finally { await ops.commit() @@ -412,7 +405,7 @@ export async function readActivityChannels (contexts: DocNotifyContext[]): Promi try { for (const context of contexts) { const notifications = notificationsByContext.get(context._id) ?? [] - await client.archiveNotifications( + await client.readNotifications( ops, notifications .filter(({ _class }) => _class === notification.class.ActivityInboxNotification) diff --git a/plugins/chunter-resources/src/utils.ts b/plugins/chunter-resources/src/utils.ts index 43f252b31e..788a756b13 100644 --- a/plugins/chunter-resources/src/utils.ts +++ b/plugins/chunter-resources/src/utils.ts @@ -513,21 +513,12 @@ export async function removeChannelAction (context?: DocNotifyContext, _?: Event if (hierarchy.isDerived(objectClass, chunter.class.Channel)) { const channel = await client.findOne(chunter.class.Channel, { _id: objectId as Ref, space: objectSpace }) await leaveChannel(channel, getCurrentAccount()._id) + await client.remove(context) } else { const object = await client.findOne(objectClass, { _id: objectId, space: objectSpace }) - // const account = getCurrentAccount() as PersonAccount - - // await client.createMixin(context._id, context._class, context.space, chunter.mixin.ChannelInfo, { hidden: true }) - // - // const chatInfo = await client.findOne(chunter.class.ChatInfo, { user: account.person }) - // - // if (chatInfo !== undefined) { - // await client.update(chatInfo, { hidden: chatInfo.hidden.concat([context._id]) }) - // } + await client.update(context, { hidden: true }) await resetChunterLocIfEqual(objectId, objectClass, object) } - - await client.remove(context) } export function isThreadMessage (message: ActivityMessage): message is ThreadMessage { diff --git a/plugins/chunter/src/index.ts b/plugins/chunter/src/index.ts index b027c3f459..6f63376819 100644 --- a/plugins/chunter/src/index.ts +++ b/plugins/chunter/src/index.ts @@ -15,7 +15,7 @@ import { ActivityMessage, ActivityMessageViewlet } from '@hcengineering/activity' import type { AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@hcengineering/core' -import { DocNotifyContext, NotificationType } from '@hcengineering/notification' +import { NotificationType } from '@hcengineering/notification' import type { Asset, Plugin, Resource } from '@hcengineering/platform' import { IntlString, plugin } from '@hcengineering/platform' import { AnyComponent } from '@hcengineering/ui' @@ -77,9 +77,8 @@ export interface ChatMessageViewlet extends ActivityMessageViewlet { label?: IntlString } -export interface ChatInfo extends Doc { +export interface ChatSyncInfo extends Doc { user: Ref - hidden: Ref[] timestamp: Timestamp } @@ -90,10 +89,6 @@ export interface TypingInfo extends Doc { lastTyping: Timestamp } -export interface ChannelInfo extends DocNotifyContext { - hidden: boolean -} - export type InlineButtonAction = (button: InlineButton, message: Ref, channel: Ref) => Promise export interface InlineButton extends AttachedDoc { @@ -146,13 +141,12 @@ export default plugin(chunterId, { DirectMessage: '' as Ref>, ChatMessage: '' as Ref>, ChatMessageViewlet: '' as Ref>, - ChatInfo: '' as Ref>, + ChatSyncInfo: '' as Ref>, InlineButton: '' as Ref>, TypingInfo: '' as Ref> }, mixin: { - ObjectChatPanel: '' as Ref>, - ChannelInfo: '' as Ref> + ObjectChatPanel: '' as Ref> }, string: { Reactions: '' as IntlString, diff --git a/plugins/notification/src/index.ts b/plugins/notification/src/index.ts index dbb22a3843..75febb84e5 100644 --- a/plugins/notification/src/index.ts +++ b/plugins/notification/src/index.ts @@ -285,6 +285,7 @@ export interface DocNotifyContext extends Doc { objectSpace: Ref isPinned: boolean + hidden: boolean lastViewedTimestamp?: Timestamp lastUpdateTimestamp?: Timestamp diff --git a/server-plugins/chunter-resources/src/index.ts b/server-plugins/chunter-resources/src/index.ts index ebd46b7776..7adf410b2b 100644 --- a/server-plugins/chunter-resources/src/index.ts +++ b/server-plugins/chunter-resources/src/index.ts @@ -14,15 +14,8 @@ // import activity, { ActivityMessage, ActivityReference } from '@hcengineering/activity' -import chunter, { - Channel, - ChannelInfo, - ChatMessage, - chunterId, - ChunterSpace, - ThreadMessage -} from '@hcengineering/chunter' -import { Person, PersonAccount } from '@hcengineering/contact' +import chunter, { Channel, ChatMessage, chunterId, ChunterSpace, ThreadMessage } from '@hcengineering/chunter' +import contact, { Person, PersonAccount } from '@hcengineering/contact' import core, { Account, AttachedDoc, @@ -39,7 +32,6 @@ import core, { TxCollectionCUD, TxCreateDoc, TxCUD, - TxMixin, TxProcessor, TxRemoveDoc, TxUpdateDoc, @@ -391,94 +383,55 @@ function combineAttributes (attributes: any[], key: string, operator: string, ar ).filter((v) => v != null) } -async function hideOldDirects ( - directs: DocNotifyContext[], - control: TriggerControl, - date: Timestamp -): Promise[]> { - const visibleDirects = directs.filter((context) => { - const hasMixin = control.hierarchy.hasMixin(context, chunter.mixin.ChannelInfo) - if (!hasMixin) return true - const info = control.hierarchy.as(context, chunter.mixin.ChannelInfo) - - return !info.hidden - }) - +function getDirectsToHide (directs: DocNotifyContext[], date: Timestamp): DocNotifyContext[] { const minVisibleDirects = 10 - if (visibleDirects.length <= minVisibleDirects) return [] - const canHide = visibleDirects.length - minVisibleDirects + if (directs.length <= minVisibleDirects) return [] + const hideCount = directs.length - minVisibleDirects - let toHide: DocNotifyContext[] = [] + const toHide: DocNotifyContext[] = [] for (const context of directs) { const { lastUpdateTimestamp = 0, lastViewedTimestamp = 0 } = context - + if (lastViewedTimestamp === 0) continue if (lastUpdateTimestamp > lastViewedTimestamp) continue - if (date - lastUpdateTimestamp < hideChannelDelay) continue - - toHide.push(context) + if (date - lastUpdateTimestamp > hideChannelDelay) { + toHide.push(context) + } } - if (toHide.length > canHide) { - toHide = toHide.splice(0, toHide.length - canHide) - } + toHide.sort((a, b) => (a.lastUpdateTimestamp ?? 0) - (b.lastUpdateTimestamp ?? 0)) - return await hideOldChannels(toHide, control) + return toHide.slice(0, hideCount) } -async function hideOldActivityChannels ( - contexts: DocNotifyContext[], - control: TriggerControl, - date: Timestamp -): Promise[]> { +function getActivityToHide (contexts: DocNotifyContext[], date: Timestamp): DocNotifyContext[] { if (contexts.length === 0) return [] - - const { hierarchy } = control const toHide: DocNotifyContext[] = [] for (const context of contexts) { const { lastUpdateTimestamp = 0, lastViewedTimestamp = 0 } = context - + if (lastViewedTimestamp === 0) continue if (lastUpdateTimestamp > lastViewedTimestamp) continue - if (date - lastUpdateTimestamp < hideChannelDelay) continue - - const params = hierarchy.as(context, chunter.mixin.ChannelInfo) - if (params.hidden) continue - - toHide.push(context) + if (date - lastUpdateTimestamp > hideChannelDelay) { + toHide.push(context) + } } - return await hideOldChannels(toHide, control) + return toHide } -async function hideOldChannels ( - contexts: DocNotifyContext[], - control: TriggerControl -): Promise[]> { - const res: TxMixin[] = [] - - for (const context of contexts) { - const tx = control.txFactory.createTxMixin(context._id, context._class, context.space, chunter.mixin.ChannelInfo, { - hidden: true - }) - res.push(tx) - } - - return res -} - -export async function updateChatInfo (control: TriggerControl, status: UserStatus, date: Timestamp): Promise { +export async function syncChat (control: TriggerControl, status: UserStatus, date: Timestamp): Promise { const account = getPersonAccountById(status.user as Ref, control) if (account === undefined) return - const update = (await control.findAll(control.ctx, chunter.class.ChatInfo, { user: account.person })).shift() - const shouldUpdate = update === undefined || date - update.timestamp > updateChatInfoDelay - - if (!shouldUpdate) return + const syncInfo = (await control.findAll(control.ctx, chunter.class.ChatSyncInfo, { user: account.person })).shift() + const shouldSync = syncInfo === undefined || date - syncInfo.timestamp > updateChatInfoDelay + if (!shouldSync) return const contexts = await control.findAll(control.ctx, notification.class.DocNotifyContext, { user: account._id, + hidden: false, isPinned: false }) @@ -492,83 +445,63 @@ export async function updateChatInfo (control: TriggerControl, status: UserStatu ) const activityContexts = contexts.filter( ({ objectClass }) => - !hierarchy.isDerived(objectClass, chunter.class.DirectMessage) && - !hierarchy.isDerived(objectClass, chunter.class.Channel) && + !hierarchy.isDerived(objectClass, chunter.class.ChunterSpace) && !hierarchy.isDerived(objectClass, activity.class.ActivityMessage) ) - const directTxes = await hideOldDirects(directContexts, control, date) - const activityTxes = await hideOldActivityChannels(activityContexts, control, date) - const mixinTxes = directTxes.concat(activityTxes) - const hidden: Ref[] = mixinTxes.map((tx) => tx.objectId) + const directsToHide = getDirectsToHide(directContexts, date) + const activityToHide = getActivityToHide(activityContexts, date) + const contextsToHide = directsToHide.concat(activityToHide) - res.push(...mixinTxes) - - if (update === undefined) { + for (const context of contextsToHide) { res.push( - control.txFactory.createTxCreateDoc(chunter.class.ChatInfo, core.space.Workspace, { - user: account.person, - hidden, - timestamp: date + control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, { + hidden: true }) ) + } + + if (syncInfo === undefined) { + const personSpace = ( + await control.findAll(control.ctx, contact.class.PersonSpace, { person: account.person }) + ).shift() + if (personSpace !== undefined) { + res.push( + control.txFactory.createTxCreateDoc(chunter.class.ChatSyncInfo, personSpace._id, { + user: account.person, + timestamp: date + }) + ) + } } else { res.push( - control.txFactory.createTxUpdateDoc(update._class, update.space, update._id, { - hidden: Array.from(new Set(update.hidden.concat(hidden))), + control.txFactory.createTxUpdateDoc(syncInfo._class, syncInfo.space, syncInfo._id, { timestamp: date }) ) } - const txIds = res.map((tx) => tx._id) - - await control.apply(control.ctx, res) - - control.ctx.contextData.broadcast.targets.docNotifyContext = (it) => { - if (txIds.includes(it._id)) { - return [account.email] - } - } + await control.apply(control.ctx, res, true) } async function OnUserStatus (originTx: TxCUD, control: TriggerControl): Promise { - // const tx = TxProcessor.extractTx(originTx) as TxCUD - // if (tx.objectClass !== core.class.UserStatus) return [] - // if (tx._class === core.class.TxCreateDoc) { - // const createTx = tx as TxCreateDoc - // const { online } = createTx.attributes - // if (online) { - // const status = TxProcessor.createDoc2Doc(createTx) - // await updateChatInfo(control, status, originTx.modifiedOn) - // } - // } else if (tx._class === core.class.TxUpdateDoc) { - // const updateTx = tx as TxUpdateDoc - // const { online } = updateTx.operations - // if (online === true) { - // const status = (await control.findAll(core.class.UserStatus, { _id: updateTx.objectId }))[0] - // await updateChatInfo(control, status, originTx.modifiedOn) - // } - // } - - return [] -} - -async function OnContextUpdate (tx: TxUpdateDoc, control: TriggerControl): Promise { - const hasUpdate = 'lastUpdateTimestamp' in tx.operations && tx.operations.lastUpdateTimestamp !== undefined - if (!hasUpdate) return [] - - // const update = (await control.findAll(notification.class.DocNotifyContext, { _id: tx.objectId }, { limit: 1 })).shift() - // if (update !== undefined) { - // const as = control.hierarchy.as(update, chunter.mixin.ChannelInfo) - // if (as.hidden) { - // return [ - // control.txFactory.createTxMixin(tx.objectId, tx.objectClass, tx.objectSpace, chunter.mixin.ChannelInfo, { - // hidden: false - // }) - // ] - // } - // } + const tx = TxProcessor.extractTx(originTx) as TxCUD + if (tx.objectClass !== core.class.UserStatus) return [] + if (tx._class === core.class.TxCreateDoc) { + const createTx = tx as TxCreateDoc + const { online } = createTx.attributes + if (online) { + const status = TxProcessor.createDoc2Doc(createTx) + await syncChat(control, status, originTx.modifiedOn) + } + } else if (tx._class === core.class.TxUpdateDoc) { + const updateTx = tx as TxUpdateDoc + const { online } = updateTx.operations + if (online === true) { + const status = (await control.findAll(control.ctx, core.class.UserStatus, { _id: updateTx.objectId }))[0] + await syncChat(control, status, originTx.modifiedOn) + } + } return [] } @@ -589,8 +522,7 @@ export default async () => ({ ChunterTrigger, OnChatMessageRemoved, ChatNotificationsHandler, - OnUserStatus, - OnContextUpdate + OnUserStatus }, function: { CommentRemove, diff --git a/server-plugins/chunter/src/index.ts b/server-plugins/chunter/src/index.ts index dc92deda3d..2a32b63396 100644 --- a/server-plugins/chunter/src/index.ts +++ b/server-plugins/chunter/src/index.ts @@ -31,8 +31,7 @@ export default plugin(serverChunterId, { ChunterTrigger: '' as Resource, OnChatMessageRemoved: '' as Resource, ChatNotificationsHandler: '' as Resource, - OnUserStatus: '' as Resource, - OnContextUpdate: '' as Resource + OnUserStatus: '' as Resource }, function: { CommentRemove: '' as Resource, diff --git a/server-plugins/notification-resources/src/index.ts b/server-plugins/notification-resources/src/index.ts index fc080e0c6d..9390692e34 100644 --- a/server-plugins/notification-resources/src/index.ts +++ b/server-plugins/notification-resources/src/index.ts @@ -716,6 +716,7 @@ async function createNotifyContext ( objectClass, objectSpace, isPinned: false, + hidden: false, tx: tx?._id, lastUpdateTimestamp: updateTimestamp, lastViewedTimestamp: sender === receiver._id ? updateTimestamp : undefined @@ -828,10 +829,11 @@ async function updateContextsTimestamp ( const res: Tx[] = [] for (const context of contexts) { - const account = getPersonAccountById(context.user, control) // accounts.find(({ _id }) => _id === context.user) + const account = getPersonAccountById(context.user, control) const isViewed = context.lastViewedTimestamp !== undefined && (context.lastUpdateTimestamp ?? 0) <= context.lastViewedTimestamp const updateTx = control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, { + hidden: false, lastUpdateTimestamp: timestamp, ...(isViewed && modifiedBy === context.user ? { @@ -1680,7 +1682,11 @@ async function updateCollaborators ( const info = toReceiverInfo(hierarchy, addedUser) if (info === undefined) continue const context = getDocNotifyContext(control, contexts, objectId, info._id) - if (context !== undefined) continue + if (context !== undefined) { + if (context.hidden) { + res.push(control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, { hidden: false })) + } + } await createNotifyContext(ctx, control, objectId, objectClass, objectSpace, info, tx.modifiedBy, undefined, tx) } diff --git a/services/ai-bot/pod-ai-bot/src/utils.ts b/services/ai-bot/pod-ai-bot/src/utils.ts index 7046e8189e..001a1043c5 100644 --- a/services/ai-bot/pod-ai-bot/src/utils.ts +++ b/services/ai-bot/pod-ai-bot/src/utils.ts @@ -74,7 +74,8 @@ export async function getDirect ( objectId: dmId, objectClass: chunter.class.DirectMessage, objectSpace: core.space.Space, - isPinned: false + isPinned: false, + hidden: false }) return dmId diff --git a/services/github/pod-github/src/notifications.ts b/services/github/pod-github/src/notifications.ts index a7884d06ac..3d84e5bdec 100644 --- a/services/github/pod-github/src/notifications.ts +++ b/services/github/pod-github/src/notifications.ts @@ -17,7 +17,8 @@ export async function createNotification ( objectClass: forDoc._class, objectSpace: forDoc.space, user: data.user, - isPinned: false + isPinned: false, + hidden: false }) docNotifyContext = await client.findOne(notification.class.DocNotifyContext, { _id: docNotifyContextId }) }