TSK-1341 Remove last view (#3115)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-04-29 21:46:13 +06:00 committed by GitHub
parent 9b04d366e3
commit ae8fcf8d62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 204 additions and 645 deletions

View File

@ -184,10 +184,6 @@ export function createModel (builder: Builder, options = { addApplication: true
}
})
builder.mixin(spaceClass, core.class.Class, notification.mixin.SpaceLastEdit, {
lastEditField: 'lastMessage'
})
builder.mixin(spaceClass, core.class.Class, view.mixin.ObjectEditor, {
editor: chunter.component.EditChannel
})
@ -197,12 +193,10 @@ export function createModel (builder: Builder, options = { addApplication: true
getName: chunter.function.GetDmName
})
builder.mixin(chunter.class.Message, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(chunter.class.Message, core.class.Class, notification.mixin.ClassCollaborators, {
fields: ['createdBy', 'replies']
})
builder.mixin(chunter.class.ChunterSpace, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(chunter.class.DirectMessage, core.class.Class, notification.mixin.ClassCollaborators, {
fields: ['members']
})

View File

@ -324,10 +324,6 @@ export function createModel (builder: Builder): void {
inlineEditor: contact.component.ContactArrayEditor
})
builder.mixin(contact.class.Contact, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(contact.class.Channel, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(contact.class.Contact, core.class.Class, notification.mixin.ClassCollaborators, {
fields: []
})

View File

@ -45,7 +45,6 @@ import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
import presentation from '@hcengineering/model-presentation'
import view, { actionTemplates, createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification'
import tags from '@hcengineering/tags'
import document from './plugin'
@ -187,8 +186,6 @@ export function createModel (builder: Builder): void {
component: document.component.CreateDocument
})
builder.mixin(document.class.Document, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(document.class.Document, core.class.Class, view.mixin.ObjectPanel, {
component: document.component.EditDoc
})

View File

@ -20,7 +20,6 @@ import attachment from '@hcengineering/model-attachment'
import core, { TAttachedDoc } from '@hcengineering/model-core'
import { createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification'
import setting from '@hcengineering/setting'
import view, { Viewlet } from '@hcengineering/view'
import inventory from './plugin'
@ -160,8 +159,6 @@ export function createModel (builder: Builder): void {
inventory.category.Inventory
)
builder.mixin(inventory.class.Product, core.class.Class, notification.mixin.TrackedDoc, {})
createAction(builder, {
label: inventory.string.CreateSubcategory,
icon: inventory.icon.Categories,

View File

@ -37,7 +37,6 @@ import view, { createAction } from '@hcengineering/model-view'
import {
DocUpdates,
EmailNotification,
LastView,
Notification,
NotificationGroup,
notificationId,
@ -46,8 +45,7 @@ import {
NotificationSetting,
NotificationStatus,
NotificationTemplate,
NotificationType,
SpaceLastEdit
NotificationType
} from '@hcengineering/notification'
import type { Asset, IntlString } from '@hcengineering/platform'
import setting from '@hcengineering/setting'
@ -61,13 +59,6 @@ export { notification as default }
export const DOMAIN_NOTIFICATION = 'notification' as Domain
@Model(notification.class.LastView, core.class.Doc, DOMAIN_NOTIFICATION)
export class TLastView extends TDoc implements LastView {
@Prop(TypeRef(core.class.Account), core.string.ModifiedBy)
@Index(IndexKind.Indexed)
user!: Ref<Account>
}
@Model(notification.class.Notification, core.class.AttachedDoc, DOMAIN_NOTIFICATION)
export class TNotification extends TAttachedDoc implements Notification {
@Prop(TypeRef(core.class.Tx), 'TX' as IntlString)
@ -137,19 +128,11 @@ export class TNotificationSetting extends TPreference implements NotificationSet
enabled!: boolean
}
@Mixin(notification.mixin.SpaceLastEdit, core.class.Class)
export class TSpaceLastEdit extends TClass implements SpaceLastEdit {
lastEditField!: string
}
@Mixin(notification.mixin.ClassCollaborators, core.class.Class)
export class TClassCollaborators extends TClass {
fields!: string[]
}
@Mixin(notification.mixin.TrackedDoc, core.class.Class)
export class TTrackedDoc extends TClass {}
@Mixin(notification.mixin.Collaborators, core.class.Doc)
@UX(notification.string.Collaborators)
export class TCollaborators extends TDoc {
@ -175,23 +158,20 @@ export class TDocUpdates extends TDoc implements DocUpdates {
hidden!: boolean
attachedToClass!: Ref<Class<Doc>>
lastTx!: Ref<TxCUD<Doc>>
lastTxTime!: Timestamp
lastTx?: Ref<TxCUD<Doc>>
lastTxTime?: Timestamp
txes!: [Ref<TxCUD<Doc>>, Timestamp][]
}
export function createModel (builder: Builder): void {
builder.createModel(
TLastView,
TNotification,
TEmaiNotification,
TNotificationType,
TNotificationProvider,
TNotificationSetting,
TNotificationGroup,
TSpaceLastEdit,
TClassCollaborators,
TTrackedDoc,
TCollaborators,
TDocUpdates,
TNotificationObjectPresenter

View File

@ -13,22 +13,9 @@
// limitations under the License.
//
import core, {
Account,
AttachedDoc,
Class,
Collection,
Data,
Doc,
DOMAIN_TX,
generateId,
Ref,
TxCollectionCUD,
TxOperations,
TxRemoveDoc
} from '@hcengineering/core'
import core, { AttachedDoc, Class, Collection, Data, Doc, DOMAIN_TX, Ref, TxOperations } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import notification, { LastView, NotificationType } from '@hcengineering/notification'
import notification, { DocUpdates, NotificationType } from '@hcengineering/notification'
import { DOMAIN_NOTIFICATION } from '.'
async function fillNotificationText (client: MigrationClient): Promise<void> {
@ -80,79 +67,6 @@ async function createSpace (client: MigrationUpgradeClient): Promise<void> {
}
}
async function migrateLastView (client: MigrationClient): Promise<void> {
// lets clear last view txes (it should be derived and shouldn't store in tx collection)
const txes = await client.find(DOMAIN_TX, {
objectClass: notification.class.LastView
})
for (const tx of txes) {
await client.delete(DOMAIN_TX, tx._id)
}
const h = client.hierarchy
const docClasses = h.getDescendants(core.class.Doc)
const trackedClasses = docClasses.filter((p) => h.hasMixin(h.getClass(p), notification.mixin.TrackedDoc))
const allowedClasses = new Set<Ref<Class<Doc>>>()
trackedClasses.forEach((p) => h.getDescendants(p).forEach((a) => allowedClasses.add(a)))
const removeTxes = await client.find<TxRemoveDoc<Doc>>(
DOMAIN_TX,
{
_class: core.class.TxRemoveDoc
},
{ projection: { objectId: 1 } }
)
const removedDocs: Set<Ref<Doc>> = new Set(removeTxes.map((p) => p.objectId))
const removedCollectionTxes = await client.find<TxCollectionCUD<Doc, AttachedDoc>>(
DOMAIN_TX,
{
_class: core.class.TxCollectionCUD,
'tx._class': core.class.TxRemoveDoc
},
{ projection: { tx: 1 } }
)
removedCollectionTxes.forEach((p) => p.tx.objectId)
const newLastView: Map<Ref<Account>, LastView> = new Map()
let total = 0
while (true) {
const lastViews = await client.find<LastView>(
DOMAIN_NOTIFICATION,
{
_class: notification.class.LastView,
attachedTo: { $exists: true }
},
{ limit: 10000 }
)
total += lastViews.length
console.log(`migrate ${total} notifications`)
if (lastViews.length === 0) break
for (const lastView of lastViews) {
if (
lastView.user !== core.account.System &&
allowedClasses.has(lastView.attachedToClass) &&
!removedDocs.has(lastView.attachedTo)
) {
const obj: LastView = newLastView.get(lastView.user) ?? {
user: lastView.user,
modifiedBy: lastView.user,
modifiedOn: Date.now(),
_id: generateId(),
space: notification.space.Notifications,
_class: notification.class.LastView
}
obj[lastView.attachedTo] = lastView.lastView
newLastView.set(lastView.user, obj)
}
}
await Promise.all(lastViews.map((p) => client.delete(DOMAIN_NOTIFICATION, p._id)))
}
for (const [, lastView] of newLastView) {
await client.create(DOMAIN_NOTIFICATION, lastView)
}
}
async function fillCollaborators (client: MigrationClient): Promise<void> {
const targetClasses = await client.model.findAll(notification.mixin.ClassCollaborators, {})
for (const targetClass of targetClasses) {
@ -227,6 +141,28 @@ async function createCustomFieldTypes (client: MigrationUpgradeClient): Promise<
}
}
async function changeDocUpdatesSpaces (client: MigrationUpgradeClient): Promise<void> {
const txop = new TxOperations(client, core.account.System)
const docUpdates = await client.findAll(notification.class.DocUpdates, { space: notification.space.Notifications })
const map = new Map<Ref<Class<Doc>>, Map<Ref<Doc>, DocUpdates[]>>()
for (const docUpdate of docUpdates) {
const _class = map.get(docUpdate.attachedToClass) ?? new Map()
const arr = _class.get(docUpdate.attachedTo) ?? []
arr.push(docUpdate)
_class.set(docUpdate.attachedTo, arr)
map.set(docUpdate.attachedToClass, _class)
}
for (const [_class, arr] of map) {
const ids = Array.from(arr.keys())
const docs = await client.findAll(_class, { _id: { $in: ids } })
for (const doc of docs) {
const updateDocs = arr.get(doc._id)
if (updateDocs === undefined) continue
await Promise.all(updateDocs.map(async (p) => await txop.update(p, { space: doc.space })))
}
}
}
async function cleanOutdatedSettings (client: MigrationClient): Promise<void> {
const res = await client.find(DOMAIN_NOTIFICATION, {
_class: notification.class.NotificationSetting
@ -240,7 +176,6 @@ export const notificationOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await removeSettings(client)
await fillNotificationText(client)
await migrateLastView(client)
await fillCollaborators(client)
await fillDocUpdatesHidder(client)
await cleanOutdatedSettings(client)
@ -248,5 +183,6 @@ export const notificationOperation: MigrateOperation = {
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await createSpace(client)
await createCustomFieldTypes(client)
await changeDocUpdatesSpaces(client)
}
}

View File

@ -23,7 +23,6 @@ import { Application } from '@hcengineering/workbench'
export default mergeIds(notificationId, notification, {
string: {
LastView: '' as IntlString,
PlatformNotification: '' as IntlString,
BrowserNotification: '' as IntlString,
EmailNotification: '' as IntlString,

View File

@ -679,10 +679,6 @@ export function createModel (builder: Builder): void {
recruit.viewlet.ApplicantDashboard
)
builder.mixin(recruit.class.Applicant, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(recruit.class.Vacancy, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(recruit.class.Applicant, core.class.Class, task.mixin.KanbanCard, {
card: recruit.component.KanbanCard
})

View File

@ -55,22 +55,10 @@ export function createModel (builder: Builder): void {
trigger: serverNotification.trigger.OnBacklinkCreate
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.UpdateLastView
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.CollaboratorDocHandler
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnUpdateLastView
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnAddCollborator
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnAttributeCreate
})

View File

@ -34,7 +34,7 @@ import {
} from '@hcengineering/model'
import core, { TAttachedDoc, TClass, TDoc, TSpace, TStatus } from '@hcengineering/model-core'
import view, { createAction, template, actionTemplates as viewTemplates } from '@hcengineering/model-view'
import notification from '@hcengineering/notification'
import {} from '@hcengineering/notification'
import { IntlString } from '@hcengineering/platform'
import tags from '@hcengineering/tags'
import {
@ -321,8 +321,6 @@ export function createModel (builder: Builder): void {
editor: task.component.TaskHeader
})
builder.mixin(task.class.Task, core.class.Class, notification.mixin.TrackedDoc, {})
builder.createDoc(
view.class.ActionCategory,
core.space.Model,

View File

@ -954,8 +954,6 @@ export function createModel (builder: Builder): void {
inlineEditor: tracker.component.ComponentStatusEditor
})
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AllValuesFunc, {
func: tracker.function.GetAllPriority
})

View File

@ -22,9 +22,8 @@
export let size: IconSize = 'small'
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
$: lastView = $lastViews[object._id]
$: subscribed = lastView !== undefined && lastView !== -1
const store = notificationClient.docUpdatesStore
$: subscribed = $store.get(object._id) !== undefined
</script>
{#if subscribed}

View File

@ -15,8 +15,8 @@
<script lang="ts">
import attachment, { Attachment } from '@hcengineering/attachment'
import type { ChunterMessage, Message } from '@hcengineering/chunter'
import core, { Ref, Space, Timestamp, WithLookup } from '@hcengineering/core'
import { LastView } from '@hcengineering/notification'
import core, { Doc, Ref, Space, Timestamp, WithLookup } from '@hcengineering/core'
import { DocUpdates } from '@hcengineering/notification'
import { NotificationClientImpl } from '@hcengineering/notification-resources'
import { createQuery } from '@hcengineering/presentation'
import { location as locationStore } from '@hcengineering/ui'
@ -69,11 +69,11 @@
}
})
let messages: WithLookup<Message>[] | undefined
let messages: WithLookup<Message>[] = []
const query = createQuery()
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
const docUpdates = notificationClient.docUpdatesStore
$: updateQuery(space)
@ -90,8 +90,8 @@
},
(res) => {
messages = res
newMessagesPos = newMessagesStart(messages)
notificationClient.updateLastView(space, chunter.class.ChunterSpace)
newMessagesPos = newMessagesStart(messages, $docUpdates)
notificationClient.read(space)
},
{
lookup: {
@ -102,22 +102,22 @@
)
}
function newMessagesStart (messages: Message[]): number {
function newMessagesStart (messages: Message[], docUpdates: Map<Ref<Doc>, DocUpdates>): number {
if (space === undefined) return -1
const lastView = ($lastViews as any)[space]
if (lastView === undefined || lastView === -1) return -1
const docUpdate = docUpdates.get(space)
const lastView = docUpdate?.txes?.[0]?.[1]
if (docUpdate === undefined || lastView === undefined) return -1
for (let index = 0; index < messages.length; index++) {
const message = messages[index]
if (message.createOn > lastView) return index
if (message.createOn >= lastView) return index
}
return -1
}
$: markUnread($lastViews)
function markUnread (lastViews: LastView) {
if (messages === undefined) return
const newPos = newMessagesStart(messages)
if (newPos !== -1 || newMessagesPos === -1) {
$: markUnread(messages, $docUpdates)
function markUnread (messages: Message[], docUpdates: Map<Ref<Doc>, DocUpdates>) {
const newPos = newMessagesStart(messages, docUpdates)
if (newPos !== -1) {
newMessagesPos = newPos
}
}
@ -165,7 +165,7 @@
}
let showFixed: boolean | undefined
let selectedDate: Timestamp | undefined = messages ? getDay(messages[0].createOn) : undefined
let selectedDate: Timestamp | undefined = undefined
function handleScroll () {
const upperVisible = getFirstVisible()
if (upperVisible) {

View File

@ -18,15 +18,7 @@
import type { ChunterSpace, Message, ThreadMessage } from '@hcengineering/chunter'
import contact, { Employee, EmployeeAccount, getName } from '@hcengineering/contact'
import { employeeByIdStore } from '@hcengineering/contact-resources'
import core, {
FindOptions,
generateId,
getCurrentAccount,
IdMap,
Ref,
SortingOrder,
TxFactory
} from '@hcengineering/core'
import core, { FindOptions, IdMap, Ref, SortingOrder, generateId, getCurrentAccount } from '@hcengineering/core'
import { NotificationClientImpl } from '@hcengineering/notification-resources'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
@ -97,7 +89,7 @@
} else {
comments = res.reverse()
}
notificationClient.updateLastView(id, chunter.class.Message)
notificationClient.read(id)
},
options
)
@ -130,8 +122,7 @@
if (parent === undefined) return
const { message, attachments } = event.detail
const me = getCurrentAccount()._id
const txFactory = new TxFactory(me)
const tx = txFactory.createTxCreateDoc<ThreadMessage>(
await client.createDoc(
chunter.class.ThreadMessage,
parent.space,
{
@ -145,9 +136,6 @@
},
commentId
)
tx.attributes.createOn = tx.modifiedOn
await notificationClient.updateLastView(_id, chunter.class.Message, tx.modifiedOn, true)
await client.tx(tx)
// Create an backlink to document
await createBacklinks(client, parent._id, parent._class, commentId, message)

View File

@ -16,8 +16,8 @@
import attachment, { Attachment } from '@hcengineering/attachment'
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
import type { ChunterMessage, Message, ThreadMessage } from '@hcengineering/chunter'
import core, { Ref, Space, generateId, getCurrentAccount } from '@hcengineering/core'
import { LastView } from '@hcengineering/notification'
import core, { Doc, Ref, Space, generateId, getCurrentAccount } from '@hcengineering/core'
import { DocUpdates } from '@hcengineering/notification'
import { NotificationClientImpl } from '@hcengineering/notification-resources'
import { createQuery, getClient } from '@hcengineering/presentation'
import { IconClose, Label, getCurrentResolvedLocation, navigate } from '@hcengineering/ui'
@ -59,7 +59,7 @@
})
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
const docUpdates = notificationClient.docUpdatesStore
const lookup = {
_id: { attachments: attachment.class.Attachment, reactions: chunter.class.Reaction },
@ -101,8 +101,8 @@
},
(res) => {
comments = res
newMessagesPos = newMessagesStart(comments, $lastViews)
notificationClient.updateLastView(id, chunter.class.Message)
newMessagesPos = newMessagesStart(comments, $docUpdates)
notificationClient.read(id)
},
{
lookup
@ -159,23 +159,25 @@
}
let comments: ThreadMessage[] = []
function newMessagesStart (comments: ThreadMessage[], lastViews: LastView): number {
const lastView = (lastViews as any)[_id]
if (lastView === undefined || lastView === -1) return -1
function newMessagesStart (comments: ThreadMessage[], docUpdates: Map<Ref<Doc>, DocUpdates>): number {
const docUpdate = docUpdates.get(_id)
const lastView = docUpdate?.txes?.[0]?.[1]
if (docUpdate === undefined || lastView === undefined) return -1
for (let index = 0; index < comments.length; index++) {
const comment = comments[index]
if (comment.createOn > lastView) return index
if (comment.createOn >= lastView) return index
}
return -1
}
$: markUnread($lastViews)
function markUnread (lastViews: LastView) {
const newPos = newMessagesStart(comments, lastViews)
if (newPos !== -1 || newMessagesPos === -1) {
$: markUnread(comments, $docUpdates)
function markUnread (comments: ThreadMessage[], docUpdates: Map<Ref<Doc>, DocUpdates>) {
const newPos = newMessagesStart(comments, docUpdates)
if (newPos !== -1) {
newMessagesPos = newPos
}
}
let newMessagesPos: number = -1
let loading = false
</script>

View File

@ -67,12 +67,12 @@ export { CommentPopup, CommentsPresenter }
async function MarkUnread (object: Message): Promise<void> {
const client = NotificationClientImpl.getClient()
await client.updateLastView(object.space, chunter.class.ChunterSpace, object.createOn - 1, true)
await client.forceRead(object.space, chunter.class.ChunterSpace)
}
async function MarkCommentUnread (object: ThreadMessage): Promise<void> {
const client = NotificationClientImpl.getClient()
await client.updateLastView(object.attachedTo, object.attachedToClass, object.createOn - 1, true)
await client.forceRead(object.attachedTo, object.attachedToClass)
}
async function SubscribeMessage (object: Message): Promise<void> {
@ -117,14 +117,20 @@ async function UnsubscribeMessage (object: ChunterMessage): Promise<void> {
}
}
)
await notificationClient.unsubscribe(object.attachedTo)
const docUpdate = notificationClient.docUpdatesMap.get(object.attachedTo)
if (docUpdate !== undefined) {
await client.remove(docUpdate)
}
} else {
await client.updateMixin(object._id, object._class, object.space, notification.mixin.Collaborators, {
$pull: {
collaborators: acc._id
}
})
await notificationClient.unsubscribe(object._id)
const docUpdate = notificationClient.docUpdatesMap.get(object._id)
if (docUpdate !== undefined) {
await client.remove(docUpdate)
}
}
}

View File

@ -17,7 +17,7 @@
import type { Channel, ChannelProvider } from '@hcengineering/contact'
import contact from '@hcengineering/contact'
import type { AttachedData, Doc, Ref } from '@hcengineering/core'
import notification, { LastView } from '@hcengineering/notification'
import notification, { DocUpdates } from '@hcengineering/notification'
import { Asset, IntlString, getResource } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import {
@ -50,8 +50,8 @@
export let focusIndex = -1
export let restricted: Ref<ChannelProvider>[] = []
let lastViews: Writable<LastView | undefined> = writable()
getResource(notification.function.GetNotificationClient).then((res) => (lastViews = res().getLastViews()))
let docUpdates: Writable<Map<Ref<Doc>, DocUpdates>> = writable(new Map())
getResource(notification.function.GetNotificationClient).then((res) => (docUpdates = res().docUpdatesStore))
const dispatch = createEventDispatcher()
interface Item {
@ -70,11 +70,11 @@
function getProvider (
item: AttachedData<Channel>,
map: Map<Ref<ChannelProvider>, ChannelProvider>,
lastViews: LastView | undefined
docUpdates: Map<Ref<Doc>, DocUpdates>
): Item | undefined {
const provider = map.get(item.provider)
if (provider) {
const notification = (item as Channel)._id !== undefined ? isNew(item as Channel, lastViews) : false
const notification = (item as Channel)._id !== undefined ? isNew(item as Channel, docUpdates) : false
return {
label: provider.label,
icon: provider.icon as Asset,
@ -92,16 +92,14 @@
}
}
function isNew (item: Channel, lastViews: LastView | undefined): boolean {
if (item.lastMessage === undefined) return false
const lastView =
(item as Channel)._id !== undefined && lastViews !== undefined ? lastViews[(item as Channel)._id] : undefined
return lastView ? lastView < item.lastMessage : (item.items ?? 0) > 0
function isNew (item: Channel, docUpdates: Map<Ref<Doc>, DocUpdates>): boolean {
const docUpdate = docUpdates.get(item._id)
return docUpdate ? docUpdate.txes.length > 0 : (item.items ?? 0) > 0
}
async function update (
value: AttachedData<Channel>[] | Channel | null,
lastViews: LastView | undefined,
docUpdates: Map<Ref<Doc>, DocUpdates>,
channelProviders: ChannelProvider[]
) {
if (value == null) {
@ -112,13 +110,13 @@
const map = getChannelProviders(channelProviders)
if (Array.isArray(value)) {
for (const item of value) {
const provider = getProvider(item, map, lastViews)
const provider = getProvider(item, map, docUpdates)
if (provider !== undefined) {
result.push(provider)
}
}
} else {
const provider = getProvider(value, map, lastViews)
const provider = getProvider(value, map, docUpdates)
if (provider !== undefined) {
result.push(provider)
}
@ -127,7 +125,7 @@
updateMenu(displayItems, channelProviders)
}
$: if (value) update(value, $lastViews, $channelProviders)
$: if (value) update(value, $docUpdates, $channelProviders)
let displayItems: Item[] = []
let actions: Action[] = []
@ -148,7 +146,7 @@
icon: pr.icon ?? contact.icon.SocialEdit,
label: pr.label,
action: async () => {
const provider = getProvider({ provider: pr._id, value: '' }, getChannelProviders(providers), $lastViews)
const provider = getProvider({ provider: pr._id, value: '' }, getChannelProviders(providers), $docUpdates)
if (provider !== undefined) {
if (_displayItems.filter((it) => it.provider === pr._id).length === 0) {
displayItems = [..._displayItems, provider]

View File

@ -16,8 +16,8 @@
<script lang="ts">
import type { Channel, ChannelProvider } from '@hcengineering/contact'
import type { AttachedData, Doc, Ref } from '@hcengineering/core'
import notification, { LastView } from '@hcengineering/notification'
import { Asset, getResource, IntlString } from '@hcengineering/platform'
import notification, { DocUpdates } from '@hcengineering/notification'
import { Asset, IntlString, getResource } from '@hcengineering/platform'
import type { AnyComponent } from '@hcengineering/ui'
import { Button } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
@ -31,8 +31,8 @@
export let reverse: boolean = false
export let integrations: Set<Ref<Doc>> = new Set<Ref<Doc>>()
let lastViews: Writable<LastView | undefined> = writable()
getResource(notification.function.GetNotificationClient).then((res) => (lastViews = res().getLastViews()))
let docUpdates: Writable<Map<Ref<Doc>, DocUpdates>> = writable(new Map())
getResource(notification.function.GetNotificationClient).then((res) => (docUpdates = res().docUpdatesStore))
interface Item {
label: IntlString
@ -48,11 +48,11 @@
function getProvider (
item: AttachedData<Channel>,
map: Map<Ref<ChannelProvider>, ChannelProvider>,
lastViews: LastView | undefined
docUpdates: Map<Ref<Doc>, DocUpdates>
): any | undefined {
const provider = map.get(item.provider)
if (provider) {
const notification = (item as Channel)._id !== undefined ? isNew(item as Channel, lastViews) : false
const notification = (item as Channel)._id !== undefined ? isNew(item as Channel, docUpdates) : false
return {
label: provider.label,
icon: provider.icon as Asset,
@ -66,16 +66,14 @@
}
}
function isNew (item: Channel, lastViews: LastView | undefined): boolean {
if (item.lastMessage === undefined) return false
const lastView =
(item as Channel)._id !== undefined && lastViews !== undefined ? lastViews[(item as Channel)._id] : undefined
return lastView ? lastView < item.lastMessage : (item.items ?? 0) > 0
function isNew (item: Channel, docUpdates: Map<Ref<Doc>, DocUpdates>): boolean {
const docUpdate = docUpdates.get(item._id)
return docUpdate ? docUpdate.txes.length > 0 : (item.items ?? 0) > 0
}
async function update (
value: AttachedData<Channel>[] | Channel | null,
lastViews: LastView | undefined,
docUpdates: Map<Ref<Doc>, DocUpdates>,
channels: ChannelProvider[]
) {
if (value === null) {
@ -86,13 +84,13 @@
const map = getChannelProviders(channels)
if (Array.isArray(value)) {
for (const item of value) {
const provider = getProvider(item, map, lastViews)
const provider = getProvider(item, map, docUpdates)
if (provider !== undefined) {
result.push(provider)
}
}
} else {
const provider = getProvider(value, map, lastViews)
const provider = getProvider(value, map, docUpdates)
if (provider !== undefined) {
result.push(provider)
}
@ -100,7 +98,7 @@
displayItems = result
}
$: if (value) update(value, $lastViews, $channelProviders)
$: if (value) update(value, $docUpdates, $channelProviders)
let displayItems: Item[] = []
let divHTML: HTMLElement

View File

@ -60,7 +60,6 @@
export let embedded = false
let lastId: Ref<Doc> = _id
let lastClass: Ref<Class<Doc>> = _class
const query = createQuery()
@ -79,17 +78,15 @@
function read (_id: Ref<Doc>) {
if (lastId !== _id) {
const prev = lastId
const prevClass = lastClass
lastId = _id
lastClass = _class
notificationClient.then((client) => client.updateLastView(prev, prevClass))
notificationClient.then((client) => client.read(prev))
}
}
const currentUser = getCurrentAccount() as EmployeeAccount
onDestroy(async () => {
notificationClient.then((client) => client.updateLastView(_id, _class))
notificationClient.then((client) => client.read(_id))
})
let requests: DocumentRequest[] = []

View File

@ -46,7 +46,7 @@
{ attachedTo: channelId },
(res) => {
messages = res
notificationClient.updateLastView(channelId, channel._class, undefined, true)
notificationClient.read(channelId)
},
{ sort: { sendOn: SortingOrder.Descending } }
)
@ -68,7 +68,7 @@
messages: convertMessages(object, channel, selectedMessages, $employeeAccountByIdStore, $employeeByIdStore)
}
)
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
await notificationClient.read(channel._id)
clear()
}

View File

@ -78,7 +78,7 @@
async function selectHandler (e: CustomEvent): Promise<void> {
currentMessage = e.detail
if (channel !== undefined) {
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
await notificationClient.read(channel._id)
}
}

View File

@ -73,7 +73,7 @@
},
objectId
)
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
await notificationClient.read(channel._id)
objectId = generateId()
dispatch('close')
}

View File

@ -92,7 +92,7 @@
.map((m) => m.trim())
.filter((m) => m.length)
})
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
await notificationClient.read(channel._id)
for (const attachment of attachments) {
await client.addCollection(
attachmentP.class.Attachment,

View File

@ -1,6 +1,5 @@
{
"string": {
"LastView": "Last View",
"Notification": "Notification",
"Notifications": "Notifications",
"NoNotifications": "No notifications",

View File

@ -1,6 +1,5 @@
{
"string": {
"LastView": "Последний просмотр",
"Notification": "Увдомление",
"Notifications": "Уведомления",
"NoNotifications": "Нет уведомлений",

View File

@ -13,8 +13,8 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { EmployeeAccount } from '@hcengineering/contact'
import { Doc, getCurrentAccount, Ref } from '@hcengineering/core'
import { EmployeeAccount } from '@hcengineering/contact'
import { getCurrentAccount, Ref } from '@hcengineering/core'
import {
NotificationProvider,
NotificationSetting,
@ -26,14 +26,10 @@
import { getCurrentLocation, showPanel } from '@hcengineering/ui'
import view from '@hcengineering/view'
import notification from '../plugin'
import { NotificationClientImpl } from '../utils'
const query = createQuery()
const settingQuery = createQuery()
const providersQuery = createQuery()
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
const lastViewId: Ref<Doc> = ((getCurrentAccount() as EmployeeAccount).employee + 'notification') as Ref<Doc>
let settingsReceived = false
let settings: Map<Ref<NotificationType>, NotificationSetting> = new Map<Ref<NotificationType>, NotificationSetting>()
@ -129,16 +125,6 @@
alreadyShown.clear()
}, 5000)
const lastView = ($lastViews as any)[lastViewId]
if ((lastView ?? notifyInstance.modifiedOn) > 0) {
await notificationClient.updateLastView(
lastViewId,
contact.class.Employee,
notifyInstance.modifiedOn,
lastView === undefined
)
}
// eslint-disable-next-line
const notification = new Notification(getCurrentLocation().path[1], {
tag: notifyInstance._id,

View File

@ -24,7 +24,7 @@
const store = notificationClient.docUpdatesStore
$: docUpdate = $store.get(value._id)
$: hasNotification = (docUpdate?.txes?.length ?? 0) > 0
$: hasNotification = (docUpdate?.txes?.length ?? 0) > 0 && docUpdate?.hidden !== true
</script>
{#if hasNotification}

View File

@ -14,44 +14,35 @@
// limitations under the License.
//
import core, { Account, Class, Doc, getCurrentAccount, Ref, Timestamp } from '@hcengineering/core'
import notification, { DocUpdates, LastView, NotificationClient } from '@hcengineering/notification'
import { Account, Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
import notification, { Collaborators, DocUpdates, NotificationClient } from '@hcengineering/notification'
import { createQuery, getClient } from '@hcengineering/presentation'
import { get, writable, Writable } from 'svelte/store'
import { writable } from 'svelte/store'
/**
* @public
*/
export class NotificationClientImpl implements NotificationClient {
protected static _instance: NotificationClientImpl | undefined = undefined
private readonly lastViewsStore = writable<LastView>()
readonly docUpdatesStore = writable<Map<Ref<Doc>, DocUpdates>>(new Map())
docUpdatesMap: Map<Ref<Doc>, DocUpdates> = new Map()
readonly docUpdates = writable<DocUpdates[]>([])
private readonly docUpdatesQuery = createQuery(true)
private readonly lastViewQuery = createQuery()
private readonly user: Ref<Account>
private constructor () {
this.user = getCurrentAccount()._id
this.lastViewQuery.query(notification.class.LastView, { user: this.user }, (result) => {
this.lastViewsStore.set(result[0])
if (result[0] === undefined) {
const client = getClient()
const u = client.txFactory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, {
user: this.user
})
u.space = core.space.DerivedTx
void client.tx(u)
}
})
this.docUpdatesQuery.query(
notification.class.DocUpdates,
{
user: this.user
},
(result) => {
this.docUpdatesStore.set(new Map(result.map((p) => [p.attachedTo, p])))
this.docUpdates.set(result)
this.docUpdatesMap = new Map(result.map((p) => [p.attachedTo, p]))
this.docUpdatesStore.set(this.docUpdatesMap)
}
)
}
@ -67,46 +58,49 @@ export class NotificationClientImpl implements NotificationClient {
return NotificationClientImpl._instance
}
getLastViews (): Writable<LastView> {
return this.lastViewsStore
}
async updateLastView (
_id: Ref<Doc>,
_class: Ref<Class<Doc>>,
time?: Timestamp,
force: boolean = false
): Promise<void> {
async read (_id: Ref<Doc>): Promise<void> {
const client = getClient()
const hierarchy = client.getHierarchy()
const mixin = hierarchy.classHierarchyMixin(_class, notification.mixin.TrackedDoc)
if (mixin === undefined) return
const lastView = time ?? new Date().getTime()
const obj = get(this.lastViewsStore)
if (obj !== undefined) {
const current = obj[_id] as Timestamp | undefined
if (current !== undefined || force) {
if (current === -1 && !force) return
if (force || (current ?? 0) < lastView) {
const u = client.txFactory.createTxUpdateDoc(obj._class, obj.space, obj._id, {
[_id]: lastView
})
u.space = core.space.DerivedTx
await client.tx(u)
}
}
const docUpdate = this.docUpdatesMap.get(_id)
if (docUpdate !== undefined) {
await client.update(docUpdate, { txes: [] })
}
}
async unsubscribe (_id: Ref<Doc>): Promise<void> {
async forceRead (_id: Ref<Doc>, _class: Ref<Class<Doc>>): Promise<void> {
const client = getClient()
const obj = get(this.lastViewsStore)
if (obj !== undefined) {
const u = client.txFactory.createTxUpdateDoc(obj._class, obj.space, obj._id, {
[_id]: -1
})
u.space = core.space.DerivedTx
await client.tx(u)
const docUpdate = this.docUpdatesMap.get(_id)
if (docUpdate !== undefined) {
await client.update(docUpdate, { txes: [] })
} else {
const doc = await client.findOne(_class, { _id })
if (doc !== undefined) {
const hiearachy = client.getHierarchy()
const collab = hiearachy.as<Doc, Collaborators>(doc, notification.mixin.Collaborators)
if (collab.collaborators === undefined) {
await client.createMixin<Doc, Collaborators>(
collab._id,
collab._class,
collab.space,
notification.mixin.Collaborators,
{
collaborators: [this.user]
}
)
} else if (!collab.collaborators.includes(this.user)) {
await client.updateMixin(collab._id, collab._class, collab.space, notification.mixin.Collaborators, {
$push: {
collaborators: this.user
}
})
}
await client.createDoc(notification.class.DocUpdates, doc.space, {
attachedTo: _id,
attachedToClass: _class,
user: this.user,
hidden: true,
txes: []
})
}
}
}
}

View File

@ -34,14 +34,6 @@ import { Writable } from './types'
import { Preference } from '@hcengineering/preference'
export * from './types'
/**
* @public
*/
export interface LastView extends Doc {
user: Ref<Account>
[key: string]: any
}
/**
* @public
*/
@ -135,20 +127,6 @@ export interface NotificationSetting extends Preference {
enabled: boolean
}
/**
* @public
*/
export interface SpaceLastEdit extends Class<Doc> {
lastEditField: string
}
/**
* @public
*/
export interface AnotherUserNotifications extends Class<Doc> {
fields: string[]
}
/**
* @public
*/
@ -156,11 +134,6 @@ export interface ClassCollaborators extends Class<Doc> {
fields: string[] // Ref<Account> | Ref<Employee> | Ref<Account>[] | Ref<Employee>[]
}
/**
* @public
*/
export interface TrackedDoc extends Class<Doc> {}
/**
* @public
*/
@ -183,8 +156,8 @@ export interface DocUpdates extends Doc {
attachedTo: Ref<Doc>
attachedToClass: Ref<Class<Doc>>
hidden: boolean
lastTx: Ref<TxCUD<Doc>>
lastTxTime: Timestamp
lastTx?: Ref<TxCUD<Doc>>
lastTxTime?: Timestamp
txes: [Ref<TxCUD<Doc>>, Timestamp][]
}
@ -198,9 +171,9 @@ export const notificationId = 'notification' as Plugin
*/
export interface NotificationClient {
docUpdatesStore: Writable<Map<Ref<Doc>, DocUpdates>>
getLastViews: () => Writable<LastView>
updateLastView: (_id: Ref<Doc>, _class: Ref<Class<Doc>>, time?: Timestamp, force?: boolean) => Promise<void>
unsubscribe: (_id: Ref<Doc>) => Promise<void>
docUpdates: Writable<DocUpdates[]>
read: (_id: Ref<Doc>) => Promise<void>
forceRead: (_id: Ref<Doc>, _class: Ref<Class<Doc>>, space: Ref<Space>) => Promise<void>
}
/**
@ -213,15 +186,11 @@ export type NotificationClientFactoy = () => NotificationClient
*/
const notification = plugin(notificationId, {
mixin: {
SpaceLastEdit: '' as Ref<Mixin<SpaceLastEdit>>,
AnotherUserNotifications: '' as Ref<Mixin<AnotherUserNotifications>>,
ClassCollaborators: '' as Ref<Mixin<ClassCollaborators>>,
Collaborators: '' as Ref<Mixin<Collaborators>>,
TrackedDoc: '' as Ref<Mixin<TrackedDoc>>,
NotificationObjectPresenter: '' as Ref<Mixin<NotificationObjectPresenter>>
},
class: {
LastView: '' as Ref<Class<LastView>>,
Notification: '' as Ref<Class<Notification>>,
EmailNotification: '' as Ref<Class<EmailNotification>>,
NotificationType: '' as Ref<Class<NotificationType>>,

View File

@ -44,7 +44,7 @@
const notificationClient = getResource(notification.function.GetNotificationClient).then((res) => res())
onDestroy(async () => {
notificationClient.then((client) => client.updateLastView(_id, recruit.class.Vacancy))
notificationClient.then((client) => client.read(_id))
})
const client = getClient()
@ -57,7 +57,7 @@
const prev = lastId
lastId = _id
if (prev) {
notificationClient.then((client) => client.updateLastView(prev, recruit.class.Vacancy))
notificationClient.then((client) => client.read(prev))
}
query.query(recruit.class.Vacancy, { _id }, (result) => {
object = result[0] as Required<Vacancy>

View File

@ -110,7 +110,7 @@
(res) => {
messages = res.reverse()
if (channel !== undefined) {
notificationClient.updateLastView(channel._id, channel._class, undefined, true)
notificationClient.read(channel._id)
}
},
{
@ -171,7 +171,7 @@
}
)
if (channel !== undefined) {
await notificationClient.updateLastView(channel._id, channel._class, channel.modifiedOn, true)
await notificationClient.read(channel._id)
}
clear()
}

View File

@ -47,7 +47,6 @@
export let embedded = false
let lastId: Ref<Doc> = _id
let lastClass: Ref<Class<Doc>> = _class
const queryClient = createQuery()
const dispatch = createEventDispatcher()
const client = getClient()
@ -66,15 +65,13 @@
function read (_id: Ref<Doc>) {
if (lastId !== _id) {
const prev = lastId
const prevClass = lastClass
lastId = _id
lastClass = _class
notificationClient.then((client) => client.updateLastView(prev, prevClass))
notificationClient.then((client) => client.read(prev))
}
}
onDestroy(async () => {
notificationClient.then((client) => client.updateLastView(_id, _class))
notificationClient.then((client) => client.read(_id))
})
$: _id &&

View File

@ -46,7 +46,6 @@
export let _class: Ref<Class<IssueTemplate>>
let lastId: Ref<Doc> = _id
let lastClass: Ref<Class<Doc>> = _class
const query = createQuery()
const dispatch = createEventDispatcher()
const client = getClient()
@ -65,15 +64,13 @@
function read (_id: Ref<Doc>) {
if (lastId !== _id) {
const prev = lastId
const prevClass = lastClass
lastId = _id
lastClass = _class
notificationClient.then((client) => client.updateLastView(prev, prevClass))
notificationClient.then((client) => client.read(prev))
}
}
onDestroy(async () => {
notificationClient.then((client) => client.updateLastView(_id, _class))
notificationClient.then((client) => client.read(_id))
})
$: _id &&

View File

@ -44,7 +44,6 @@
let realObjectClass: Ref<Class<Doc>> = _class
let lastId: Ref<Doc> = _id
let lastClass: Ref<Class<Doc>> = _class
let object: Doc
const client = getClient()
@ -55,15 +54,13 @@
function read (_id: Ref<Doc>) {
if (lastId !== _id) {
const prev = lastId
const prevClass = lastClass
lastId = _id
lastClass = _class
notificationClient.then((client) => client.updateLastView(prev, prevClass))
notificationClient.then((client) => client.read(prev))
}
}
onDestroy(async () => {
notificationClient.then((client) => client.updateLastView(_id, _class))
notificationClient.then((client) => client.read(_id))
})
const query = createQuery()

View File

@ -15,7 +15,7 @@
<script lang="ts">
import type { Class, Doc, Ref, Space } from '@hcengineering/core'
import core from '@hcengineering/core'
import notification, { LastView } from '@hcengineering/notification'
import { DocUpdates } from '@hcengineering/notification'
import { NotificationClientImpl } from '@hcengineering/notification-resources'
import { getResource } from '@hcengineering/platform'
import preference from '@hcengineering/preference'
@ -98,20 +98,13 @@
}
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
const docUpdates = notificationClient.docUpdatesStore
const hierarchy = client.getHierarchy()
$: clazz = hierarchy.getClass(model.spaceClass)
$: lastEditMixin = hierarchy.as(clazz, notification.mixin.SpaceLastEdit)
function isChanged (space: Space, lastViews: LastView): boolean {
const field = lastEditMixin?.lastEditField
const lastView = lastViews[space._id]
if (lastView === undefined || lastView === -1) return false
if (field === undefined) return false
const value = (space as any)[field]
if (isNaN(value)) return false
return lastView < value
function isChanged (space: Space, docUpdates: Map<Ref<Doc>, DocUpdates>): boolean {
const update = docUpdates.get(space._id)
if (update === undefined) return false
return update.txes.length > 0 && update.hidden !== true
}
function getParentActions (): Action[] {
@ -147,7 +140,7 @@
icon={classIcon(client, space._class)}
selected={deselect ? false : currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
bold={isChanged(space, $docUpdates)}
/>
{/await}
</NavLink>

View File

@ -15,7 +15,7 @@
<script lang="ts">
import type { Doc, Ref, Space } from '@hcengineering/core'
import core from '@hcengineering/core'
import notification, { LastView } from '@hcengineering/notification'
import { DocUpdates } from '@hcengineering/notification'
import { NotificationClientImpl } from '@hcengineering/notification-resources'
import { IntlString, getResource } from '@hcengineering/platform'
import preference from '@hcengineering/preference'
@ -74,20 +74,12 @@
}
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
const hierarchy = client.getHierarchy()
const docUpdates = notificationClient.docUpdatesStore
function isChanged (space: Space, lastViews: LastView): boolean {
const clazz = hierarchy.getClass(space._class)
const lastEditMixin = hierarchy.as(clazz, notification.mixin.SpaceLastEdit)
const field = lastEditMixin?.lastEditField
const lastView = lastViews[space._id]
if (lastView === undefined || lastView === -1) return false
if (field === undefined) return false
const value = (space as any)[field]
if (isNaN(value)) return false
return lastView < value
function isChanged (space: Space, docUpdates: Map<Ref<Doc>, DocUpdates>): boolean {
const update = docUpdates.get(space._id)
if (update === undefined) return false
return update.txes.length > 0 && update.hidden !== true
}
</script>
@ -102,7 +94,7 @@
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
bold={isChanged(space, $docUpdates)}
/>
</NavLink>
{/await}

View File

@ -26,7 +26,6 @@ import core, {
Hierarchy,
Ref,
Tx,
TxCollectionCUD,
TxCreateDoc,
TxCUD,
TxProcessor,
@ -192,84 +191,11 @@ async function ThreadMessageDelete (tx: Tx, control: TriggerControl): Promise<Tx
return [updateTx]
}
async function MessageCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const hierarchy = control.hierarchy
const actualTx = TxProcessor.extractTx(tx)
if (actualTx._class !== core.class.TxCreateDoc) return []
const doc = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<Doc>)
if (!hierarchy.isDerived(doc._class, chunter.class.Message)) {
return []
}
const message = doc as Message
const channel = (
await control.findAll(
chunter.class.ChunterSpace,
{
_id: message.space
},
{ limit: 1 }
)
)[0]
if (channel?.lastMessage === undefined || channel.lastMessage < message.createOn) {
const res = control.txFactory.createTxUpdateDoc<ChunterSpace>(channel._class, channel.space, channel._id, {
lastMessage: message.createOn
})
return [res]
}
return []
}
async function MessageDelete (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const hierarchy = control.hierarchy
const rmTx = TxProcessor.extractTx(tx) as TxCollectionCUD<ChunterSpace, Message>
if (rmTx._class !== core.class.TxRemoveDoc) return []
if (!hierarchy.isDerived(rmTx.objectClass, chunter.class.Message)) {
return []
}
const message = control.removedMap.get(rmTx.objectId) as Message
if (message === undefined) {
return []
}
const channel = (
await control.findAll(
chunter.class.ChunterSpace,
{
_id: message.space
},
{ limit: 1 }
)
)[0]
if (channel?.lastMessage === message.createOn) {
const messages = await control.findAll(chunter.class.Message, {
attachedTo: channel._id
})
const lastMessageDate = messages.reduce((maxDate, mess) => (mess.createOn > maxDate ? mess.createOn : maxDate), 0)
const updateTx = control.txFactory.createTxUpdateDoc<ChunterSpace>(channel._class, channel.space, channel._id, {
lastMessage: lastMessageDate > 0 ? lastMessageDate : undefined
})
return [updateTx]
}
return []
}
/**
* @public
*/
export async function ChunterTrigger (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const promises = [
MessageCreate(tx, control),
MessageDelete(tx, control),
ThreadMessageCreate(tx, control),
ThreadMessageDelete(tx, control),
CommentCreate(tx as TxCUD<Doc>, control)

View File

@ -100,7 +100,7 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
}
if (docs.length === 0) {
res.push(
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, notification.space.Notifications, {
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, channel.space, {
user: tx.modifiedBy,
attachedTo: channel._id,
attachedToClass: channel._class,

View File

@ -45,7 +45,6 @@ import notification, {
Collaborators,
DocUpdates,
EmailNotification,
LastView,
NotificationProvider,
NotificationType
} from '@hcengineering/notification'
@ -54,7 +53,6 @@ import type { TriggerControl } from '@hcengineering/server-core'
import serverNotification, {
HTMLPresenter,
TextPresenter,
createLastViewTx,
getEmployeeAccount,
getEmployeeAccountById
} from '@hcengineering/server-notification'
@ -254,62 +252,6 @@ async function getEmailNotificationTx (
}
}
/**
* @public
*/
export async function UpdateLastView (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = TxProcessor.extractTx(tx)
if (actualTx._class !== core.class.TxRemoveDoc) {
return []
}
if ((actualTx as TxCUD<Doc>).objectClass === notification.class.LastView) {
return []
}
const result: Tx[] = []
const removeTx = actualTx as TxRemoveDoc<Doc>
const lastViews = await control.findAll(notification.class.LastView, { [removeTx.objectId]: { $exists: true } })
for (const lastView of lastViews) {
const clearTx = control.txFactory.createTxUpdateDoc(lastView._class, lastView.space, lastView._id, {
$unset: {
[removeTx.objectId]: ''
}
})
result.push(clearTx)
}
return result
}
/**
* @public
*/
export async function OnUpdateLastView (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = TxProcessor.extractTx(tx) as TxUpdateDoc<LastView>
if (actualTx._class !== core.class.TxUpdateDoc) return []
if (actualTx.objectClass !== notification.class.LastView) return []
const result: Tx[] = []
const lastView = (await control.findAll(notification.class.LastView, { _id: actualTx.objectId }))[0]
if (lastView === undefined) return result
for (const key in actualTx.operations) {
const docs = await control.findAll(notification.class.DocUpdates, {
attachedTo: key as Ref<Doc>,
user: lastView.user
})
for (const doc of docs) {
const txes = doc.txes.filter((p) => p[1] > actualTx.operations[key])
result.push(
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
txes
})
)
}
}
return result
}
function getBacklink (ptx: TxCollectionCUD<Doc, Backlink>): Backlink {
return TxProcessor.createDoc2Doc(ptx.tx as TxCreateDoc<Backlink>)
}
@ -493,7 +435,7 @@ async function getNotificationTxes (
const current = docUpdates.find((p) => p.user === target)
if (current === undefined) {
res.push(
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, notification.space.Notifications, {
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, object.space, {
user: target,
attachedTo: object._id,
attachedToClass: object._class,
@ -709,6 +651,17 @@ function isMixinTx (tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): tx is TxMixin<Doc
return tx._class === core.class.TxMixin
}
async function changeSpaceTxes (control: TriggerControl, tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): Promise<Tx[]> {
if (tx._class !== core.class.TxUpdateDoc) return []
const ctx = tx as TxUpdateDoc<Doc>
if (ctx.operations.space === undefined) return []
const docUpdates = await control.findAll(notification.class.DocUpdates, { attachedTo: tx.objectId })
return docUpdates.map((value) =>
control.txFactory.createTxUpdateDoc(value._class, value.space, value._id, { space: ctx.operations.space })
)
}
async function updateCollaboratorDoc (
tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>,
control: TriggerControl,
@ -750,47 +703,11 @@ async function updateCollaboratorDoc (
res = res.concat(await getSpaceCollabTxes(control, doc, tx, originTx))
res = res.concat(await changeSpaceTxes(control, tx))
return res
}
/**
* @public
*/
export async function OnAddCollborator (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const result: Tx[] = []
const actualTx = TxProcessor.extractTx(tx) as TxMixin<Doc, Collaborators>
if (actualTx._class !== core.class.TxMixin) return []
if (actualTx.mixin !== notification.mixin.Collaborators) return []
if (actualTx.attributes.collaborators !== undefined) {
for (const collab of actualTx.attributes.collaborators) {
const resTx = await createLastViewTx(control.findAll, actualTx.objectId, collab)
if (resTx !== undefined) {
result.push(resTx)
}
}
}
if (actualTx.attributes.$push?.collaborators !== undefined) {
const collab = actualTx.attributes.$push?.collaborators
if (typeof collab === 'object') {
if ('$each' in collab) {
for (const collaborator of collab.$each) {
const resTx = await createLastViewTx(control.findAll, actualTx.objectId, collaborator)
if (resTx !== undefined) {
result.push(resTx)
}
}
}
} else {
const resTx = await createLastViewTx(control.findAll, actualTx.objectId, collab)
if (resTx !== undefined) {
result.push(resTx)
}
}
}
return result
}
/**
* @public
*/
@ -897,9 +814,6 @@ export default async () => ({
trigger: {
OnBacklinkCreate,
CollaboratorDocHandler: collaboratorDocHandler,
OnUpdateLastView,
UpdateLastView,
OnAddCollborator,
OnAttributeCreate,
OnAttributeUpdate
},

View File

@ -15,8 +15,8 @@
//
import contact, { Employee, EmployeeAccount } from '@hcengineering/contact'
import { Account, Class, Doc, Mixin, Ref, Tx, TxCreateDoc, TxFactory, TxUpdateDoc } from '@hcengineering/core'
import notification, { LastView, NotificationType } from '@hcengineering/notification'
import { Account, Class, Doc, Mixin, Ref, Tx } from '@hcengineering/core'
import { NotificationType } from '@hcengineering/notification'
import { Plugin, Resource, plugin } from '@hcengineering/platform'
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
@ -25,42 +25,6 @@ import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
*/
export const serverNotificationId = 'server-notification' as Plugin
/**
* @public
*/
export async function getUpdateLastViewTx (
findAll: TriggerControl['findAll'],
attachedTo: Ref<Doc>,
lastView: number,
user: Ref<Account>
): Promise<TxUpdateDoc<LastView> | TxCreateDoc<LastView> | undefined> {
const current = (
await findAll(
notification.class.LastView,
{
user
},
{ limit: 1 }
)
)[0]
const factory = new TxFactory(user, true)
if (current !== undefined) {
if (current[attachedTo] === -1 || current[attachedTo] >= lastView) {
return
}
const u = factory.createTxUpdateDoc(current._class, current.space, current._id, {
[attachedTo]: lastView
})
return u
} else {
const u = factory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, {
user,
[attachedTo]: lastView
})
return u
}
}
/**
* @public
*/
@ -115,38 +79,6 @@ export async function getEmployee (employee: Ref<Employee>, control: TriggerCont
return account
}
/**
* @public
*/
export async function createLastViewTx (
findAll: TriggerControl['findAll'],
attachedTo: Ref<Doc>,
user: Ref<Account>
): Promise<TxCreateDoc<LastView> | TxUpdateDoc<LastView> | undefined> {
const current = (
await findAll(
notification.class.LastView,
{
user
},
{ limit: 1 }
)
)[0]
const factory = new TxFactory(user, true)
if (current === undefined) {
const u = factory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, {
user,
[attachedTo]: 1
})
return u
} else if (current[attachedTo] === undefined) {
const u = factory.createTxUpdateDoc(current._class, current.space, current._id, {
[attachedTo]: 1
})
return u
}
}
/**
* @public
*/
@ -191,10 +123,7 @@ export default plugin(serverNotificationId, {
},
trigger: {
OnBacklinkCreate: '' as Resource<TriggerFunc>,
UpdateLastView: '' as Resource<TriggerFunc>,
OnUpdateLastView: '' as Resource<TriggerFunc>,
CollaboratorDocHandler: '' as Resource<TriggerFunc>,
OnAddCollborator: '' as Resource<TriggerFunc>,
OnAttributeCreate: '' as Resource<TriggerFunc>,
OnAttributeUpdate: '' as Resource<TriggerFunc>
},

View File

@ -101,7 +101,7 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
}
if (docs.length === 0) {
res.push(
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, notification.space.Notifications, {
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, channel.space, {
user: tx.modifiedBy,
attachedTo: channel._id,
attachedToClass: channel._class,