mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03:00
UBERF-7016: Hide channels without any activity long time (#6176)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
1340ca78a9
commit
416eb9942e
249
models/chunter/src/actions.ts
Normal file
249
models/chunter/src/actions.ts
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
//
|
||||||
|
// Copyright © 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { type Builder } from '@hcengineering/model'
|
||||||
|
import view, { actionTemplates as viewTemplates, createAction, template } from '@hcengineering/model-view'
|
||||||
|
import notification, { notificationActionTemplates } from '@hcengineering/model-notification'
|
||||||
|
import activity from '@hcengineering/activity'
|
||||||
|
import workbench from '@hcengineering/model-workbench'
|
||||||
|
|
||||||
|
import chunter from './plugin'
|
||||||
|
|
||||||
|
const actionTemplates = template({
|
||||||
|
removeChannel: {
|
||||||
|
action: chunter.actionImpl.RemoveChannel,
|
||||||
|
label: view.string.Archive,
|
||||||
|
icon: view.icon.Delete,
|
||||||
|
input: 'focus',
|
||||||
|
keyBinding: ['Backspace'],
|
||||||
|
category: chunter.category.Chunter,
|
||||||
|
target: notification.class.DocNotifyContext,
|
||||||
|
context: { mode: ['context', 'browser'], group: 'remove' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function defineActions (builder: Builder): void {
|
||||||
|
createAction(
|
||||||
|
builder,
|
||||||
|
{
|
||||||
|
action: chunter.actionImpl.ReplyToThread,
|
||||||
|
label: chunter.string.ReplyToThread,
|
||||||
|
icon: chunter.icon.Thread,
|
||||||
|
input: 'focus',
|
||||||
|
category: chunter.category.Chunter,
|
||||||
|
target: activity.class.ActivityMessage,
|
||||||
|
visibilityTester: chunter.function.CanReplyToThread,
|
||||||
|
inline: true,
|
||||||
|
context: {
|
||||||
|
mode: 'context',
|
||||||
|
group: 'edit'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunter.action.ReplyToThreadAction
|
||||||
|
)
|
||||||
|
|
||||||
|
createAction(
|
||||||
|
builder,
|
||||||
|
{
|
||||||
|
action: view.actionImpl.CopyTextToClipboard,
|
||||||
|
actionProps: {
|
||||||
|
textProvider: chunter.function.GetLink
|
||||||
|
},
|
||||||
|
label: chunter.string.CopyLink,
|
||||||
|
icon: chunter.icon.Copy,
|
||||||
|
input: 'none',
|
||||||
|
category: chunter.category.Chunter,
|
||||||
|
target: activity.class.ActivityMessage,
|
||||||
|
visibilityTester: chunter.function.CanCopyMessageLink,
|
||||||
|
context: {
|
||||||
|
mode: ['context', 'browser'],
|
||||||
|
application: chunter.app.Chunter,
|
||||||
|
group: 'copy'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunter.action.CopyChatMessageLink
|
||||||
|
)
|
||||||
|
|
||||||
|
createAction(
|
||||||
|
builder,
|
||||||
|
{
|
||||||
|
action: chunter.actionImpl.UnarchiveChannel,
|
||||||
|
label: chunter.string.UnarchiveChannel,
|
||||||
|
icon: view.icon.Archive,
|
||||||
|
input: 'focus',
|
||||||
|
category: chunter.category.Chunter,
|
||||||
|
target: chunter.class.Channel,
|
||||||
|
query: {
|
||||||
|
archived: true
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
mode: 'context',
|
||||||
|
group: 'tools'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunter.action.UnarchiveChannel
|
||||||
|
)
|
||||||
|
|
||||||
|
createAction(
|
||||||
|
builder,
|
||||||
|
{
|
||||||
|
action: chunter.actionImpl.ConvertDmToPrivateChannel,
|
||||||
|
label: chunter.string.ConvertToPrivate,
|
||||||
|
icon: chunter.icon.Lock,
|
||||||
|
input: 'focus',
|
||||||
|
category: chunter.category.Chunter,
|
||||||
|
target: chunter.class.DirectMessage,
|
||||||
|
context: {
|
||||||
|
mode: 'context',
|
||||||
|
group: 'edit'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunter.action.ConvertToPrivate
|
||||||
|
)
|
||||||
|
|
||||||
|
createAction(
|
||||||
|
builder,
|
||||||
|
{
|
||||||
|
action: chunter.actionImpl.ArchiveChannel,
|
||||||
|
label: chunter.string.ArchiveChannel,
|
||||||
|
icon: view.icon.Archive,
|
||||||
|
input: 'focus',
|
||||||
|
category: chunter.category.Chunter,
|
||||||
|
target: chunter.class.Channel,
|
||||||
|
query: {
|
||||||
|
archived: false
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
mode: 'context',
|
||||||
|
group: 'tools'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunter.action.ArchiveChannel
|
||||||
|
)
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
...viewTemplates.open,
|
||||||
|
target: chunter.class.Channel,
|
||||||
|
context: {
|
||||||
|
mode: ['browser', 'context'],
|
||||||
|
group: 'create'
|
||||||
|
},
|
||||||
|
action: workbench.actionImpl.Navigate,
|
||||||
|
actionProps: {
|
||||||
|
mode: 'space'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
createAction(
|
||||||
|
builder,
|
||||||
|
{
|
||||||
|
action: chunter.actionImpl.DeleteChatMessage,
|
||||||
|
label: view.string.Delete,
|
||||||
|
icon: view.icon.Delete,
|
||||||
|
input: 'focus',
|
||||||
|
keyBinding: ['Backspace'],
|
||||||
|
category: chunter.category.Chunter,
|
||||||
|
target: chunter.class.ChatMessage,
|
||||||
|
visibilityTester: chunter.function.CanDeleteMessage,
|
||||||
|
context: { mode: ['context', 'browser'], group: 'remove' }
|
||||||
|
},
|
||||||
|
chunter.action.DeleteChatMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
createAction(
|
||||||
|
builder,
|
||||||
|
{
|
||||||
|
...actionTemplates.removeChannel,
|
||||||
|
icon: view.icon.EyeCrossed,
|
||||||
|
label: view.string.Hide,
|
||||||
|
query: {
|
||||||
|
attachedToClass: { $nin: [chunter.class.DirectMessage, chunter.class.Channel] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunter.action.RemoveChannel
|
||||||
|
)
|
||||||
|
|
||||||
|
createAction(
|
||||||
|
builder,
|
||||||
|
{
|
||||||
|
...actionTemplates.removeChannel,
|
||||||
|
label: chunter.string.CloseConversation,
|
||||||
|
query: {
|
||||||
|
attachedToClass: chunter.class.DirectMessage
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunter.action.CloseConversation
|
||||||
|
)
|
||||||
|
|
||||||
|
createAction(
|
||||||
|
builder,
|
||||||
|
{
|
||||||
|
...actionTemplates.removeChannel,
|
||||||
|
action: chunter.actionImpl.LeaveChannel,
|
||||||
|
label: chunter.string.LeaveChannel,
|
||||||
|
query: {
|
||||||
|
attachedToClass: chunter.class.Channel
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunter.action.LeaveChannel
|
||||||
|
)
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
...notificationActionTemplates.pinContext,
|
||||||
|
label: chunter.string.StarChannel,
|
||||||
|
query: {
|
||||||
|
attachedToClass: chunter.class.Channel
|
||||||
|
},
|
||||||
|
override: [notification.action.PinDocNotifyContext]
|
||||||
|
})
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
...notificationActionTemplates.unpinContext,
|
||||||
|
label: chunter.string.UnstarChannel,
|
||||||
|
query: {
|
||||||
|
attachedToClass: chunter.class.Channel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
...notificationActionTemplates.pinContext,
|
||||||
|
label: chunter.string.StarConversation,
|
||||||
|
query: {
|
||||||
|
attachedToClass: chunter.class.DirectMessage
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
...notificationActionTemplates.unpinContext,
|
||||||
|
label: chunter.string.UnstarConversation,
|
||||||
|
query: {
|
||||||
|
attachedToClass: chunter.class.DirectMessage
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
...notificationActionTemplates.pinContext,
|
||||||
|
query: {
|
||||||
|
attachedToClass: { $nin: [chunter.class.DirectMessage, chunter.class.Channel] }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
...notificationActionTemplates.unpinContext,
|
||||||
|
query: {
|
||||||
|
attachedToClass: { $nin: [chunter.class.DirectMessage, chunter.class.Channel] }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -22,10 +22,12 @@ import {
|
|||||||
type ChatMessageViewlet,
|
type ChatMessageViewlet,
|
||||||
type ChunterSpace,
|
type ChunterSpace,
|
||||||
type ObjectChatPanel,
|
type ObjectChatPanel,
|
||||||
type ThreadMessage
|
type ThreadMessage,
|
||||||
|
type ChatInfo,
|
||||||
|
type ChannelInfo
|
||||||
} from '@hcengineering/chunter'
|
} from '@hcengineering/chunter'
|
||||||
import presentation from '@hcengineering/model-presentation'
|
import presentation from '@hcengineering/model-presentation'
|
||||||
import contact from '@hcengineering/contact'
|
import contact, { type Person } from '@hcengineering/contact'
|
||||||
import {
|
import {
|
||||||
type Class,
|
type Class,
|
||||||
type Doc,
|
type Doc,
|
||||||
@ -46,17 +48,20 @@ import {
|
|||||||
TypeRef,
|
TypeRef,
|
||||||
TypeString,
|
TypeString,
|
||||||
TypeTimestamp,
|
TypeTimestamp,
|
||||||
UX
|
UX,
|
||||||
|
Hidden
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import attachment from '@hcengineering/model-attachment'
|
import attachment from '@hcengineering/model-attachment'
|
||||||
import core, { TClass, TDoc, TSpace } from '@hcengineering/model-core'
|
import core, { TClass, TDoc, TSpace } from '@hcengineering/model-core'
|
||||||
import notification, { notificationActionTemplates } from '@hcengineering/model-notification'
|
import notification, { TDocNotifyContext } from '@hcengineering/model-notification'
|
||||||
import view, { createAction, template, actionTemplates as viewTemplates } from '@hcengineering/model-view'
|
import view from '@hcengineering/model-view'
|
||||||
import workbench from '@hcengineering/model-workbench'
|
import workbench from '@hcengineering/model-workbench'
|
||||||
import type { IntlString } from '@hcengineering/platform'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
import { TActivityMessage } from '@hcengineering/model-activity'
|
import { TActivityMessage } from '@hcengineering/model-activity'
|
||||||
|
import { type DocNotifyContext } from '@hcengineering/notification'
|
||||||
|
|
||||||
import chunter from './plugin'
|
import chunter from './plugin'
|
||||||
|
import { defineActions } from './actions'
|
||||||
|
|
||||||
export { chunterId } from '@hcengineering/chunter'
|
export { chunterId } from '@hcengineering/chunter'
|
||||||
export { chunterOperation } from './migration'
|
export { chunterOperation } from './migration'
|
||||||
@ -133,18 +138,18 @@ export class TObjectChatPanel extends TClass implements ObjectChatPanel {
|
|||||||
ignoreKeys!: string[]
|
ignoreKeys!: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionTemplates = template({
|
@Mixin(chunter.mixin.ChannelInfo, notification.class.DocNotifyContext)
|
||||||
removeChannel: {
|
export class TChannelInfo extends TDocNotifyContext implements ChannelInfo {
|
||||||
action: chunter.actionImpl.RemoveChannel,
|
@Hidden()
|
||||||
label: view.string.Archive,
|
hidden!: boolean
|
||||||
icon: view.icon.Delete,
|
}
|
||||||
input: 'focus',
|
|
||||||
keyBinding: ['Backspace'],
|
@Model(chunter.class.ChatInfo, core.class.Doc, DOMAIN_CHUNTER)
|
||||||
category: chunter.category.Chunter,
|
export class TChatInfo extends TDoc implements ChatInfo {
|
||||||
target: notification.class.DocNotifyContext,
|
user!: Ref<Person>
|
||||||
context: { mode: ['context', 'browser'], group: 'remove' }
|
hidden!: Ref<DocNotifyContext>[]
|
||||||
}
|
timestamp!: Timestamp
|
||||||
})
|
}
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(
|
builder.createModel(
|
||||||
@ -154,7 +159,9 @@ export function createModel (builder: Builder): void {
|
|||||||
TChatMessage,
|
TChatMessage,
|
||||||
TThreadMessage,
|
TThreadMessage,
|
||||||
TChatMessageViewlet,
|
TChatMessageViewlet,
|
||||||
TObjectChatPanel
|
TObjectChatPanel,
|
||||||
|
TChatInfo,
|
||||||
|
TChannelInfo
|
||||||
)
|
)
|
||||||
const spaceClasses = [chunter.class.Channel, chunter.class.DirectMessage]
|
const spaceClasses = [chunter.class.Channel, chunter.class.DirectMessage]
|
||||||
|
|
||||||
@ -236,26 +243,6 @@ export function createModel (builder: Builder): void {
|
|||||||
chunter.category.Chunter
|
chunter.category.Chunter
|
||||||
)
|
)
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
action: chunter.actionImpl.ArchiveChannel,
|
|
||||||
label: chunter.string.ArchiveChannel,
|
|
||||||
icon: view.icon.Archive,
|
|
||||||
input: 'focus',
|
|
||||||
category: chunter.category.Chunter,
|
|
||||||
target: chunter.class.Channel,
|
|
||||||
query: {
|
|
||||||
archived: false
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
mode: 'context',
|
|
||||||
group: 'tools'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chunter.action.ArchiveChannel
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
view.class.Viewlet,
|
view.class.Viewlet,
|
||||||
core.space.Model,
|
core.space.Model,
|
||||||
@ -271,43 +258,6 @@ export function createModel (builder: Builder): void {
|
|||||||
chunter.viewlet.Channels
|
chunter.viewlet.Channels
|
||||||
)
|
)
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
action: chunter.actionImpl.UnarchiveChannel,
|
|
||||||
label: chunter.string.UnarchiveChannel,
|
|
||||||
icon: view.icon.Archive,
|
|
||||||
input: 'focus',
|
|
||||||
category: chunter.category.Chunter,
|
|
||||||
target: chunter.class.Channel,
|
|
||||||
query: {
|
|
||||||
archived: true
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
mode: 'context',
|
|
||||||
group: 'tools'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chunter.action.UnarchiveChannel
|
|
||||||
)
|
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
action: chunter.actionImpl.ConvertDmToPrivateChannel,
|
|
||||||
label: chunter.string.ConvertToPrivate,
|
|
||||||
icon: chunter.icon.Lock,
|
|
||||||
input: 'focus',
|
|
||||||
category: chunter.category.Chunter,
|
|
||||||
target: chunter.class.DirectMessage,
|
|
||||||
context: {
|
|
||||||
mode: 'context',
|
|
||||||
group: 'edit'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chunter.action.ConvertToPrivate
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
workbench.class.Application,
|
workbench.class.Application,
|
||||||
core.space.Model,
|
core.space.Model,
|
||||||
@ -330,28 +280,6 @@ export function createModel (builder: Builder): void {
|
|||||||
encode: chunter.function.GetThreadLink
|
encode: chunter.function.GetThreadLink
|
||||||
})
|
})
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
action: view.actionImpl.CopyTextToClipboard,
|
|
||||||
actionProps: {
|
|
||||||
textProvider: chunter.function.GetLink
|
|
||||||
},
|
|
||||||
label: chunter.string.CopyLink,
|
|
||||||
icon: chunter.icon.Copy,
|
|
||||||
input: 'none',
|
|
||||||
category: chunter.category.Chunter,
|
|
||||||
target: activity.class.ActivityMessage,
|
|
||||||
visibilityTester: chunter.function.CanCopyMessageLink,
|
|
||||||
context: {
|
|
||||||
mode: ['context', 'browser'],
|
|
||||||
application: chunter.app.Chunter,
|
|
||||||
group: 'copy'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chunter.action.CopyChatMessageLink
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ClassFilters, {
|
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ClassFilters, {
|
||||||
filters: []
|
filters: []
|
||||||
})
|
})
|
||||||
@ -428,19 +356,6 @@ export function createModel (builder: Builder): void {
|
|||||||
chunter.ids.ThreadNotification
|
chunter.ids.ThreadNotification
|
||||||
)
|
)
|
||||||
|
|
||||||
createAction(builder, {
|
|
||||||
...viewTemplates.open,
|
|
||||||
target: chunter.class.Channel,
|
|
||||||
context: {
|
|
||||||
mode: ['browser', 'context'],
|
|
||||||
group: 'create'
|
|
||||||
},
|
|
||||||
action: workbench.actionImpl.Navigate,
|
|
||||||
actionProps: {
|
|
||||||
mode: 'space'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
builder.createDoc(activity.class.ActivityMessagesFilter, core.space.Model, {
|
builder.createDoc(activity.class.ActivityMessagesFilter, core.space.Model, {
|
||||||
label: chunter.string.Comments,
|
label: chunter.string.Comments,
|
||||||
position: 60,
|
position: 60,
|
||||||
@ -478,105 +393,6 @@ export function createModel (builder: Builder): void {
|
|||||||
chunter.ids.ThreadMessageViewlet
|
chunter.ids.ThreadMessageViewlet
|
||||||
)
|
)
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
action: chunter.actionImpl.DeleteChatMessage,
|
|
||||||
label: view.string.Delete,
|
|
||||||
icon: view.icon.Delete,
|
|
||||||
input: 'focus',
|
|
||||||
keyBinding: ['Backspace'],
|
|
||||||
category: chunter.category.Chunter,
|
|
||||||
target: chunter.class.ChatMessage,
|
|
||||||
visibilityTester: chunter.function.CanDeleteMessage,
|
|
||||||
context: { mode: ['context', 'browser'], group: 'remove' }
|
|
||||||
},
|
|
||||||
chunter.action.DeleteChatMessage
|
|
||||||
)
|
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
...actionTemplates.removeChannel,
|
|
||||||
query: {
|
|
||||||
attachedToClass: { $nin: [chunter.class.DirectMessage, chunter.class.Channel] }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chunter.action.RemoveChannel
|
|
||||||
)
|
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
...actionTemplates.removeChannel,
|
|
||||||
label: chunter.string.CloseConversation,
|
|
||||||
query: {
|
|
||||||
attachedToClass: chunter.class.DirectMessage
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chunter.action.CloseConversation
|
|
||||||
)
|
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
...actionTemplates.removeChannel,
|
|
||||||
action: chunter.actionImpl.LeaveChannel,
|
|
||||||
label: chunter.string.LeaveChannel,
|
|
||||||
query: {
|
|
||||||
attachedToClass: chunter.class.Channel
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chunter.action.LeaveChannel
|
|
||||||
)
|
|
||||||
|
|
||||||
createAction(builder, {
|
|
||||||
...notificationActionTemplates.pinContext,
|
|
||||||
label: chunter.string.StarChannel,
|
|
||||||
query: {
|
|
||||||
attachedToClass: chunter.class.Channel
|
|
||||||
},
|
|
||||||
override: [notification.action.PinDocNotifyContext]
|
|
||||||
})
|
|
||||||
|
|
||||||
createAction(builder, {
|
|
||||||
...notificationActionTemplates.unpinContext,
|
|
||||||
label: chunter.string.UnstarChannel,
|
|
||||||
query: {
|
|
||||||
attachedToClass: chunter.class.Channel
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createAction(builder, {
|
|
||||||
...notificationActionTemplates.pinContext,
|
|
||||||
label: chunter.string.StarConversation,
|
|
||||||
query: {
|
|
||||||
attachedToClass: chunter.class.DirectMessage
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createAction(builder, {
|
|
||||||
...notificationActionTemplates.unpinContext,
|
|
||||||
label: chunter.string.UnstarConversation,
|
|
||||||
query: {
|
|
||||||
attachedToClass: chunter.class.DirectMessage
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createAction(builder, {
|
|
||||||
...notificationActionTemplates.pinContext,
|
|
||||||
query: {
|
|
||||||
attachedToClass: { $nin: [chunter.class.DirectMessage, chunter.class.Channel] }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createAction(builder, {
|
|
||||||
...notificationActionTemplates.unpinContext,
|
|
||||||
query: {
|
|
||||||
attachedToClass: { $nin: [chunter.class.DirectMessage, chunter.class.Channel] }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
|
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
|
||||||
ofClass: chunter.class.Channel,
|
ofClass: chunter.class.Channel,
|
||||||
components: { input: chunter.component.ChatMessageInput }
|
components: { input: chunter.component.ChatMessageInput }
|
||||||
@ -627,25 +443,6 @@ export function createModel (builder: Builder): void {
|
|||||||
function: chunter.function.ReplyToThread
|
function: chunter.function.ReplyToThread
|
||||||
})
|
})
|
||||||
|
|
||||||
createAction(
|
|
||||||
builder,
|
|
||||||
{
|
|
||||||
action: chunter.actionImpl.ReplyToThread,
|
|
||||||
label: chunter.string.ReplyToThread,
|
|
||||||
icon: chunter.icon.Thread,
|
|
||||||
input: 'focus',
|
|
||||||
category: chunter.category.Chunter,
|
|
||||||
target: activity.class.ActivityMessage,
|
|
||||||
visibilityTester: chunter.function.CanReplyToThread,
|
|
||||||
inline: true,
|
|
||||||
context: {
|
|
||||||
mode: 'context',
|
|
||||||
group: 'edit'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chunter.action.ReplyToThreadAction
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ClassFilters, {
|
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ClassFilters, {
|
||||||
filters: ['name', 'topic', 'private', 'archived', 'members'],
|
filters: ['name', 'topic', 'private', 'archived', 'members'],
|
||||||
strict: true
|
strict: true
|
||||||
@ -662,6 +459,8 @@ export function createModel (builder: Builder): void {
|
|||||||
ignoredTypes: [],
|
ignoredTypes: [],
|
||||||
enabledTypes: [chunter.ids.DMNotification, chunter.ids.ChannelNotification, chunter.ids.ThreadNotification]
|
enabledTypes: [chunter.ids.DMNotification, chunter.ids.ChannelNotification, chunter.ids.ThreadNotification]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineActions(builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default chunter
|
export default chunter
|
||||||
|
@ -63,8 +63,7 @@ export async function createDocNotifyContexts (
|
|||||||
await tx.createDoc(notification.class.DocNotifyContext, core.space.Space, {
|
await tx.createDoc(notification.class.DocNotifyContext, core.space.Space, {
|
||||||
user: user._id,
|
user: user._id,
|
||||||
attachedTo,
|
attachedTo,
|
||||||
attachedToClass,
|
attachedToClass
|
||||||
hidden: false
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,10 +203,6 @@ export class TDocNotifyContext extends TDoc implements DocNotifyContext {
|
|||||||
@Index(IndexKind.Indexed)
|
@Index(IndexKind.Indexed)
|
||||||
attachedToClass!: Ref<Class<Doc>>
|
attachedToClass!: Ref<Class<Doc>>
|
||||||
|
|
||||||
@Prop(TypeBoolean(), core.string.Archived)
|
|
||||||
@Index(IndexKind.Indexed)
|
|
||||||
hidden!: boolean
|
|
||||||
|
|
||||||
@Prop(TypeDate(), core.string.Date)
|
@Prop(TypeDate(), core.string.Date)
|
||||||
lastViewedTimestamp?: Timestamp
|
lastViewedTimestamp?: Timestamp
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import chunter from '@hcengineering/chunter'
|
|||||||
import serverNotification from '@hcengineering/server-notification'
|
import serverNotification from '@hcengineering/server-notification'
|
||||||
import serverCore, { type ObjectDDParticipant } from '@hcengineering/server-core'
|
import serverCore, { type ObjectDDParticipant } from '@hcengineering/server-core'
|
||||||
import serverChunter from '@hcengineering/server-chunter'
|
import serverChunter from '@hcengineering/server-chunter'
|
||||||
|
import notification from '@hcengineering/notification'
|
||||||
|
|
||||||
export { serverChunterId } from '@hcengineering/server-chunter'
|
export { serverChunterId } from '@hcengineering/server-chunter'
|
||||||
|
|
||||||
@ -61,6 +62,22 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
|
trigger: serverChunter.trigger.OnUserStatus,
|
||||||
|
txMatch: {
|
||||||
|
objectClass: core.class.UserStatus
|
||||||
|
},
|
||||||
|
isAsync: true
|
||||||
|
})
|
||||||
|
|
||||||
|
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, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverChunter.trigger.OnChatMessageRemoved,
|
trigger: serverChunter.trigger.OnChatMessageRemoved,
|
||||||
txMatch: {
|
txMatch: {
|
||||||
|
@ -84,6 +84,8 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
|||||||
private readonly isInitialLoadedStore = writable(false)
|
private readonly isInitialLoadedStore = writable(false)
|
||||||
private readonly isTailLoading = writable(false)
|
private readonly isTailLoading = writable(false)
|
||||||
|
|
||||||
|
readonly isTailLoaded = writable(false)
|
||||||
|
|
||||||
public datesStore = writable<Timestamp[]>([])
|
public datesStore = writable<Timestamp[]>([])
|
||||||
public newTimestampStore = writable<Timestamp | undefined>(undefined)
|
public newTimestampStore = writable<Timestamp | undefined>(undefined)
|
||||||
|
|
||||||
@ -264,6 +266,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
|||||||
this.tailStore.set(res.reverse())
|
this.tailStore.set(res.reverse())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isTailLoaded.set(true)
|
||||||
this.isTailLoading.set(false)
|
this.isTailLoading.set(false)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -557,6 +560,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
|||||||
this.isInitialLoadedStore.set(false)
|
this.isInitialLoadedStore.set(false)
|
||||||
this.tailQuery.unsubscribe()
|
this.tailQuery.unsubscribe()
|
||||||
this.tailStart = undefined
|
this.tailStart = undefined
|
||||||
|
this.isTailLoaded.set(false)
|
||||||
this.backwardNextPromise = undefined
|
this.backwardNextPromise = undefined
|
||||||
this.forwardNextPromise = undefined
|
this.forwardNextPromise = undefined
|
||||||
this.forwardNextStore.set(undefined)
|
this.forwardNextStore.set(undefined)
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
import { Loading, ModernButton, Scroller, ScrollParams } from '@hcengineering/ui'
|
import { Loading, ModernButton, Scroller, ScrollParams } from '@hcengineering/ui'
|
||||||
import { afterUpdate, beforeUpdate, onDestroy, onMount, tick } from 'svelte'
|
import { afterUpdate, beforeUpdate, onDestroy, onMount, tick } from 'svelte'
|
||||||
import { get } from 'svelte/store'
|
import { get } from 'svelte/store'
|
||||||
|
import { DocNotifyContext } from '@hcengineering/notification'
|
||||||
|
|
||||||
import { ChannelDataProvider, MessageMetadata } from '../channelDataProvider'
|
import { ChannelDataProvider, MessageMetadata } from '../channelDataProvider'
|
||||||
import {
|
import {
|
||||||
@ -52,7 +53,7 @@
|
|||||||
export let objectClass: Ref<Class<Doc>>
|
export let objectClass: Ref<Class<Doc>>
|
||||||
export let objectId: Ref<Doc>
|
export let objectId: Ref<Doc>
|
||||||
export let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
|
export let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
|
||||||
export let scrollElement: HTMLDivElement | undefined = undefined
|
export let scrollElement: HTMLDivElement | undefined | null = undefined
|
||||||
export let startFromBottom = false
|
export let startFromBottom = false
|
||||||
export let selectedFilters: Ref<ActivityMessagesFilter>[] = []
|
export let selectedFilters: Ref<ActivityMessagesFilter>[] = []
|
||||||
export let embedded = false
|
export let embedded = false
|
||||||
@ -70,9 +71,9 @@
|
|||||||
const loadMoreThreshold = 40
|
const loadMoreThreshold = 40
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const hierarchy = client.getHierarchy()
|
|
||||||
const inboxClient = InboxNotificationsClientImpl.getClient()
|
const inboxClient = InboxNotificationsClientImpl.getClient()
|
||||||
const contextByDocStore = inboxClient.contextByDoc
|
const contextByDocStore = inboxClient.contextByDoc
|
||||||
|
const notificationsByContextStore = inboxClient.inboxNotificationsByContext
|
||||||
|
|
||||||
let filters: ActivityMessagesFilter[] = []
|
let filters: ActivityMessagesFilter[] = []
|
||||||
const filterResources = new Map<
|
const filterResources = new Map<
|
||||||
@ -83,6 +84,7 @@
|
|||||||
const messagesStore = provider.messagesStore
|
const messagesStore = provider.messagesStore
|
||||||
const isLoadingStore = provider.isLoadingStore
|
const isLoadingStore = provider.isLoadingStore
|
||||||
const isLoadingMoreStore = provider.isLoadingMoreStore
|
const isLoadingMoreStore = provider.isLoadingMoreStore
|
||||||
|
const isTailLoadedStore = provider.isTailLoaded
|
||||||
const newTimestampStore = provider.newTimestampStore
|
const newTimestampStore = provider.newTimestampStore
|
||||||
const datesStore = provider.datesStore
|
const datesStore = provider.datesStore
|
||||||
const metadataStore = provider.metadataStore
|
const metadataStore = provider.metadataStore
|
||||||
@ -91,7 +93,7 @@
|
|||||||
let displayMessages: DisplayActivityMessage[] = []
|
let displayMessages: DisplayActivityMessage[] = []
|
||||||
let extensions: ActivityExtension[] = []
|
let extensions: ActivityExtension[] = []
|
||||||
|
|
||||||
let scroller: Scroller | undefined = undefined
|
let scroller: Scroller | undefined | null = undefined
|
||||||
let separatorElement: HTMLDivElement | undefined = undefined
|
let separatorElement: HTMLDivElement | undefined = undefined
|
||||||
let scrollContentBox: HTMLDivElement | undefined = undefined
|
let scrollContentBox: HTMLDivElement | undefined = undefined
|
||||||
|
|
||||||
@ -137,7 +139,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
function scrollToBottom (afterScrollFn?: () => void): void {
|
function scrollToBottom (afterScrollFn?: () => void): void {
|
||||||
if (scroller !== undefined && scrollElement !== undefined) {
|
if (scroller != null && scrollElement != null) {
|
||||||
scroller.scrollBy(scrollElement.scrollHeight)
|
scroller.scrollBy(scrollElement.scrollHeight)
|
||||||
updateSelectedDate()
|
updateSelectedDate()
|
||||||
afterScrollFn?.()
|
afterScrollFn?.()
|
||||||
@ -279,7 +281,7 @@
|
|||||||
scrollToRestore = scrollElement?.scrollHeight ?? 0
|
scrollToRestore = scrollElement?.scrollHeight ?? 0
|
||||||
provider.addNextChunk('backward', messages[0]?.createdOn, limit)
|
provider.addNextChunk('backward', messages[0]?.createdOn, limit)
|
||||||
backwardRequested = true
|
backwardRequested = true
|
||||||
} else if (shouldLoadMoreDown()) {
|
} else if (shouldLoadMoreDown() && !$isTailLoadedStore) {
|
||||||
scrollToRestore = 0
|
scrollToRestore = 0
|
||||||
shouldScrollToNew = false
|
shouldScrollToNew = false
|
||||||
isScrollAtBottom = false
|
isScrollAtBottom = false
|
||||||
@ -637,7 +639,7 @@
|
|||||||
function updateDownButtonVisibility (
|
function updateDownButtonVisibility (
|
||||||
metadata: MessageMetadata[],
|
metadata: MessageMetadata[],
|
||||||
displayMessages: DisplayActivityMessage[],
|
displayMessages: DisplayActivityMessage[],
|
||||||
element?: HTMLDivElement
|
element?: HTMLDivElement | null
|
||||||
): void {
|
): void {
|
||||||
if (metadata.length === 0 || displayMessages.length === 0) {
|
if (metadata.length === 0 || displayMessages.length === 0) {
|
||||||
showScrollDownButton = false
|
showScrollDownButton = false
|
||||||
@ -677,6 +679,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: forceReadContext(isScrollAtBottom, notifyContext)
|
||||||
|
|
||||||
|
function forceReadContext (isScrollAtBottom: boolean, context?: DocNotifyContext): void {
|
||||||
|
if (context === undefined || !isScrollAtBottom) return
|
||||||
|
const { lastUpdateTimestamp = 0, lastViewedTimestamp = 0 } = context
|
||||||
|
|
||||||
|
if (lastViewedTimestamp >= lastUpdateTimestamp) return
|
||||||
|
|
||||||
|
const notifications = $notificationsByContextStore.get(context._id) ?? []
|
||||||
|
const unViewed = notifications.filter(({ isViewed }) => !isViewed)
|
||||||
|
|
||||||
|
if (unViewed.length === 0) {
|
||||||
|
void inboxClient.readDoc(client, objectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const canLoadNextForwardStore = provider.canLoadNextForwardStore
|
const canLoadNextForwardStore = provider.canLoadNextForwardStore
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -808,5 +826,17 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
bottom: -0.75rem;
|
bottom: -0.75rem;
|
||||||
|
animation: 1s fadeIn;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
99% {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -63,8 +63,7 @@
|
|||||||
await client.createDoc(notification.class.DocNotifyContext, channelId, {
|
await client.createDoc(notification.class.DocNotifyContext, channelId, {
|
||||||
user: accountId,
|
user: accountId,
|
||||||
attachedTo: channelId,
|
attachedTo: channelId,
|
||||||
attachedToClass: chunter.class.Channel,
|
attachedToClass: chunter.class.Channel
|
||||||
hidden: false
|
|
||||||
})
|
})
|
||||||
|
|
||||||
openChannel(channelId, chunter.class.Channel)
|
openChannel(channelId, chunter.class.Channel)
|
||||||
|
@ -81,7 +81,6 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (context !== undefined) {
|
if (context !== undefined) {
|
||||||
await client.diffUpdate(context, { hidden: false })
|
|
||||||
openChannel(dmId, chunter.class.DirectMessage)
|
openChannel(dmId, chunter.class.DirectMessage)
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -90,8 +89,7 @@
|
|||||||
await client.createDoc(notification.class.DocNotifyContext, dmId, {
|
await client.createDoc(notification.class.DocNotifyContext, dmId, {
|
||||||
user: myAccId,
|
user: myAccId,
|
||||||
attachedTo: dmId,
|
attachedTo: dmId,
|
||||||
attachedToClass: chunter.class.DirectMessage,
|
attachedToClass: chunter.class.DirectMessage
|
||||||
hidden: false
|
|
||||||
})
|
})
|
||||||
|
|
||||||
openChannel(dmId, chunter.class.DirectMessage)
|
openChannel(dmId, chunter.class.DirectMessage)
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
notification.class.DocNotifyContext,
|
notification.class.DocNotifyContext,
|
||||||
{
|
{
|
||||||
...model.query,
|
...model.query,
|
||||||
hidden: false,
|
[`${chunter.mixin.ChannelInfo}.hidden`]: { $ne: true },
|
||||||
user: getCurrentAccount()._id
|
user: getCurrentAccount()._id
|
||||||
},
|
},
|
||||||
(res: DocNotifyContext[]) => {
|
(res: DocNotifyContext[]) => {
|
||||||
|
@ -128,6 +128,7 @@
|
|||||||
{count}
|
{count}
|
||||||
title={item.title}
|
title={item.title}
|
||||||
description={item.description}
|
description={item.description}
|
||||||
|
secondaryNotifyMarker={(context?.lastViewedTimestamp ?? 0) < (context?.lastUpdateTimestamp ?? 0)}
|
||||||
{actions}
|
{actions}
|
||||||
{type}
|
{type}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
export let isSelected: boolean = false
|
export let isSelected: boolean = false
|
||||||
export let isSecondary: boolean = false
|
export let isSecondary: boolean = false
|
||||||
export let count: number | null = null
|
export let count: number | null = null
|
||||||
|
export let secondaryNotifyMarker: boolean = false
|
||||||
export let title: string | undefined = undefined
|
export let title: string | undefined = undefined
|
||||||
export let intlTitle: IntlString | undefined = undefined
|
export let intlTitle: IntlString | undefined = undefined
|
||||||
export let description: string | undefined = undefined
|
export let description: string | undefined = undefined
|
||||||
@ -99,6 +100,10 @@
|
|||||||
<div class="antiHSpacer" />
|
<div class="antiHSpacer" />
|
||||||
<NotifyMarker {count} />
|
<NotifyMarker {count} />
|
||||||
<div class="antiHSpacer" />
|
<div class="antiHSpacer" />
|
||||||
|
{:else if secondaryNotifyMarker}
|
||||||
|
<div class="antiHSpacer" />
|
||||||
|
<NotifyMarker count={0} kind="secondary" size="x-small" />
|
||||||
|
<div class="antiHSpacer" />
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
import notification, { type DocNotifyContext } from '@hcengineering/notification'
|
||||||
import {
|
import {
|
||||||
generateId,
|
generateId,
|
||||||
type Ref,
|
type Ref,
|
||||||
@ -353,8 +353,8 @@ function getActivityActions (contexts: DocNotifyContext[]): Action[] {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: view.icon.CheckCircle,
|
icon: view.icon.EyeCrossed,
|
||||||
label: notification.string.ArchiveAll,
|
label: view.string.Hide,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
archiveActivityChannels(contexts)
|
archiveActivityChannels(contexts)
|
||||||
}
|
}
|
||||||
@ -400,18 +400,18 @@ export function loadSavedAttachments (): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function removeActivityChannels (contexts: DocNotifyContext[]): Promise<void> {
|
export async function removeActivityChannels (contexts: DocNotifyContext[]): Promise<void> {
|
||||||
const client = InboxNotificationsClientImpl.getClient()
|
|
||||||
const notificationsByContext = get(client.inboxNotificationsByContext)
|
|
||||||
const ops = getClient().apply(generateId(), 'removeActivityChannels')
|
const ops = getClient().apply(generateId(), 'removeActivityChannels')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const context of contexts) {
|
for (const context of contexts) {
|
||||||
const notifications = notificationsByContext.get(context._id) ?? []
|
await ops.createMixin(context._id, context._class, context.space, chunter.mixin.ChannelInfo, { hidden: true })
|
||||||
await client.archiveNotifications(
|
}
|
||||||
ops,
|
const hidden = contexts.map(({ _id }) => _id)
|
||||||
notifications.map(({ _id }: InboxNotification) => _id)
|
const account = getCurrentAccount() as PersonAccount
|
||||||
)
|
const chatInfo = await ops.findOne(chunter.class.ChatInfo, { user: account.person })
|
||||||
await ops.remove(context)
|
|
||||||
|
if (chatInfo !== undefined) {
|
||||||
|
await ops.update(chatInfo, { hidden: chatInfo.hidden.concat(hidden) })
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await ops.commit()
|
await ops.commit()
|
||||||
|
@ -35,10 +35,9 @@ import {
|
|||||||
type Timestamp,
|
type Timestamp,
|
||||||
type WithLookup
|
type WithLookup
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
import { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
||||||
import {
|
import {
|
||||||
InboxNotificationsClientImpl,
|
InboxNotificationsClientImpl,
|
||||||
archiveContextNotifications,
|
|
||||||
isActivityNotification,
|
isActivityNotification,
|
||||||
isMentionNotification
|
isMentionNotification
|
||||||
} from '@hcengineering/notification-resources'
|
} from '@hcengineering/notification-resources'
|
||||||
@ -343,7 +342,12 @@ export async function joinChannel (channel: Channel, value: Ref<Account> | Array
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function leaveChannel (channel: Channel, value: Ref<Account> | Array<Ref<Account>>): Promise<void> {
|
export async function leaveChannel (
|
||||||
|
channel: Channel | undefined,
|
||||||
|
value: Ref<Account> | Array<Ref<Account>>
|
||||||
|
): Promise<void> {
|
||||||
|
if (channel === undefined) return
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
@ -351,10 +355,8 @@ export async function leaveChannel (channel: Channel, value: Ref<Account> | Arra
|
|||||||
await client.update(channel, { $pull: { members: { $in: value } } })
|
await client.update(channel, { $pull: { members: { $in: value } } })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const context = await client.findOne(notification.class.DocNotifyContext, { attachedTo: channel._id })
|
|
||||||
|
|
||||||
await client.update(channel, { $pull: { members: value } })
|
await client.update(channel, { $pull: { members: value } })
|
||||||
await removeChannelAction(context, undefined, { object: channel })
|
await resetChunterLocIfEqual(channel._id, channel._class, channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,11 +501,27 @@ export async function removeChannelAction (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
const inboxClient = InboxNotificationsClientImpl.getClient()
|
||||||
|
|
||||||
await archiveContextNotifications(context)
|
if (hierarchy.isDerived(context.attachedToClass, chunter.class.Channel)) {
|
||||||
await client.remove(context)
|
const channel = await client.findOne(chunter.class.Channel, { _id: context.attachedTo as Ref<Channel> })
|
||||||
|
await leaveChannel(channel, getCurrentAccount()._id)
|
||||||
|
} else {
|
||||||
|
const object = await client.findOne(context.attachedToClass, { _id: context.attachedTo })
|
||||||
|
const account = getCurrentAccount() as PersonAccount
|
||||||
|
|
||||||
await resetChunterLocIfEqual(context.attachedTo, context.attachedToClass, props?.object)
|
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 resetChunterLocIfEqual(context.attachedTo, context.attachedToClass, object)
|
||||||
|
}
|
||||||
|
|
||||||
|
void inboxClient.readDoc(client, context.attachedTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isThreadMessage (message: ActivityMessage): message is ThreadMessage {
|
export function isThreadMessage (message: ActivityMessage): message is ThreadMessage {
|
||||||
|
@ -15,11 +15,12 @@
|
|||||||
|
|
||||||
import { ActivityMessage, ActivityMessageViewlet } from '@hcengineering/activity'
|
import { ActivityMessage, ActivityMessageViewlet } from '@hcengineering/activity'
|
||||||
import type { Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@hcengineering/core'
|
import type { Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@hcengineering/core'
|
||||||
import { NotificationType } from '@hcengineering/notification'
|
import { DocNotifyContext, NotificationType } from '@hcengineering/notification'
|
||||||
import type { Asset, Plugin } from '@hcengineering/platform'
|
import type { Asset, Plugin } from '@hcengineering/platform'
|
||||||
import { IntlString, plugin } from '@hcengineering/platform'
|
import { IntlString, plugin } from '@hcengineering/platform'
|
||||||
import { AnyComponent } from '@hcengineering/ui'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
import { Action } from '@hcengineering/view'
|
import { Action } from '@hcengineering/view'
|
||||||
|
import { Person } from '@hcengineering/contact'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -72,6 +73,16 @@ export interface ChatMessageViewlet extends ActivityMessageViewlet {
|
|||||||
label?: IntlString
|
label?: IntlString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ChatInfo extends Doc {
|
||||||
|
user: Ref<Person>
|
||||||
|
hidden: Ref<DocNotifyContext>[]
|
||||||
|
timestamp: Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChannelInfo extends DocNotifyContext {
|
||||||
|
hidden: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -110,10 +121,12 @@ export default plugin(chunterId, {
|
|||||||
Channel: '' as Ref<Class<Channel>>,
|
Channel: '' as Ref<Class<Channel>>,
|
||||||
DirectMessage: '' as Ref<Class<DirectMessage>>,
|
DirectMessage: '' as Ref<Class<DirectMessage>>,
|
||||||
ChatMessage: '' as Ref<Class<ChatMessage>>,
|
ChatMessage: '' as Ref<Class<ChatMessage>>,
|
||||||
ChatMessageViewlet: '' as Ref<Class<ChatMessageViewlet>>
|
ChatMessageViewlet: '' as Ref<Class<ChatMessageViewlet>>,
|
||||||
|
ChatInfo: '' as Ref<Class<ChatInfo>>
|
||||||
},
|
},
|
||||||
mixin: {
|
mixin: {
|
||||||
ObjectChatPanel: '' as Ref<Mixin<ObjectChatPanel>>
|
ObjectChatPanel: '' as Ref<Mixin<ObjectChatPanel>>,
|
||||||
|
ChannelInfo: '' as Ref<Mixin<ChannelInfo>>
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
Reactions: '' as IntlString,
|
Reactions: '' as IntlString,
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
$: notifyContext = $contextByDocStore.get(value._id)
|
$: notifyContext = $contextByDocStore.get(value._id)
|
||||||
$: inboxNotifications = notifyContext ? $inboxNotificationsByContextStore.get(notifyContext._id) ?? [] : []
|
$: inboxNotifications = notifyContext ? $inboxNotificationsByContextStore.get(notifyContext._id) ?? [] : []
|
||||||
|
|
||||||
$: hasNotification = !notifyContext?.hidden && inboxNotifications.some(({ isViewed }) => !isViewed)
|
$: hasNotification = inboxNotifications.some(({ isViewed }) => !isViewed)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if hasNotification}
|
{#if hasNotification}
|
||||||
|
@ -14,13 +14,14 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let count: number = 0
|
export let count: number = 0
|
||||||
export let size: 'small' | 'medium' = 'small'
|
export let kind: 'primary' | 'secondary' = 'primary'
|
||||||
|
export let size: 'x-small' | 'small' | 'medium' = 'small'
|
||||||
|
|
||||||
const maxNumber = 9
|
const maxNumber = 9
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if count > 0}
|
{#if count > 0}
|
||||||
<div class="notifyMarker {size}">
|
<div class="notifyMarker {size} {kind}">
|
||||||
{#if count > maxNumber}
|
{#if count > maxNumber}
|
||||||
{maxNumber}+
|
{maxNumber}+
|
||||||
{:else}
|
{:else}
|
||||||
@ -29,6 +30,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if count === 0 && kind === 'secondary'}
|
||||||
|
<div class="notifyMarker {size} {kind}" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.notifyMarker {
|
.notifyMarker {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -36,10 +41,22 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: var(--global-higlight-Color);
|
|
||||||
color: var(--global-on-accent-TextColor);
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
background-color: var(--global-higlight-Color);
|
||||||
|
color: var(--global-on-accent-TextColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
background-color: var(--global-subtle-BackgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.x-small {
|
||||||
|
width: 0.75rem;
|
||||||
|
height: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
&.small {
|
&.small {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
|
@ -73,7 +73,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
return inboxNotifications.reduce((result, notification) => {
|
return inboxNotifications.reduce((result, notification) => {
|
||||||
const notifyContext = notifyContexts.find(({ _id }) => _id === notification.docNotifyContext)
|
const notifyContext = notifyContexts.find(({ _id }) => _id === notification.docNotifyContext)
|
||||||
|
|
||||||
if (notifyContext === undefined || notifyContext.hidden) {
|
if (notifyContext === undefined) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,13 +207,6 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.createDoc(notification.class.DocNotifyContext, doc.space, {
|
|
||||||
attachedTo: _id,
|
|
||||||
attachedToClass: _class,
|
|
||||||
user: getCurrentAccount()._id,
|
|
||||||
hidden: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async readNotifications (client: TxOperations, ids: Array<Ref<InboxNotification>>): Promise<void> {
|
async readNotifications (client: TxOperations, ids: Array<Ref<InboxNotification>>): Promise<void> {
|
||||||
|
@ -87,16 +87,10 @@ export function loadNotificationSettings (): void {
|
|||||||
loadNotificationSettings()
|
loadNotificationSettings()
|
||||||
|
|
||||||
export async function hasDocNotifyContextPinAction (docNotifyContext: DocNotifyContext): Promise<boolean> {
|
export async function hasDocNotifyContextPinAction (docNotifyContext: DocNotifyContext): Promise<boolean> {
|
||||||
if (docNotifyContext.hidden) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return docNotifyContext.isPinned !== true
|
return docNotifyContext.isPinned !== true
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function hasDocNotifyContextUnpinAction (docNotifyContext: DocNotifyContext): Promise<boolean> {
|
export async function hasDocNotifyContextUnpinAction (docNotifyContext: DocNotifyContext): Promise<boolean> {
|
||||||
if (docNotifyContext.hidden) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return docNotifyContext.isPinned === true
|
return docNotifyContext.isPinned === true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +276,6 @@ export interface DocNotifyContext extends Doc {
|
|||||||
attachedTo: Ref<Doc>
|
attachedTo: Ref<Doc>
|
||||||
attachedToClass: Ref<Class<Doc>>
|
attachedToClass: Ref<Class<Doc>>
|
||||||
|
|
||||||
hidden: boolean
|
|
||||||
isPinned?: boolean
|
isPinned?: boolean
|
||||||
lastViewedTimestamp?: Timestamp
|
lastViewedTimestamp?: Timestamp
|
||||||
lastUpdateTimestamp?: Timestamp
|
lastUpdateTimestamp?: Timestamp
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
): boolean {
|
): boolean {
|
||||||
const context = notifyContextByDoc.get(space._id)
|
const context = notifyContextByDoc.get(space._id)
|
||||||
|
|
||||||
if (context === undefined || context.hidden) {
|
if (context === undefined) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
): boolean {
|
): boolean {
|
||||||
const notifyContext = docUpdates.get(space._id)
|
const notifyContext = docUpdates.get(space._id)
|
||||||
if (notifyContext === undefined) return false
|
if (notifyContext === undefined) return false
|
||||||
return !notifyContext.hidden && !!inboxNotificationsByContext.get(notifyContext._id)?.length
|
return !!inboxNotificationsByContext.get(notifyContext._id)?.length
|
||||||
}
|
}
|
||||||
$: visibleSpace = spaces.find((space) => currentSpace === space._id)
|
$: visibleSpace = spaces.find((space) => currentSpace === space._id)
|
||||||
</script>
|
</script>
|
||||||
|
@ -14,8 +14,15 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import activity, { ActivityMessage, ActivityReference } from '@hcengineering/activity'
|
import activity, { ActivityMessage, ActivityReference } from '@hcengineering/activity'
|
||||||
import chunter, { Channel, ChatMessage, chunterId, ChunterSpace, ThreadMessage } from '@hcengineering/chunter'
|
import chunter, {
|
||||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
Channel,
|
||||||
|
ChannelInfo,
|
||||||
|
ChatMessage,
|
||||||
|
chunterId,
|
||||||
|
ChunterSpace,
|
||||||
|
ThreadMessage
|
||||||
|
} from '@hcengineering/chunter'
|
||||||
|
import contact, { Person, PersonAccount } from '@hcengineering/contact'
|
||||||
import core, {
|
import core, {
|
||||||
Account,
|
Account,
|
||||||
AttachedDoc,
|
AttachedDoc,
|
||||||
@ -27,6 +34,7 @@ import core, {
|
|||||||
FindResult,
|
FindResult,
|
||||||
Hierarchy,
|
Hierarchy,
|
||||||
Ref,
|
Ref,
|
||||||
|
Timestamp,
|
||||||
Tx,
|
Tx,
|
||||||
TxCollectionCUD,
|
TxCollectionCUD,
|
||||||
TxCreateDoc,
|
TxCreateDoc,
|
||||||
@ -34,9 +42,10 @@ import core, {
|
|||||||
TxMixin,
|
TxMixin,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
TxRemoveDoc,
|
TxRemoveDoc,
|
||||||
TxUpdateDoc
|
TxUpdateDoc,
|
||||||
|
UserStatus
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, { Collaborators, NotificationContent } from '@hcengineering/notification'
|
import notification, { Collaborators, DocNotifyContext, NotificationContent } from '@hcengineering/notification'
|
||||||
import { getMetadata, IntlString, translate } from '@hcengineering/platform'
|
import { getMetadata, IntlString, translate } from '@hcengineering/platform'
|
||||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||||
import {
|
import {
|
||||||
@ -50,6 +59,9 @@ import { workbenchId } from '@hcengineering/workbench'
|
|||||||
import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
|
import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
|
||||||
import { encodeObjectURI } from '@hcengineering/view'
|
import { encodeObjectURI } from '@hcengineering/view'
|
||||||
|
|
||||||
|
const updateChatInfoDelay = 12 * 60 * 60 * 1000 // 12 hours
|
||||||
|
const hideChannelDelay = 7 * 24 * 60 * 60 * 1000 // 7 days
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -411,14 +423,12 @@ async function OnChannelMembersChanged (tx: TxUpdateDoc<Channel>, control: Trigg
|
|||||||
attachedTo: tx.objectId,
|
attachedTo: tx.objectId,
|
||||||
attachedToClass: tx.objectClass,
|
attachedToClass: tx.objectClass,
|
||||||
user: addedMember,
|
user: addedMember,
|
||||||
hidden: false,
|
|
||||||
lastViewedTimestamp: tx.modifiedOn
|
lastViewedTimestamp: tx.modifiedOn
|
||||||
})
|
})
|
||||||
|
|
||||||
await control.apply([createTx])
|
await control.apply([createTx])
|
||||||
} else {
|
} else {
|
||||||
const updateTx = control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, {
|
const updateTx = control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, {
|
||||||
hidden: false,
|
|
||||||
lastViewedTimestamp: tx.modifiedOn
|
lastViewedTimestamp: tx.modifiedOn
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -455,13 +465,201 @@ async function OnCollaboratorsChanged (tx: TxMixin<Doc, Collaborators>, control:
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
const minVisibleDirects = 10
|
||||||
|
|
||||||
|
if (visibleDirects.length <= minVisibleDirects) return []
|
||||||
|
const canHide = visibleDirects.length - minVisibleDirects
|
||||||
|
|
||||||
|
let toHide: DocNotifyContext[] = []
|
||||||
|
|
||||||
|
for (const context of directs) {
|
||||||
|
const { lastUpdateTimestamp = 0, lastViewedTimestamp = 0 } = context
|
||||||
|
|
||||||
|
if (lastUpdateTimestamp > lastViewedTimestamp) continue
|
||||||
|
if (date - lastUpdateTimestamp < hideChannelDelay) continue
|
||||||
|
|
||||||
|
toHide.push(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toHide.length > canHide) {
|
||||||
|
toHide = toHide.splice(0, toHide.length - canHide)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await hideOldChannels(toHide, control)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hideOldActivityChannels (
|
||||||
|
contexts: DocNotifyContext[],
|
||||||
|
control: TriggerControl,
|
||||||
|
date: Timestamp
|
||||||
|
): Promise<TxMixin<DocNotifyContext, ChannelInfo>[]> {
|
||||||
|
if (contexts.length === 0) return []
|
||||||
|
|
||||||
|
const { hierarchy } = control
|
||||||
|
const toHide: DocNotifyContext[] = []
|
||||||
|
|
||||||
|
for (const context of contexts) {
|
||||||
|
const { lastUpdateTimestamp = 0, lastViewedTimestamp = 0 } = context
|
||||||
|
|
||||||
|
if (lastUpdateTimestamp > lastViewedTimestamp) continue
|
||||||
|
console.log({ diff: date - lastUpdateTimestamp, delay: hideChannelDelay })
|
||||||
|
if (date - lastUpdateTimestamp < hideChannelDelay) continue
|
||||||
|
|
||||||
|
const params = hierarchy.as(context, chunter.mixin.ChannelInfo)
|
||||||
|
if (params.hidden) continue
|
||||||
|
|
||||||
|
toHide.push(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await hideOldChannels(toHide, control)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateChatInfo (control: TriggerControl, status: UserStatus, date: Timestamp): Promise<void> {
|
||||||
|
const account = await control.modelDb.findOne(contact.class.PersonAccount, { _id: status.user as Ref<PersonAccount> })
|
||||||
|
if (account === undefined) return
|
||||||
|
|
||||||
|
const chatUpdates = await control.queryFind(chunter.class.ChatInfo, {})
|
||||||
|
const update = chatUpdates.find(({ user }) => user === account.person)
|
||||||
|
const shouldUpdate = update === undefined || date - update.timestamp > updateChatInfoDelay
|
||||||
|
|
||||||
|
if (!shouldUpdate) return
|
||||||
|
|
||||||
|
const contexts = await control.findAll(notification.class.DocNotifyContext, {
|
||||||
|
user: account._id,
|
||||||
|
isPinned: { $ne: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (contexts.length === 0) return
|
||||||
|
|
||||||
|
const { hierarchy } = control
|
||||||
|
const res: Tx[] = []
|
||||||
|
|
||||||
|
const directContexts = contexts.filter(({ attachedToClass }) =>
|
||||||
|
hierarchy.isDerived(attachedToClass, chunter.class.DirectMessage)
|
||||||
|
)
|
||||||
|
const activityContexts = contexts.filter(
|
||||||
|
({ attachedToClass }) =>
|
||||||
|
!hierarchy.isDerived(attachedToClass, chunter.class.DirectMessage) &&
|
||||||
|
!hierarchy.isDerived(attachedToClass, chunter.class.Channel) &&
|
||||||
|
!hierarchy.isDerived(attachedToClass, chunter.class.Channel)
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
res.push(...mixinTxes)
|
||||||
|
|
||||||
|
if (update === undefined) {
|
||||||
|
res.push(
|
||||||
|
control.txFactory.createTxCreateDoc(chunter.class.ChatInfo, core.space.Workspace, {
|
||||||
|
user: account.person,
|
||||||
|
hidden,
|
||||||
|
timestamp: date
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
res.push(
|
||||||
|
control.txFactory.createTxUpdateDoc(update._class, update.space, update._id, {
|
||||||
|
hidden: Array.from(new Set(update.hidden.concat(hidden))),
|
||||||
|
timestamp: date
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const txIds = res.map((tx) => tx._id)
|
||||||
|
|
||||||
|
await control.apply(res)
|
||||||
|
|
||||||
|
control.operationContext.derived.targets.docNotifyContext = (it) => {
|
||||||
|
if (txIds.includes(it._id)) {
|
||||||
|
return [account.email]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 chatUpdates = await control.queryFind(chunter.class.ChatInfo, {})
|
||||||
|
for (const update of chatUpdates) {
|
||||||
|
if (update.hidden.includes(tx.objectId)) {
|
||||||
|
return [
|
||||||
|
control.txFactory.createTxMixin(tx.objectId, tx.objectClass, tx.objectSpace, chunter.mixin.ChannelInfo, {
|
||||||
|
hidden: false
|
||||||
|
}),
|
||||||
|
control.txFactory.createTxUpdateDoc(update._class, update.space, update._id, {
|
||||||
|
hidden: update.hidden.filter((id) => id !== tx.objectId)
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export default async () => ({
|
export default async () => ({
|
||||||
trigger: {
|
trigger: {
|
||||||
ChunterTrigger,
|
ChunterTrigger,
|
||||||
OnChatMessageRemoved,
|
OnChatMessageRemoved,
|
||||||
OnChannelMembersChanged,
|
OnChannelMembersChanged,
|
||||||
ChatNotificationsHandler
|
ChatNotificationsHandler,
|
||||||
|
OnUserStatus,
|
||||||
|
OnContextUpdate
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
CommentRemove,
|
CommentRemove,
|
||||||
|
@ -31,7 +31,9 @@ export default plugin(serverChunterId, {
|
|||||||
ChunterTrigger: '' as Resource<TriggerFunc>,
|
ChunterTrigger: '' as Resource<TriggerFunc>,
|
||||||
OnChatMessageRemoved: '' as Resource<TriggerFunc>,
|
OnChatMessageRemoved: '' as Resource<TriggerFunc>,
|
||||||
OnChannelMembersChanged: '' as Resource<TriggerFunc>,
|
OnChannelMembersChanged: '' as Resource<TriggerFunc>,
|
||||||
ChatNotificationsHandler: '' as Resource<TriggerFunc>
|
ChatNotificationsHandler: '' as Resource<TriggerFunc>,
|
||||||
|
OnUserStatus: '' as Resource<TriggerFunc>,
|
||||||
|
OnContextUpdate: '' as Resource<TriggerFunc>
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
CommentRemove: '' as Resource<ObjectDDParticipantFunc>,
|
CommentRemove: '' as Resource<ObjectDDParticipantFunc>,
|
||||||
|
@ -95,8 +95,7 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
|
|||||||
// )
|
// )
|
||||||
res.push(
|
res.push(
|
||||||
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
|
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
|
||||||
lastUpdateTimestamp: tx.modifiedOn,
|
lastUpdateTimestamp: tx.modifiedOn
|
||||||
hidden: false
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -106,7 +105,6 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
|
|||||||
user: tx.modifiedBy,
|
user: tx.modifiedBy,
|
||||||
attachedTo: channel._id,
|
attachedTo: channel._id,
|
||||||
attachedToClass: channel._class,
|
attachedToClass: channel._class,
|
||||||
hidden: false,
|
|
||||||
lastUpdateTimestamp: tx.modifiedOn
|
lastUpdateTimestamp: tx.modifiedOn
|
||||||
// TODO: push inbox notification
|
// TODO: push inbox notification
|
||||||
// txes: [
|
// txes: [
|
||||||
|
@ -39,6 +39,7 @@ import core, {
|
|||||||
MixinUpdate,
|
MixinUpdate,
|
||||||
Ref,
|
Ref,
|
||||||
RefTo,
|
RefTo,
|
||||||
|
SortingOrder,
|
||||||
Space,
|
Space,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
toIdMap,
|
toIdMap,
|
||||||
@ -349,9 +350,6 @@ export async function pushInboxNotifications (
|
|||||||
}
|
}
|
||||||
const context = getDocNotifyContext(contexts, account._id, attachedTo, res)
|
const context = getDocNotifyContext(contexts, account._id, attachedTo, res)
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
||||||
const isHidden = !!context?.hidden
|
|
||||||
|
|
||||||
let docNotifyContextId: Ref<DocNotifyContext>
|
let docNotifyContextId: Ref<DocNotifyContext>
|
||||||
|
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
@ -359,7 +357,6 @@ export async function pushInboxNotifications (
|
|||||||
user: account._id,
|
user: account._id,
|
||||||
attachedTo,
|
attachedTo,
|
||||||
attachedToClass,
|
attachedToClass,
|
||||||
hidden: false,
|
|
||||||
lastUpdateTimestamp: shouldUpdateTimestamp ? modifiedOn : undefined
|
lastUpdateTimestamp: shouldUpdateTimestamp ? modifiedOn : undefined
|
||||||
})
|
})
|
||||||
await control.apply([createContextTx])
|
await control.apply([createContextTx])
|
||||||
@ -372,35 +369,19 @@ export async function pushInboxNotifications (
|
|||||||
}
|
}
|
||||||
docNotifyContextId = createContextTx.objectId
|
docNotifyContextId = createContextTx.objectId
|
||||||
} else {
|
} else {
|
||||||
if (shouldUpdateTimestamp && context.lastUpdateTimestamp !== modifiedOn) {
|
|
||||||
const updateTx = control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, {
|
|
||||||
lastUpdateTimestamp: modifiedOn
|
|
||||||
})
|
|
||||||
await control.apply([updateTx])
|
|
||||||
if (target.account?.email !== undefined) {
|
|
||||||
control.operationContext.derived.targets['docNotifyContext' + updateTx._id] = (it) => {
|
|
||||||
if (it._id === updateTx._id) {
|
|
||||||
return [target.account?.email as string]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
docNotifyContextId = context._id
|
docNotifyContextId = context._id
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isHidden) {
|
const notificationData = {
|
||||||
const notificationData = {
|
user: account._id,
|
||||||
user: account._id,
|
isViewed: false,
|
||||||
isViewed: false,
|
docNotifyContext: docNotifyContextId,
|
||||||
docNotifyContext: docNotifyContextId,
|
...data
|
||||||
...data
|
|
||||||
}
|
|
||||||
const notificationTx = control.txFactory.createTxCreateDoc(_class, space, notificationData)
|
|
||||||
res.push(notificationTx)
|
|
||||||
|
|
||||||
return notificationTx
|
|
||||||
}
|
}
|
||||||
|
const notificationTx = control.txFactory.createTxCreateDoc(_class, space, notificationData)
|
||||||
|
res.push(notificationTx)
|
||||||
|
|
||||||
|
return notificationTx
|
||||||
}
|
}
|
||||||
|
|
||||||
async function activityInboxNotificationToText (
|
async function activityInboxNotificationToText (
|
||||||
@ -762,6 +743,46 @@ export async function getNotificationTxes (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateContextsTimestamp (
|
||||||
|
contexts: DocNotifyContext[],
|
||||||
|
timestamp: Timestamp,
|
||||||
|
control: TriggerControl,
|
||||||
|
modifiedBy: Ref<Account>
|
||||||
|
): Promise<void> {
|
||||||
|
if (contexts.length === 0) return
|
||||||
|
|
||||||
|
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, {
|
||||||
|
_id: { $in: contexts.map((it) => it.user as Ref<PersonAccount>) }
|
||||||
|
})
|
||||||
|
const res: Tx[] = []
|
||||||
|
|
||||||
|
for (const context of contexts) {
|
||||||
|
const account = accounts.find(({ _id }) => _id === context.user)
|
||||||
|
const isViewed =
|
||||||
|
context.lastViewedTimestamp !== undefined && (context.lastUpdateTimestamp ?? 0) <= context.lastViewedTimestamp
|
||||||
|
const updateTx = control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, {
|
||||||
|
lastUpdateTimestamp: timestamp,
|
||||||
|
...(isViewed && modifiedBy === context.user
|
||||||
|
? {
|
||||||
|
lastViewedTimestamp: timestamp
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
})
|
||||||
|
|
||||||
|
res.push(updateTx)
|
||||||
|
|
||||||
|
if (account?.email !== undefined) {
|
||||||
|
control.operationContext.derived.targets['docNotifyContext' + updateTx._id] = (it) => {
|
||||||
|
if (it._id === updateTx._id) {
|
||||||
|
return [account.email]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await control.apply(res)
|
||||||
|
}
|
||||||
|
|
||||||
export async function createCollabDocInfo (
|
export async function createCollabDocInfo (
|
||||||
collaborators: Ref<PersonAccount>[],
|
collaborators: Ref<PersonAccount>[],
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
@ -778,6 +799,10 @@ export async function createCollabDocInfo (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo: object._id })
|
||||||
|
|
||||||
|
await updateContextsTimestamp(notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy)
|
||||||
|
|
||||||
const docMessages = activityMessages.filter((message) => message.attachedTo === object._id)
|
const docMessages = activityMessages.filter((message) => message.attachedTo === object._id)
|
||||||
if (docMessages.length === 0) {
|
if (docMessages.length === 0) {
|
||||||
return res
|
return res
|
||||||
@ -797,10 +822,6 @@ export async function createCollabDocInfo (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, {
|
|
||||||
attachedTo: object._id
|
|
||||||
})
|
|
||||||
|
|
||||||
const usersInfo = await getUsersInfo([...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
|
const usersInfo = await getUsersInfo([...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
|
||||||
const sender = usersInfo.find(({ _id }) => _id === originTx.modifiedBy) ?? {
|
const sender = usersInfo.find(({ _id }) => _id === originTx.modifiedBy) ?? {
|
||||||
_id: originTx.modifiedBy
|
_id: originTx.modifiedBy
|
||||||
@ -1467,12 +1488,65 @@ export async function getCollaborators (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function OnDocRemove (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
async function OnActivityMessageRemove (message: ActivityMessage, control: TriggerControl): Promise<Tx[]> {
|
||||||
const etx = TxProcessor.extractTx(tx)
|
if (control.removedMap.has(message.attachedTo)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
if (etx._class !== core.class.TxRemoveDoc) return []
|
const contexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo: message.attachedTo })
|
||||||
|
if (contexts.length === 0) return []
|
||||||
|
|
||||||
return await removeCollaboratorDoc(etx as TxRemoveDoc<Doc>, control)
|
const isLastUpdate = contexts.some((context) => {
|
||||||
|
const { lastUpdateTimestamp = 0, lastViewedTimestamp = 0 } = context
|
||||||
|
return lastUpdateTimestamp === message.createdOn && lastViewedTimestamp < lastUpdateTimestamp
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!isLastUpdate) return []
|
||||||
|
|
||||||
|
const lastMessage = (
|
||||||
|
await control.findAll(
|
||||||
|
activity.class.ActivityMessage,
|
||||||
|
{ attachedTo: message.attachedTo, space: message.space },
|
||||||
|
{ sort: { createdOn: SortingOrder.Descending }, limit: 1 }
|
||||||
|
)
|
||||||
|
)[0]
|
||||||
|
if (lastMessage === undefined) return []
|
||||||
|
|
||||||
|
const res: Tx[] = []
|
||||||
|
|
||||||
|
for (const context of contexts) {
|
||||||
|
if (context.lastUpdateTimestamp === message.createdOn) {
|
||||||
|
const tx = control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, {
|
||||||
|
lastUpdateTimestamp: lastMessage.createdOn ?? lastMessage.modifiedOn
|
||||||
|
})
|
||||||
|
|
||||||
|
res.push(tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
async function OnDocRemove (originTx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||||
|
const tx = TxProcessor.extractTx(originTx) as TxRemoveDoc<Doc>
|
||||||
|
|
||||||
|
if (tx._class !== core.class.TxRemoveDoc) return []
|
||||||
|
|
||||||
|
const res: Tx[] = []
|
||||||
|
|
||||||
|
if (control.hierarchy.isDerived(tx.objectClass, activity.class.ActivityMessage)) {
|
||||||
|
const message = control.removedMap.get(tx.objectId) as ActivityMessage | undefined
|
||||||
|
|
||||||
|
if (message !== undefined) {
|
||||||
|
const txes = await OnActivityMessageRemove(message, control)
|
||||||
|
res.push(...txes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const txes = await removeCollaboratorDoc(tx, control)
|
||||||
|
|
||||||
|
res.push(...txes)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
|
@ -90,8 +90,7 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
|
|||||||
// )
|
// )
|
||||||
res.push(
|
res.push(
|
||||||
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
|
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
|
||||||
lastUpdateTimestamp: tx.modifiedOn,
|
lastUpdateTimestamp: tx.modifiedOn
|
||||||
hidden: false
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -101,7 +100,6 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
|
|||||||
user: tx.modifiedBy,
|
user: tx.modifiedBy,
|
||||||
attachedTo: channel._id,
|
attachedTo: channel._id,
|
||||||
attachedToClass: channel._class,
|
attachedToClass: channel._class,
|
||||||
hidden: false,
|
|
||||||
lastUpdateTimestamp: tx.modifiedOn
|
lastUpdateTimestamp: tx.modifiedOn
|
||||||
// TODO: push inbox notifications
|
// TODO: push inbox notifications
|
||||||
// txes: [
|
// txes: [
|
||||||
|
@ -14,7 +14,6 @@ export async function createNotification (
|
|||||||
const docNotifyContextId = await client.createDoc(notification.class.DocNotifyContext, forDoc.space, {
|
const docNotifyContextId = await client.createDoc(notification.class.DocNotifyContext, forDoc.space, {
|
||||||
attachedTo: forDoc._id,
|
attachedTo: forDoc._id,
|
||||||
attachedToClass: forDoc._class,
|
attachedToClass: forDoc._class,
|
||||||
hidden: false,
|
|
||||||
user: data.user,
|
user: data.user,
|
||||||
isPinned: false
|
isPinned: false
|
||||||
})
|
})
|
||||||
@ -29,9 +28,6 @@ export async function createNotification (
|
|||||||
props: data.props
|
props: data.props
|
||||||
})
|
})
|
||||||
if (existing !== undefined) {
|
if (existing !== undefined) {
|
||||||
await client.update(docNotifyContext as DocNotifyContext, {
|
|
||||||
lastUpdateTimestamp: Date.now()
|
|
||||||
})
|
|
||||||
await client.update(existing, {
|
await client.update(existing, {
|
||||||
isViewed: false
|
isViewed: false
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user