Fix channels hide and return autohide (#6529)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-09-11 19:35:18 +04:00 committed by GitHub
parent 4e44f73314
commit 682e4b3011
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 129 additions and 196 deletions

View File

@ -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<Person>
hidden!: Ref<DocNotifyContext>[]
timestamp!: Timestamp
@ -190,8 +181,7 @@ export function createModel (builder: Builder): void {
TThreadMessage,
TChatMessageViewlet,
TObjectChatPanel,
TChatInfo,
TChannelInfo,
TChatSyncInfo,
TInlineButton,
TTypingInfo
)

View File

@ -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<Class<Doc>> })
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) => {

View File

@ -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<TxCUD<Doc>>
}

View File

@ -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 }
)
}
}
])

View File

@ -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: {

View File

@ -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
})

View File

@ -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

View File

@ -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<void> {
const ops = getClient().apply(undefined, 'removeActivityChannels')
export async function hideActivityChannels (contexts: DocNotifyContext[]): Promise<void> {
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)

View File

@ -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<Channel>, 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 {

View File

@ -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<Person>
hidden: Ref<DocNotifyContext>[]
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<ChatMessage>, channel: Ref<Doc>) => Promise<void>
export interface InlineButton extends AttachedDoc {
@ -146,13 +141,12 @@ export default plugin(chunterId, {
DirectMessage: '' as Ref<Class<DirectMessage>>,
ChatMessage: '' as Ref<Class<ChatMessage>>,
ChatMessageViewlet: '' as Ref<Class<ChatMessageViewlet>>,
ChatInfo: '' as Ref<Class<ChatInfo>>,
ChatSyncInfo: '' as Ref<Class<ChatSyncInfo>>,
InlineButton: '' as Ref<Class<InlineButton>>,
TypingInfo: '' as Ref<Class<TypingInfo>>
},
mixin: {
ObjectChatPanel: '' as Ref<Mixin<ObjectChatPanel>>,
ChannelInfo: '' as Ref<Mixin<ChannelInfo>>
ObjectChatPanel: '' as Ref<Mixin<ObjectChatPanel>>
},
string: {
Reactions: '' as IntlString,

View File

@ -285,6 +285,7 @@ export interface DocNotifyContext extends Doc<PersonSpace> {
objectSpace: Ref<Space>
isPinned: boolean
hidden: boolean
lastViewedTimestamp?: Timestamp
lastUpdateTimestamp?: Timestamp

View File

@ -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<TxMixin<DocNotifyContext, ChannelInfo>[]> {
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<TxMixin<DocNotifyContext, ChannelInfo>[]> {
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<TxMixin<DocNotifyContext, ChannelInfo>[]> {
const res: TxMixin<DocNotifyContext, ChannelInfo>[] = []
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<void> {
export async function syncChat (control: TriggerControl, status: UserStatus, date: Timestamp): Promise<void> {
const account = getPersonAccountById(status.user as Ref<PersonAccount>, 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<DocNotifyContext>[] = 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<UserStatus>, control: TriggerControl): Promise<Tx[]> {
// const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
// if (tx.objectClass !== core.class.UserStatus) return []
// if (tx._class === core.class.TxCreateDoc) {
// const createTx = tx as TxCreateDoc<UserStatus>
// 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<UserStatus>
// 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<DocNotifyContext>, control: TriggerControl): Promise<Tx[]> {
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<UserStatus>
if (tx.objectClass !== core.class.UserStatus) return []
if (tx._class === core.class.TxCreateDoc) {
const createTx = tx as TxCreateDoc<UserStatus>
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<UserStatus>
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,

View File

@ -31,8 +31,7 @@ export default plugin(serverChunterId, {
ChunterTrigger: '' as Resource<TriggerFunc>,
OnChatMessageRemoved: '' as Resource<TriggerFunc>,
ChatNotificationsHandler: '' as Resource<TriggerFunc>,
OnUserStatus: '' as Resource<TriggerFunc>,
OnContextUpdate: '' as Resource<TriggerFunc>
OnUserStatus: '' as Resource<TriggerFunc>
},
function: {
CommentRemove: '' as Resource<ObjectDDParticipantFunc>,

View File

@ -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)
}

View File

@ -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

View File

@ -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 })
}