mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-05 06:49:30 +00:00
TSK-925 Collaborators (#2849)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
c2f32f997f
commit
bdbe7de4f1
@ -194,6 +194,16 @@ 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']
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: chunter.component.DmPresenter
|
||||
})
|
||||
|
@ -37,6 +37,7 @@
|
||||
"@hcengineering/ui": "^0.6.3",
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/contact": "^0.6.11",
|
||||
"@hcengineering/notification": "^0.6.7",
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.2",
|
||||
"cross-fetch": "^3.1.5",
|
||||
|
@ -55,6 +55,7 @@ import workbench from '@hcengineering/model-workbench'
|
||||
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import notification from '@hcengineering/notification'
|
||||
import templates from '@hcengineering/templates'
|
||||
import contact from './plugin'
|
||||
|
||||
@ -313,6 +314,14 @@ 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: []
|
||||
})
|
||||
|
||||
builder.mixin(contact.class.Member, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: contact.component.MemberPresenter
|
||||
})
|
||||
|
@ -36,6 +36,7 @@
|
||||
"@hcengineering/core": "^0.6.21",
|
||||
"@hcengineering/ui": "^0.6.3",
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/notification": "^0.6.7",
|
||||
"@hcengineering/document": "^0.6.0",
|
||||
"@hcengineering/document-resources": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.2",
|
||||
|
@ -46,6 +46,7 @@ import presentation from '@hcengineering/model-presentation'
|
||||
import view, { actionTemplates, createAction } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import tags from '@hcengineering/tags'
|
||||
import notification from '@hcengineering/notification'
|
||||
import document from './plugin'
|
||||
|
||||
export const DOMAIN_DOCUMENT = 'document' as Domain
|
||||
@ -182,6 +183,8 @@ 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
|
||||
})
|
||||
|
@ -156,7 +156,7 @@ export function createModel (builder: Builder): void {
|
||||
inventory.category.Inventory
|
||||
)
|
||||
|
||||
builder.mixin(inventory.class.Product, core.class.Class, notification.mixin.LastViewAttached, {})
|
||||
builder.mixin(inventory.class.Product, core.class.Class, notification.mixin.TrackedDoc, {})
|
||||
|
||||
createAction(builder, {
|
||||
label: inventory.string.CreateSubcategory,
|
||||
|
@ -14,14 +14,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Account, Doc, Domain, DOMAIN_MODEL, Ref, Timestamp, TxCUD } from '@hcengineering/core'
|
||||
import { ArrOf, Builder, Mixin, Model, Prop, TypeRef, TypeString, TypeTimestamp } from '@hcengineering/model'
|
||||
import { Account, Doc, Domain, DOMAIN_MODEL, IndexKind, Ref, TxCUD } from '@hcengineering/core'
|
||||
import { ArrOf, Builder, Index, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@hcengineering/model'
|
||||
import core, { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
|
||||
import type {
|
||||
AnotherUserNotifications,
|
||||
EmailNotification,
|
||||
LastView,
|
||||
LastViewAttached,
|
||||
Notification,
|
||||
NotificationProvider,
|
||||
NotificationSetting,
|
||||
@ -35,12 +34,10 @@ import notification from './plugin'
|
||||
|
||||
export const DOMAIN_NOTIFICATION = 'notification' as Domain
|
||||
|
||||
@Model(notification.class.LastView, core.class.AttachedDoc, DOMAIN_NOTIFICATION)
|
||||
export class TLastView extends TAttachedDoc implements LastView {
|
||||
@Prop(TypeTimestamp(), notification.string.LastView)
|
||||
lastView!: Timestamp
|
||||
|
||||
@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>
|
||||
}
|
||||
|
||||
@ -112,8 +109,20 @@ export class TAnotherUserNotifications extends TClass implements AnotherUserNoti
|
||||
fields!: string[]
|
||||
}
|
||||
|
||||
@Mixin(notification.mixin.LastViewAttached, core.class.Class)
|
||||
export class TLastViewAttached extends TClass implements LastViewAttached {}
|
||||
@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 {
|
||||
@Prop(ArrOf(TypeRef(core.class.Account)), notification.string.Collaborators)
|
||||
collaborators!: Ref<Account>[]
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(
|
||||
@ -125,7 +134,9 @@ export function createModel (builder: Builder): void {
|
||||
TNotificationSetting,
|
||||
TSpaceLastEdit,
|
||||
TAnotherUserNotifications,
|
||||
TLastViewAttached
|
||||
TClassCollaborators,
|
||||
TTrackedDoc,
|
||||
TCollaborators
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
@ -155,24 +166,28 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.PlatformNotification,
|
||||
default: true
|
||||
label: notification.string.Notification,
|
||||
hidden: true,
|
||||
textTemplate: '',
|
||||
htmlTemplate: '',
|
||||
subjectTemplate: ''
|
||||
},
|
||||
notification.ids.PlatformNotification
|
||||
notification.ids.CollaboratorNotification
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.BrowserNotification,
|
||||
default: true
|
||||
},
|
||||
notification.ids.BrowserNotification
|
||||
)
|
||||
// Temporarily disabled, we should think about it
|
||||
// builder.createDoc(
|
||||
// notification.class.NotificationProvider,
|
||||
// core.space.Model,
|
||||
// {
|
||||
// label: notification.string.BrowserNotification,
|
||||
// default: true
|
||||
// },
|
||||
// notification.ids.BrowserNotification
|
||||
// )
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationProvider,
|
||||
|
@ -13,9 +13,21 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { DOMAIN_TX, Ref, TxCreateDoc, TxOperations } from '@hcengineering/core'
|
||||
import core, {
|
||||
Account,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Doc,
|
||||
DOMAIN_TX,
|
||||
generateId,
|
||||
Ref,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxOperations,
|
||||
TxRemoveDoc
|
||||
} from '@hcengineering/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
|
||||
import notification, { Notification, NotificationType } from '@hcengineering/notification'
|
||||
import notification, { LastView, Notification, NotificationType } from '@hcengineering/notification'
|
||||
import { DOMAIN_NOTIFICATION } from '.'
|
||||
|
||||
async function fillNotificationText (client: MigrationClient): Promise<void> {
|
||||
@ -79,9 +91,104 @@ 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) {
|
||||
const domain = client.hierarchy.getDomain(targetClass._id)
|
||||
const desc = client.hierarchy.getDescendants(targetClass._id)
|
||||
await client.update(
|
||||
domain,
|
||||
{
|
||||
_class: { $in: desc },
|
||||
'notification:mixin:Collaborators': { $exists: false }
|
||||
},
|
||||
{
|
||||
'notification:mixin:Collaborators': {
|
||||
collaborators: []
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const notificationOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await fillNotificationText(client)
|
||||
await migrateLastView(client)
|
||||
await fillCollaborators(client)
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
await createSpace(client)
|
||||
|
@ -26,7 +26,8 @@ export default mergeIds(notificationId, notification, {
|
||||
MentionNotification: '' as IntlString,
|
||||
PlatformNotification: '' as IntlString,
|
||||
BrowserNotification: '' as IntlString,
|
||||
EmailNotification: '' as IntlString
|
||||
EmailNotification: '' as IntlString,
|
||||
Collaborators: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
NotificationSettings: '' as AnyComponent
|
||||
|
@ -37,6 +37,7 @@
|
||||
"@hcengineering/recruit": "^0.6.7",
|
||||
"@hcengineering/recruit-resources": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.2",
|
||||
"@hcengineering/notification": "^0.6.7",
|
||||
"@hcengineering/model-attachment": "^0.6.0",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.2",
|
||||
|
@ -41,6 +41,7 @@ import presentation from '@hcengineering/model-presentation'
|
||||
import tags from '@hcengineering/model-tags'
|
||||
import task, { actionTemplates, DOMAIN_TASK, TSpaceWithStates, TTask } from '@hcengineering/model-task'
|
||||
import tracker from '@hcengineering/model-tracker'
|
||||
import notification from '@hcengineering/notification'
|
||||
import view, { actionTemplates as viewTemplates, createAction } from '@hcengineering/model-view'
|
||||
import workbench, { Application, createNavigateAction } from '@hcengineering/model-workbench'
|
||||
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
@ -211,6 +212,14 @@ export function createModel (builder: Builder): void {
|
||||
editor: recruit.component.VacancyList
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Vacancy, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['createdBy']
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Applicant, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['createdBy']
|
||||
})
|
||||
|
||||
builder.mixin(recruit.mixin.Candidate, core.class.Mixin, view.mixin.ObjectFactory, {
|
||||
component: recruit.component.CreateCandidate
|
||||
})
|
||||
@ -603,6 +612,10 @@ 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
|
||||
})
|
||||
|
@ -42,4 +42,16 @@ export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverNotification.trigger.UpdateLastView
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverNotification.trigger.CreateCollaboratorDoc
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverNotification.trigger.UpdateCollaboratorDoc
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverNotification.trigger.OnAddCollborator
|
||||
})
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ export function createModel (builder: Builder): void {
|
||||
editor: task.component.TaskHeader
|
||||
})
|
||||
|
||||
builder.mixin(task.class.Task, core.class.Class, notification.mixin.LastViewAttached, {})
|
||||
builder.mixin(task.class.Task, core.class.Class, notification.mixin.TrackedDoc, {})
|
||||
builder.mixin(task.class.Task, core.class.Class, notification.mixin.AnotherUserNotifications, {
|
||||
fields: ['assignee']
|
||||
})
|
||||
|
@ -886,6 +886,10 @@ export function createModel (builder: Builder): void {
|
||||
presenter: tracker.component.PriorityPresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['createdBy', 'assignee']
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AttributeFilter, {
|
||||
component: view.component.ValueFilter
|
||||
})
|
||||
@ -937,7 +941,8 @@ export function createModel (builder: Builder): void {
|
||||
inlineEditor: tracker.component.ComponentStatusEditor
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.LastViewAttached, {})
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.TrackedDoc, {})
|
||||
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.AnotherUserNotifications, {
|
||||
fields: ['assignee']
|
||||
})
|
||||
|
@ -74,6 +74,17 @@ export class Hierarchy {
|
||||
return typeof (d as any)[mixin] === 'object'
|
||||
}
|
||||
|
||||
classHierarchyMixin<D extends Doc, M extends D>(_class: Ref<Class<D>>, mixin: Ref<Mixin<M>>): M | undefined {
|
||||
let clazz = this.getClass(_class)
|
||||
while (true) {
|
||||
if (this.hasMixin(clazz, mixin)) {
|
||||
return this.as(clazz, mixin) as any as M
|
||||
}
|
||||
if (clazz.extends === undefined) return
|
||||
clazz = this.getClass(clazz.extends)
|
||||
}
|
||||
}
|
||||
|
||||
isMixin (_class: Ref<Class<Doc>>): boolean {
|
||||
const data = this.classifiers.get(_class)
|
||||
return data !== undefined && this._isMixin(data)
|
||||
|
@ -150,13 +150,24 @@ function $inc (document: Doc, keyval: Record<string, number>): void {
|
||||
}
|
||||
}
|
||||
|
||||
function $unset (document: Doc, keyval: Record<string, PropertyType>): void {
|
||||
const doc = document as any
|
||||
for (const key in keyval) {
|
||||
if (doc[key] !== undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete doc[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const operators: Record<string, _OperatorFunc> = {
|
||||
$push,
|
||||
$pull,
|
||||
$update,
|
||||
$move,
|
||||
$pushMixin,
|
||||
$inc
|
||||
$inc,
|
||||
$unset
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,6 +201,20 @@ export interface PushOptions<T extends object> {
|
||||
$move?: Partial<OmitNever<ArrayMoveDescriptor<Required<T>>>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface UnsetProperties {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface UnsetOptions {
|
||||
$unset?: UnsetProperties
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
ArrayAsElementPosition,
|
||||
Client,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
@ -9,25 +8,18 @@ import {
|
||||
IncOptions,
|
||||
ModelDb,
|
||||
ObjQueryType,
|
||||
OmitNever,
|
||||
PushOptions,
|
||||
Ref
|
||||
Ref,
|
||||
UnsetOptions
|
||||
} from '@hcengineering/core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface UnsetOptions<T extends object> {
|
||||
$unset?: Partial<OmitNever<ArrayAsElementPosition<Required<T>>>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type MigrateUpdate<T extends Doc> = Partial<T> &
|
||||
Omit<PushOptions<T>, '$move'> &
|
||||
IncOptions<T> &
|
||||
UnsetOptions<T> & {
|
||||
UnsetOptions & {
|
||||
// For any other mongo stuff
|
||||
[key: string]: any
|
||||
}
|
||||
|
@ -40,8 +40,7 @@
|
||||
"@hcengineering/chunter": "^0.6.2",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/calendar": "^0.6.3",
|
||||
"@hcengineering/notification": "^0.6.7"
|
||||
"@hcengineering/calendar": "^0.6.3"
|
||||
},
|
||||
"repository": "https://github.com/hcenginneing/anticrm",
|
||||
"publishConfig": {
|
||||
|
@ -17,10 +17,15 @@
|
||||
import activity from '@hcengineering/activity'
|
||||
import calendar from '@hcengineering/calendar'
|
||||
import type { Doc } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import type { Asset } from '@hcengineering/platform'
|
||||
import { AnySvelteComponent, Component, Panel, Icon, Scroller } from '@hcengineering/ui'
|
||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
import {
|
||||
AnySvelteComponent,
|
||||
Component,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
Icon,
|
||||
Panel,
|
||||
Scroller
|
||||
} from '@hcengineering/ui'
|
||||
|
||||
export let title: string | undefined = undefined
|
||||
export let subtitle: string | undefined = undefined
|
||||
@ -89,7 +94,6 @@
|
||||
|
||||
<svelte:fragment slot="utils">
|
||||
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
|
||||
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
|
||||
{#if isUtils && $$slots.utils}
|
||||
<div class="buttons-divider" />
|
||||
<slot name="utils" />
|
||||
|
@ -15,8 +15,8 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { Label, tooltip } from '@hcengineering/ui'
|
||||
import type { AnySvelteComponent, ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
import { Label, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getAttribute, KeyedAttribute, updateAttribute } from '../attributes'
|
||||
import { getAttributeEditor, getClient } from '../utils'
|
||||
@ -38,8 +38,7 @@
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let editor: Promise<void | AnySvelteComponent> | undefined
|
||||
let editor: AnySvelteComponent | undefined
|
||||
|
||||
function onChange (value: any) {
|
||||
const doc = object as Doc
|
||||
@ -47,63 +46,64 @@
|
||||
;(doc as any)[attributeKey] = value
|
||||
dispatch('update', { key, value })
|
||||
} else {
|
||||
updateAttribute(client, doc, _class, { key: attributeKey, attr: attribute }, value)
|
||||
updateAttribute(client, doc, doc._class, { key: attributeKey, attr: attribute }, value)
|
||||
}
|
||||
}
|
||||
|
||||
function getEditor (_class: Ref<Class<Doc>>, key: KeyedAttribute | string) {
|
||||
getAttributeEditor(client, _class, key).then((p) => (editor = p))
|
||||
}
|
||||
|
||||
$: getEditor(_class, key)
|
||||
|
||||
$: attribute = typeof key === 'string' ? hierarchy.getAttribute(_class, key) : key.attr
|
||||
$: attributeKey = typeof key === 'string' ? key : key.key
|
||||
$: editor = getAttributeEditor(client, _class, key)
|
||||
$: isReadonly = (attribute.readonly ?? false) || readonly
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
{#await editor then instance}
|
||||
{#if instance}
|
||||
{#if showHeader}
|
||||
<span
|
||||
class="overflow-label"
|
||||
use:tooltip={{
|
||||
component: Label,
|
||||
props: { label: attribute.label }
|
||||
}}><Label label={attribute.label} /></span
|
||||
>
|
||||
<div class="flex flex-grow min-w-0">
|
||||
<svelte:component
|
||||
this={instance}
|
||||
readonly={isReadonly}
|
||||
label={attribute?.label}
|
||||
placeholder={attribute?.label}
|
||||
{kind}
|
||||
{size}
|
||||
{width}
|
||||
{justify}
|
||||
type={attribute?.type}
|
||||
{maxWidth}
|
||||
{attributeKey}
|
||||
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
{object}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div style="grid-column: 1/3;">
|
||||
<svelte:component
|
||||
this={instance}
|
||||
type={attribute?.type}
|
||||
{maxWidth}
|
||||
{attributeKey}
|
||||
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
|
||||
readonly={isReadonly}
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
{object}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/await}
|
||||
{#if showHeader}
|
||||
<span
|
||||
class="overflow-label"
|
||||
use:tooltip={{
|
||||
component: Label,
|
||||
props: { label: attribute.label }
|
||||
}}><Label label={attribute.label} /></span
|
||||
>
|
||||
<div class="flex flex-grow min-w-0">
|
||||
<svelte:component
|
||||
this={editor}
|
||||
readonly={isReadonly}
|
||||
label={attribute?.label}
|
||||
placeholder={attribute?.label}
|
||||
{kind}
|
||||
{size}
|
||||
{width}
|
||||
{justify}
|
||||
type={attribute?.type}
|
||||
{maxWidth}
|
||||
{attributeKey}
|
||||
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
{object}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div style="grid-column: 1/3;">
|
||||
<svelte:component
|
||||
this={editor}
|
||||
type={attribute?.type}
|
||||
{maxWidth}
|
||||
{attributeKey}
|
||||
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
|
||||
readonly={isReadonly}
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
{object}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -312,6 +312,7 @@ export async function getAttributeEditor (
|
||||
_class: Ref<Class<Obj>>,
|
||||
key: KeyedAttribute | string
|
||||
): Promise<AnySvelteComponent | undefined> {
|
||||
console.log('get attribute editor', _class, key)
|
||||
const hierarchy = client.getHierarchy()
|
||||
const attribute = typeof key === 'string' ? hierarchy.getAttribute(_class, key) : key.attr
|
||||
const presenterClass = attribute !== undefined ? getAttributePresenterClass(hierarchy, attribute) : undefined
|
||||
@ -320,7 +321,6 @@ export async function getAttributeEditor (
|
||||
return
|
||||
}
|
||||
|
||||
const typeClass = hierarchy.getClass(presenterClass.attrClass)
|
||||
let mixin: Ref<Mixin<AttributeEditor>>
|
||||
|
||||
switch (presenterClass.category) {
|
||||
@ -337,16 +337,9 @@ export async function getAttributeEditor (
|
||||
}
|
||||
}
|
||||
|
||||
let editorMixin = hierarchy.as(typeClass, mixin)
|
||||
let parent = typeClass.extends
|
||||
const editorMixin = hierarchy.classHierarchyMixin(presenterClass.attrClass, mixin)
|
||||
|
||||
while (editorMixin.inlineEditor === undefined && parent !== undefined) {
|
||||
const parentClass = hierarchy.getClass(parent)
|
||||
editorMixin = hierarchy.as(parentClass, mixin)
|
||||
parent = parentClass.extends
|
||||
}
|
||||
|
||||
if (editorMixin.inlineEditor === undefined) {
|
||||
if (editorMixin?.inlineEditor === undefined) {
|
||||
// if (presenterClass.category === 'array') {
|
||||
// // NOTE: Don't show error for array attributes for compatibility with previous implementation
|
||||
// } else {
|
||||
|
@ -13,32 +13,32 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import activity, { TxViewlet, ActivityFilter } from '@hcengineering/activity'
|
||||
import activity, { ActivityFilter, TxViewlet } from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import core, { Class, Doc, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import notification, { LastView } from '@hcengineering/notification'
|
||||
import { getResource, IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import notification from '@hcengineering/notification'
|
||||
import {
|
||||
ActionIcon,
|
||||
Component,
|
||||
eventToHTMLElement,
|
||||
Grid,
|
||||
Icon,
|
||||
IconActivity,
|
||||
Label,
|
||||
Scroller,
|
||||
Icon,
|
||||
showPopup,
|
||||
Spinner,
|
||||
ActionIcon,
|
||||
eventToHTMLElement
|
||||
Spinner
|
||||
} from '@hcengineering/ui'
|
||||
import { ActivityKey, activityKey, DisplayTx, newActivity } from '../activity'
|
||||
import TxView from './TxView.svelte'
|
||||
import { filterCollectionTxes } from '../utils'
|
||||
import { Writable } from 'svelte/store'
|
||||
import { ActivityKey, activityKey, DisplayTx, newActivity } from '../activity'
|
||||
import activityPlg from '../plugin'
|
||||
import { filterCollectionTxes } from '../utils'
|
||||
import FilterPopup from './FilterPopup.svelte'
|
||||
import IconFilter from './icons/Filter.svelte'
|
||||
import IconClose from './icons/Close.svelte'
|
||||
import IconFilter from './icons/Filter.svelte'
|
||||
import TxView from './TxView.svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let integrate: boolean = false
|
||||
@ -71,7 +71,7 @@
|
||||
getResource(notification.function.GetNotificationClient).then((res) => {
|
||||
lastViews = res().getLastViews()
|
||||
})
|
||||
let lastViews: Writable<Map<Ref<Doc>, number>> | undefined
|
||||
let lastViews: Writable<LastView> | undefined
|
||||
|
||||
let viewlets: Map<ActivityKey, TxViewlet>
|
||||
|
||||
@ -112,8 +112,8 @@
|
||||
|
||||
$: newTxPos = newTx(filtered, $lastViews)
|
||||
|
||||
function newTx (txes: DisplayTx[], lastViews: Map<Ref<Doc>, number> | undefined): number {
|
||||
const lastView = lastViews?.get(object._id)
|
||||
function newTx (txes: DisplayTx[], lastViews: LastView | undefined): number {
|
||||
const lastView = (lastViews as any)?.[object._id]
|
||||
if (lastView === undefined || lastView === -1) return -1
|
||||
for (let index = 0; index < txes.length; index++) {
|
||||
const tx = txes[index]
|
||||
|
@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import notification from '@hcengineering/notification'
|
||||
import { State } from '@hcengineering/task'
|
||||
import { Button, Component, getEventPositionElement, getPlatformColor, IconMoreV, showPopup } from '@hcengineering/ui'
|
||||
import { Button, getEventPositionElement, getPlatformColor, IconMoreV, showPopup } from '@hcengineering/ui'
|
||||
import { ContextMenu } from '@hcengineering/view-resources'
|
||||
export let state: State
|
||||
|
||||
@ -16,7 +15,6 @@
|
||||
<div class="flex-between h-full font-medium pr-2 pl-4">
|
||||
<span class="lines-limit-2">{state.title}</span>
|
||||
<div class="flex">
|
||||
<Component is={notification.component.LastViewEditor} props={{ value: state }} />
|
||||
<Button icon={IconMoreV} kind="transparent" on:click={showMenu} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
const lastViews = notificationClient.getLastViews()
|
||||
$: lastView = $lastViews.get(object._id)
|
||||
$: lastView = $lastViews[object._id]
|
||||
$: subscribed = lastView !== undefined && lastView !== -1
|
||||
</script>
|
||||
|
||||
|
@ -15,7 +15,8 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import type { ChunterMessage, Message } from '@hcengineering/chunter'
|
||||
import core, { Doc, Ref, Space, Timestamp, WithLookup } from '@hcengineering/core'
|
||||
import core, { Ref, Space, Timestamp, WithLookup } from '@hcengineering/core'
|
||||
import { LastView } from '@hcengineering/notification'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { location as locationStore } from '@hcengineering/ui'
|
||||
@ -103,7 +104,7 @@
|
||||
|
||||
function newMessagesStart (messages: Message[]): number {
|
||||
if (space === undefined) return -1
|
||||
const lastView = $lastViews.get(space)
|
||||
const lastView = ($lastViews as any)[space]
|
||||
if (lastView === undefined || lastView === -1) return -1
|
||||
for (let index = 0; index < messages.length; index++) {
|
||||
const message = messages[index]
|
||||
@ -113,7 +114,7 @@
|
||||
}
|
||||
|
||||
$: markUnread($lastViews)
|
||||
function markUnread (lastViews: Map<Ref<Doc>, number>) {
|
||||
function markUnread (lastViews: LastView) {
|
||||
if (messages === undefined) return
|
||||
const newPos = newMessagesStart(messages)
|
||||
if (newPos !== -1 || newMessagesPos === -1) {
|
||||
|
@ -15,9 +15,8 @@
|
||||
<script lang="ts">
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import { ChunterMessage, Message, ChunterSpace } from '@hcengineering/chunter'
|
||||
import { ChunterMessage, ChunterSpace, Message } from '@hcengineering/chunter'
|
||||
import { generateId, getCurrentAccount, Ref, Space } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { location, navigate } from '@hcengineering/ui'
|
||||
import { get } from 'svelte/store'
|
||||
@ -51,22 +50,6 @@
|
||||
},
|
||||
_id
|
||||
)
|
||||
if (
|
||||
chunterSpace._class === chunter.class.DirectMessage &&
|
||||
!chunterSpace.lastMessage &&
|
||||
chunterSpace.members.length !== 1
|
||||
) {
|
||||
await Promise.all(
|
||||
chunterSpace.members
|
||||
.filter((accId) => accId !== me)
|
||||
.map((accId) =>
|
||||
client.addCollection(notification.class.LastView, space, space, chunterSpace._class, 'lastViews', {
|
||||
user: accId,
|
||||
lastView: 0
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, space, chunter.class.ChunterSpace, _id, message)
|
||||
|
@ -17,12 +17,10 @@
|
||||
import { AttachmentList, AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import type { ChunterMessage, Message, Reaction } from '@hcengineering/chunter'
|
||||
import { EmployeeAccount } from '@hcengineering/contact'
|
||||
import { employeeByIdStore, EmployeePresenter } from '@hcengineering/contact-resources'
|
||||
import { Avatar, employeeByIdStore, EmployeePresenter } from '@hcengineering/contact-resources'
|
||||
import { getCurrentAccount, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient, MessageViewer } from '@hcengineering/presentation'
|
||||
import { Avatar } from '@hcengineering/contact-resources'
|
||||
import { EmojiPopup } from '@hcengineering/text-editor'
|
||||
import ui, { ActionIcon, Button, IconMoreH, Label, showPopup, tooltip } from '@hcengineering/ui'
|
||||
import { Action } from '@hcengineering/view'
|
||||
@ -32,6 +30,7 @@
|
||||
import chunter from '../plugin'
|
||||
import { getTime } from '../utils'
|
||||
// import Share from './icons/Share.svelte'
|
||||
import notification, { Collaborators } from '@hcengineering/notification'
|
||||
import Bookmark from './icons/Bookmark.svelte'
|
||||
import Emoji from './icons/Emoji.svelte'
|
||||
import Thread from './icons/Thread.svelte'
|
||||
@ -52,13 +51,15 @@
|
||||
$: attachments = (message.$lookup?.attachments ?? []) as Attachment[]
|
||||
|
||||
const client = getClient()
|
||||
const hieararchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
const me = getCurrentAccount()._id
|
||||
|
||||
$: reactions = message.$lookup?.reactions as Reaction[] | undefined
|
||||
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
const lastViews = notificationClient.getLastViews()
|
||||
$: subscribed = ($lastViews.get(message._id) ?? -1) > -1
|
||||
$: subscribed = (
|
||||
hieararchy.as(message, notification.mixin.Collaborators) as any as Collaborators
|
||||
).collaborators?.includes(me)
|
||||
$: subscribeAction = subscribed
|
||||
? ({
|
||||
label: chunter.string.TurnOffReplies,
|
||||
|
@ -149,7 +149,7 @@
|
||||
await client.tx(tx)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, parent.space, chunter.class.ChunterSpace, commentId, message)
|
||||
await createBacklinks(client, parent._id, parent._class, commentId, message)
|
||||
|
||||
commentId = generateId()
|
||||
loading = false
|
||||
|
@ -16,7 +16,8 @@
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
|
||||
import type { ChunterMessage, Message, ThreadMessage } from '@hcengineering/chunter'
|
||||
import core, { Doc, generateId, getCurrentAccount, Ref, Space } from '@hcengineering/core'
|
||||
import core, { generateId, getCurrentAccount, Ref, Space } from '@hcengineering/core'
|
||||
import { LastView } from '@hcengineering/notification'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { getCurrentLocation, IconClose, Label, navigate } from '@hcengineering/ui'
|
||||
@ -150,7 +151,7 @@
|
||||
)
|
||||
|
||||
// Create an backlink to document
|
||||
await createBacklinks(client, currentSpace, chunter.class.ChunterSpace, commentId, message)
|
||||
await createBacklinks(client, _id, chunter.class.Message, commentId, message)
|
||||
|
||||
commentId = generateId()
|
||||
isScrollForced = true
|
||||
@ -158,8 +159,8 @@
|
||||
}
|
||||
let comments: ThreadMessage[] = []
|
||||
|
||||
function newMessagesStart (comments: ThreadMessage[], lastViews: Map<Ref<Doc>, number>): number {
|
||||
const lastView = lastViews.get(_id)
|
||||
function newMessagesStart (comments: ThreadMessage[], lastViews: LastView): number {
|
||||
const lastView = (lastViews as any)[_id]
|
||||
if (lastView === undefined || lastView === -1) return -1
|
||||
for (let index = 0; index < comments.length; index++) {
|
||||
const comment = comments[index]
|
||||
@ -169,7 +170,7 @@
|
||||
}
|
||||
|
||||
$: markUnread($lastViews)
|
||||
function markUnread (lastViews: Map<Ref<Doc>, number>) {
|
||||
function markUnread (lastViews: LastView) {
|
||||
const newPos = newMessagesStart(comments, lastViews)
|
||||
if (newPos !== -1 || newMessagesPos === -1) {
|
||||
newMessagesPos = newPos
|
||||
|
@ -22,7 +22,7 @@ import chunter, {
|
||||
Message,
|
||||
ThreadMessage
|
||||
} from '@hcengineering/chunter'
|
||||
import core, { Data, Doc, DocumentQuery, Ref, RelatedDocument, Space } from '@hcengineering/core'
|
||||
import core, { Data, Doc, DocumentQuery, getCurrentAccount, Ref, RelatedDocument, Space } from '@hcengineering/core'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { IntlString, Resources, translate } from '@hcengineering/platform'
|
||||
import preference from '@hcengineering/preference'
|
||||
@ -55,6 +55,7 @@ import { get, writable } from 'svelte/store'
|
||||
import { DisplayTx } from '../../activity/lib'
|
||||
import { updateBacklinksList } from './backlinks'
|
||||
import { getDmName, getTitle, getLink, resolveLocation } from './utils'
|
||||
import notification from '@hcengineering/notification'
|
||||
|
||||
export { default as Header } from './components/Header.svelte'
|
||||
export { classIcon } from './utils'
|
||||
@ -72,20 +73,53 @@ async function MarkCommentUnread (object: ThreadMessage): Promise<void> {
|
||||
|
||||
async function SubscribeMessage (object: Message): Promise<void> {
|
||||
const client = getClient()
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
if (client.getHierarchy().isDerived(object._class, chunter.class.ThreadMessage)) {
|
||||
await notificationClient.updateLastView(object.attachedTo, object.attachedToClass, undefined, true)
|
||||
const acc = getCurrentAccount()
|
||||
const hierarchy = client.getHierarchy()
|
||||
if (hierarchy.isDerived(object._class, chunter.class.ThreadMessage)) {
|
||||
await client.updateMixin(
|
||||
object.attachedTo,
|
||||
object.attachedToClass,
|
||||
object.space,
|
||||
notification.mixin.Collaborators,
|
||||
{
|
||||
$push: {
|
||||
collaborators: acc._id
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
await notificationClient.updateLastView(object._id, object._class, undefined, true)
|
||||
await client.updateMixin(object._id, object._class, object.space, notification.mixin.Collaborators, {
|
||||
$push: {
|
||||
collaborators: acc._id
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function UnsubscribeMessage (object: Message): Promise<void> {
|
||||
async function UnsubscribeMessage (object: ChunterMessage): Promise<void> {
|
||||
const client = getClient()
|
||||
const acc = getCurrentAccount()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
if (client.getHierarchy().isDerived(object._class, chunter.class.ThreadMessage)) {
|
||||
if (hierarchy.isDerived(object._class, chunter.class.ThreadMessage)) {
|
||||
await client.updateMixin(
|
||||
object.attachedTo,
|
||||
object.attachedToClass,
|
||||
object.space,
|
||||
notification.mixin.Collaborators,
|
||||
{
|
||||
$pull: {
|
||||
collaborators: acc._id
|
||||
}
|
||||
}
|
||||
)
|
||||
await notificationClient.unsubscribe(object.attachedTo)
|
||||
} else {
|
||||
await client.updateMixin(object._id, object._class, object.space, notification.mixin.Collaborators, {
|
||||
$pull: {
|
||||
collaborators: acc._id
|
||||
}
|
||||
})
|
||||
await notificationClient.unsubscribe(object._id)
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@
|
||||
"@hcengineering/ui": "^0.6.3",
|
||||
"@hcengineering/setting": "^0.6.2",
|
||||
"@hcengineering/presentation": "^0.6.2",
|
||||
"@hcengineering/notification": "^0.6.7",
|
||||
"@hcengineering/core": "^0.6.21",
|
||||
"@hcengineering/view": "^0.6.2",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
|
@ -16,7 +16,8 @@
|
||||
<script lang="ts">
|
||||
import type { Channel, ChannelProvider } from '@hcengineering/contact'
|
||||
import contact from '@hcengineering/contact'
|
||||
import type { AttachedData, Doc, Ref, Timestamp } from '@hcengineering/core'
|
||||
import type { AttachedData, Doc, Ref } from '@hcengineering/core'
|
||||
import { LastView } from '@hcengineering/notification'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
@ -69,7 +70,7 @@
|
||||
function getProvider (
|
||||
item: AttachedData<Channel>,
|
||||
map: Map<Ref<ChannelProvider>, ChannelProvider>,
|
||||
lastViews: Map<Ref<Doc>, Timestamp>
|
||||
lastViews: LastView
|
||||
): Item | undefined {
|
||||
const provider = map.get(item.provider)
|
||||
if (provider) {
|
||||
@ -91,13 +92,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function isNew (item: Channel, lastViews: Map<Ref<Doc>, Timestamp>): boolean {
|
||||
function isNew (item: Channel, lastViews: LastView): boolean {
|
||||
if (item.lastMessage === undefined) return false
|
||||
const lastView = (item as Channel)._id !== undefined ? lastViews.get((item as Channel)._id) : undefined
|
||||
const lastView = (item as Channel)._id !== undefined ? lastViews[(item as Channel)._id] : undefined
|
||||
return lastView ? lastView < item.lastMessage : (item.items ?? 0) > 0
|
||||
}
|
||||
|
||||
async function update (value: AttachedData<Channel>[] | Channel | null, lastViews: Map<Ref<Doc>, Timestamp>) {
|
||||
async function update (value: AttachedData<Channel>[] | Channel | null, lastViews: LastView) {
|
||||
if (value == null) {
|
||||
displayItems = []
|
||||
return
|
||||
|
@ -15,14 +15,15 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Channel, ChannelProvider } from '@hcengineering/contact'
|
||||
import type { AttachedData, Doc, Ref, Timestamp } from '@hcengineering/core'
|
||||
import type { AttachedData, Doc, Ref } from '@hcengineering/core'
|
||||
import { LastView } from '@hcengineering/notification'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import type { AnyComponent } from '@hcengineering/ui'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { Button } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getChannelProviders } from '../utils'
|
||||
import ChannelsPopup from './ChannelsPopup.svelte'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
|
||||
export let value: AttachedData<Channel>[] | Channel | null
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'large'
|
||||
@ -46,7 +47,7 @@
|
||||
function getProvider (
|
||||
item: AttachedData<Channel>,
|
||||
map: Map<Ref<ChannelProvider>, ChannelProvider>,
|
||||
lastViews: Map<Ref<Doc>, Timestamp>
|
||||
lastViews: LastView
|
||||
): any | undefined {
|
||||
const provider = map.get(item.provider)
|
||||
if (provider) {
|
||||
@ -64,13 +65,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function isNew (item: Channel, lastViews: Map<Ref<Doc>, Timestamp>): boolean {
|
||||
function isNew (item: Channel, lastViews: LastView): boolean {
|
||||
if (item.lastMessage === undefined) return false
|
||||
const lastView = (item as Channel)._id !== undefined ? lastViews.get((item as Channel)._id) : undefined
|
||||
const lastView = (item as Channel)._id !== undefined ? lastViews[(item as Channel)._id] : undefined
|
||||
return lastView ? lastView < item.lastMessage : (item.items ?? 0) > 0
|
||||
}
|
||||
|
||||
async function update (value: AttachedData<Channel>[] | Channel | null, lastViews: Map<Ref<Doc>, Timestamp>) {
|
||||
async function update (value: AttachedData<Channel>[] | Channel | null, lastViews: LastView) {
|
||||
if (value === null) {
|
||||
displayItems = []
|
||||
return
|
||||
|
@ -18,12 +18,12 @@
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { Button, ButtonKind, ButtonSize, Label, showPopup, TooltipAlignment } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import Members from './icons/Members.svelte'
|
||||
import UsersPopup from './UsersPopup.svelte'
|
||||
import UserInfo from './UserInfo.svelte'
|
||||
import CombineAvatars from './CombineAvatars.svelte'
|
||||
import plugin from '../plugin'
|
||||
import { employeeByIdStore } from '../utils'
|
||||
import CombineAvatars from './CombineAvatars.svelte'
|
||||
import Members from './icons/Members.svelte'
|
||||
import UserInfo from './UserInfo.svelte'
|
||||
import UsersPopup from './UsersPopup.svelte'
|
||||
|
||||
export let items: Ref<Employee>[] = []
|
||||
export let _class: Ref<Class<Employee>> = contact.class.Employee
|
||||
@ -40,13 +40,8 @@
|
||||
export let emptyLabel = plugin.string.Members
|
||||
export let readonly: boolean = false
|
||||
|
||||
let persons: Employee[] = []
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: query.query<Employee>(_class, { _id: { $in: items } }, (result) => {
|
||||
persons = result
|
||||
})
|
||||
let persons: Employee[] = items.map((p) => $employeeByIdStore.get(p)).filter((p) => p !== undefined) as Employee[]
|
||||
$: persons = items.map((p) => $employeeByIdStore.get(p)).filter((p) => p !== undefined) as Employee[]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
"Remove": "Delete notification",
|
||||
"RemoveAll": "Delete all notifications",
|
||||
"MarkAllAsRead": "Mark all notifications as read",
|
||||
"MarkAsRead": "Mark as read"
|
||||
"MarkAsRead": "Mark as read",
|
||||
"Collaborators": "Collaborators"
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
"Remove": "Удалить нотификацию",
|
||||
"RemoveAll": "Удалить все нотификации",
|
||||
"MarkAllAsRead": "Отметить все нотификации как прочитанные",
|
||||
"MarkAsRead": "Отметить нотификация прочитанной"
|
||||
"MarkAsRead": "Отметить нотификация прочитанной",
|
||||
"Collaborators": "Участники"
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,7 @@
|
||||
}
|
||||
}
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let clearTimer: number | undefined
|
||||
|
||||
@ -136,7 +137,7 @@
|
||||
alreadyShown.clear()
|
||||
}, 5000)
|
||||
|
||||
const lastView = $lastViews.get(lastViewId)
|
||||
const lastView = ($lastViews as any)[lastViewId]
|
||||
if ((lastView ?? notifyInstance.modifiedOn) > 0) {
|
||||
await notificationClient.updateLastView(
|
||||
lastViewId,
|
||||
@ -154,16 +155,10 @@
|
||||
})
|
||||
|
||||
notification.onclick = () => {
|
||||
if (notifyInstance.action !== undefined) {
|
||||
showPanel(
|
||||
notifyInstance.action.component,
|
||||
notifyInstance.action.objectId,
|
||||
notifyInstance.action.objectClass,
|
||||
'content'
|
||||
)
|
||||
} else {
|
||||
showPanel(view.component.EditDoc, notifyInstance.attachedTo, notifyInstance.attachedToClass, 'content')
|
||||
}
|
||||
const targetClass = hierarchy.getClass(notifyInstance.attachedToClass)
|
||||
const panelComponent = hierarchy.as(targetClass, view.mixin.ObjectPanel)
|
||||
const component = panelComponent.component ?? view.component.EditDoc
|
||||
showPanel(component, notifyInstance.attachedTo, notifyInstance.attachedToClass, 'content')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,39 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2022 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Doc } from '@hcengineering/core'
|
||||
import { Button, tooltip } from '@hcengineering/ui'
|
||||
import notification from '../plugin'
|
||||
import { NotificationClientImpl } from '../utils'
|
||||
|
||||
export let value: Doc
|
||||
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
const lastViews = notificationClient.getLastViews()
|
||||
$: lastView = $lastViews.get(value._id)
|
||||
$: subscribed = lastView !== undefined && lastView !== -1
|
||||
</script>
|
||||
|
||||
<div use:tooltip={{ label: subscribed ? notification.string.DontTrack : notification.string.Track }}>
|
||||
<Button
|
||||
size={'medium'}
|
||||
kind={'transparent'}
|
||||
icon={subscribed ? notification.icon.Track : notification.icon.DontTrack}
|
||||
on:click={() => {
|
||||
if (subscribed) notificationClient.unsubscribe(value._id)
|
||||
else notificationClient.updateLastView(value._id, value._class, undefined, true)
|
||||
}}
|
||||
/>
|
||||
</div>
|
@ -23,7 +23,7 @@
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
const lastViews = notificationClient.getLastViews()
|
||||
|
||||
$: lastView = $lastViews.get(value._id)
|
||||
$: lastView = ($lastViews as any)[value._id]
|
||||
$: hasNotification = lastView !== undefined && lastView !== -1 && lastView < value.modifiedOn
|
||||
</script>
|
||||
|
||||
|
@ -18,7 +18,6 @@ import { Resources } from '@hcengineering/platform'
|
||||
import NotificationsPopup from './components/NotificationsPopup.svelte'
|
||||
import NotificationSettings from './components/NotificationSettings.svelte'
|
||||
import NotificationPresenter from './components/NotificationPresenter.svelte'
|
||||
import LastViewEditor from './components/LastViewEditor.svelte'
|
||||
import { NotificationClientImpl } from './utils'
|
||||
|
||||
export * from './utils'
|
||||
@ -29,8 +28,7 @@ export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
NotificationsPopup,
|
||||
NotificationPresenter,
|
||||
NotificationSettings,
|
||||
LastViewEditor
|
||||
NotificationSettings
|
||||
},
|
||||
function: {
|
||||
GetNotificationClient: NotificationClientImpl.getClient
|
||||
|
@ -14,31 +14,33 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { Class, Doc, getCurrentAccount, Ref, Timestamp } from '@hcengineering/core'
|
||||
import core, { Account, Class, Doc, getCurrentAccount, Ref, Timestamp } from '@hcengineering/core'
|
||||
import notification, { LastView, NotificationClient } from '@hcengineering/notification'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { writable, Writable } from 'svelte/store'
|
||||
import { get, writable, Writable } from 'svelte/store'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class NotificationClientImpl implements NotificationClient {
|
||||
protected static _instance: NotificationClientImpl | undefined = undefined
|
||||
private lastViews = new Map<Ref<Doc>, LastView>()
|
||||
private readonly lastViewsStore = writable(new Map<Ref<Doc>, Timestamp>())
|
||||
private readonly lastViewsStore = writable<LastView>()
|
||||
|
||||
private readonly lastViewQuery = createQuery()
|
||||
private readonly user: Ref<Account>
|
||||
|
||||
private constructor () {
|
||||
this.lastViewQuery.query(notification.class.LastView, { user: getCurrentAccount()._id }, (result) => {
|
||||
const res: Map<Ref<Doc>, Timestamp> = new Map<Ref<Doc>, Timestamp>()
|
||||
const lastViews: Map<Ref<Doc>, LastView> = new Map<Ref<Doc>, LastView>()
|
||||
result.forEach((p) => {
|
||||
res.set(p.attachedTo, p.lastView)
|
||||
lastViews.set(p.attachedTo, p)
|
||||
})
|
||||
this.lastViews = lastViews
|
||||
this.lastViewsStore.set(res)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -53,7 +55,7 @@ export class NotificationClientImpl implements NotificationClient {
|
||||
return NotificationClientImpl._instance
|
||||
}
|
||||
|
||||
getLastViews (): Writable<Map<Ref<Doc>, Timestamp>> {
|
||||
getLastViews (): Writable<LastView> {
|
||||
return this.lastViewsStore
|
||||
}
|
||||
|
||||
@ -64,39 +66,35 @@ export class NotificationClientImpl implements NotificationClient {
|
||||
force: boolean = false
|
||||
): Promise<void> {
|
||||
const client = getClient()
|
||||
const user = getCurrentAccount()._id
|
||||
const hierarchy = client.getHierarchy()
|
||||
const mixin = hierarchy.classHierarchyMixin(_class, notification.mixin.TrackedDoc)
|
||||
if (mixin === undefined) return
|
||||
const lastView = time ?? new Date().getTime()
|
||||
const current = this.lastViews.get(_id)
|
||||
if (current !== undefined) {
|
||||
if (current.lastView === -1 && !force) return
|
||||
if (current.lastView < lastView || force) {
|
||||
const u = client.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
|
||||
lastView
|
||||
})
|
||||
u.space = core.space.DerivedTx
|
||||
await client.tx(u)
|
||||
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)
|
||||
}
|
||||
}
|
||||
} else if (force) {
|
||||
const u = client.txFactory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, {
|
||||
user,
|
||||
lastView,
|
||||
attachedTo: _id,
|
||||
attachedToClass: _class,
|
||||
collection: 'lastViews'
|
||||
})
|
||||
u.space = core.space.DerivedTx
|
||||
await client.tx(u)
|
||||
}
|
||||
}
|
||||
|
||||
async unsubscribe (_id: Ref<Doc>): Promise<void> {
|
||||
const client = getClient()
|
||||
const user = getCurrentAccount()._id
|
||||
const current = await client.findOne(notification.class.LastView, { attachedTo: _id, user })
|
||||
if (current !== undefined) {
|
||||
await client.updateDoc(current._class, current.space, current._id, {
|
||||
lastView: -1
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,29 +13,20 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Account, AttachedDoc, Class, Doc, Mixin, Ref, Space, Timestamp, TxCUD } from '@hcengineering/core'
|
||||
import { Account, AttachedDoc, Class, Doc, Mixin, Ref, Space, Timestamp, TxCUD } from '@hcengineering/core'
|
||||
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { IntegrationType } from '@hcengineering/setting'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Writable } from './types'
|
||||
import { IntegrationType } from '@hcengineering/setting'
|
||||
export * from './types'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface LastView extends AttachedDoc {
|
||||
lastView: Timestamp
|
||||
export interface LastView extends Doc {
|
||||
user: Ref<Account>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface NotificationAction {
|
||||
component: AnyComponent
|
||||
objectId: Ref<Doc>
|
||||
objectClass: Ref<Class<Doc>>
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,9 +37,6 @@ export interface Notification extends AttachedDoc {
|
||||
status: NotificationStatus
|
||||
text: string
|
||||
type: Ref<NotificationType>
|
||||
|
||||
// Defined to open particular item if required.
|
||||
action?: NotificationAction
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,13 +99,27 @@ export interface SpaceLastEdit extends Class<Doc> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface LastViewAttached extends Class<AttachedDoc> {}
|
||||
export interface AnotherUserNotifications extends Class<Doc> {
|
||||
fields: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface AnotherUserNotifications extends Class<Doc> {
|
||||
fields: string[]
|
||||
export interface ClassCollaborators extends Class<Doc> {
|
||||
fields: string[] // Ref<Account> | Ref<Employee> | Ref<Account>[] | Ref<Employee>[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TrackedDoc extends Class<Doc> {}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Collaborators extends Doc {
|
||||
collaborators: Ref<Account>[]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,7 +131,7 @@ export const notificationId = 'notification' as Plugin
|
||||
* @public
|
||||
*/
|
||||
export interface NotificationClient {
|
||||
getLastViews: () => Writable<Map<Ref<Doc>, Timestamp>>
|
||||
getLastViews: () => Writable<LastView>
|
||||
updateLastView: (_id: Ref<Doc>, _class: Ref<Class<Doc>>, time?: Timestamp, force?: boolean) => Promise<void>
|
||||
unsubscribe: (_id: Ref<Doc>) => Promise<void>
|
||||
}
|
||||
@ -146,7 +148,9 @@ const notification = plugin(notificationId, {
|
||||
mixin: {
|
||||
SpaceLastEdit: '' as Ref<Mixin<SpaceLastEdit>>,
|
||||
AnotherUserNotifications: '' as Ref<Mixin<AnotherUserNotifications>>,
|
||||
LastViewAttached: '' as Ref<Mixin<LastViewAttached>>
|
||||
ClassCollaborators: '' as Ref<Mixin<ClassCollaborators>>,
|
||||
Collaborators: '' as Ref<Mixin<Collaborators>>,
|
||||
TrackedDoc: '' as Ref<Mixin<TrackedDoc>>
|
||||
},
|
||||
class: {
|
||||
LastView: '' as Ref<Class<LastView>>,
|
||||
@ -159,6 +163,7 @@ const notification = plugin(notificationId, {
|
||||
ids: {
|
||||
MentionNotification: '' as Ref<NotificationType>,
|
||||
DMNotification: '' as Ref<NotificationType>,
|
||||
CollaboratorNotification: '' as Ref<NotificationType>,
|
||||
PlatformNotification: '' as Ref<NotificationProvider>,
|
||||
BrowserNotification: '' as Ref<NotificationProvider>,
|
||||
EmailNotification: '' as Ref<NotificationProvider>,
|
||||
@ -169,8 +174,7 @@ const notification = plugin(notificationId, {
|
||||
},
|
||||
component: {
|
||||
NotificationsPopup: '' as AnyComponent,
|
||||
NotificationPresenter: '' as AnyComponent,
|
||||
LastViewEditor: '' as AnyComponent
|
||||
NotificationPresenter: '' as AnyComponent
|
||||
},
|
||||
icon: {
|
||||
Notifications: '' as Asset,
|
||||
|
@ -15,15 +15,14 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Attachments } from '@hcengineering/attachment-resources'
|
||||
import type { Ref } from '@hcengineering/core'
|
||||
import core from '@hcengineering/core'
|
||||
import core, { ClassifierKind, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Vacancy } from '@hcengineering/recruit'
|
||||
import { FullDescriptionBox } from '@hcengineering/text-editor'
|
||||
import tracker from '@hcengineering/tracker'
|
||||
import { Button, Component, EditBox, Grid, IconMoreH, showPopup } from '@hcengineering/ui'
|
||||
import { ClassAttributeBar, ContextMenu } from '@hcengineering/view-resources'
|
||||
import { ContextMenu, DocAttributeBar } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import recruit from '../plugin'
|
||||
import VacancyApplications from './VacancyApplications.svelte'
|
||||
@ -60,6 +59,21 @@
|
||||
showPopup(ContextMenu, { object }, (ev as MouseEvent).target as HTMLElement)
|
||||
}
|
||||
}
|
||||
|
||||
const ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
|
||||
const hierarchy = client.getHierarchy()
|
||||
let mixins: Mixin<Doc>[] = []
|
||||
|
||||
function getMixins (object: Doc): void {
|
||||
if (object === undefined) return
|
||||
const descendants = hierarchy.getDescendants(core.class.Doc).map((p) => hierarchy.getClass(p))
|
||||
|
||||
mixins = descendants.filter(
|
||||
(m) => m.kind === ClassifierKind.MIXIN && !ignoreMixins.has(m._id) && hierarchy.hasMixin(object, m._id)
|
||||
)
|
||||
}
|
||||
|
||||
$: getMixins(object)
|
||||
</script>
|
||||
|
||||
{#if object}
|
||||
@ -88,11 +102,10 @@
|
||||
{#if dir === 'column'}
|
||||
<div class="ac-subtitle">
|
||||
<div class="ac-subtitle-content">
|
||||
<ClassAttributeBar
|
||||
<DocAttributeBar
|
||||
{object}
|
||||
_class={object._class}
|
||||
{mixins}
|
||||
ignoreKeys={['name', 'description', 'fullDescription', 'private', 'archived']}
|
||||
to={core.class.Doc}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,7 +57,7 @@
|
||||
$: getMixins(issue, showAllMixins)
|
||||
|
||||
function getMixins (object: Issue, showAllMixins: boolean): void {
|
||||
const descendants = hierarchy.getDescendants(tracker.class.Issue).map((p) => hierarchy.getClass(p))
|
||||
const descendants = hierarchy.getDescendants(core.class.Doc).map((p) => hierarchy.getClass(p))
|
||||
|
||||
mixins = descendants.filter(
|
||||
(m) =>
|
||||
|
@ -14,8 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { EmployeeAccount } from '@hcengineering/contact'
|
||||
import { DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Doc, DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import type { Issue } from '@hcengineering/tracker'
|
||||
@ -36,10 +35,10 @@
|
||||
|
||||
const subscribedQuery = createQuery()
|
||||
$: subscribedQuery.query(
|
||||
notification.class.LastView,
|
||||
{ user: getCurrentAccount()._id, attachedToClass: tracker.class.Issue, lastView: { $ne: -1 } },
|
||||
tracker.class.Issue,
|
||||
{ 'notification:mixin:Collaborators.collaborators': getCurrentAccount()._id },
|
||||
(result) => {
|
||||
const newSub = result.map(({ attachedTo }) => attachedTo as Ref<Issue>)
|
||||
const newSub = result.map((p) => p._id as Ref<Doc> as Ref<Issue>)
|
||||
const curSub = subscribed._id.$in
|
||||
if (curSub.length !== newSub.length || curSub.some((id, i) => newSub[i] !== id)) {
|
||||
subscribed = { _id: { $in: newSub } }
|
||||
|
@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Contact, getName } from '@hcengineering/contact'
|
||||
import { Class, ClassifierKind, Doc, Mixin, Obj, Ref } from '@hcengineering/core'
|
||||
import core, { Class, ClassifierKind, Doc, Mixin, Obj, Ref } from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { Asset, getResource, translate } from '@hcengineering/platform'
|
||||
@ -44,7 +44,6 @@
|
||||
let lastId: Ref<Doc> = _id
|
||||
let lastClass: Ref<Class<Doc>> = _class
|
||||
let object: Doc
|
||||
let parentClass: Ref<Class<Doc>>
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -93,9 +92,9 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function getMixins (parentClass: Ref<Class<Doc>>, object: Doc, showAllMixins: boolean): void {
|
||||
if (object === undefined || parentClass === undefined) return
|
||||
const descendants = hierarchy.getDescendants(parentClass).map((p) => hierarchy.getClass(p))
|
||||
function getMixins (object: Doc, showAllMixins: boolean): void {
|
||||
if (object === undefined) return
|
||||
const descendants = hierarchy.getDescendants(core.class.Doc).map((p) => hierarchy.getClass(p))
|
||||
|
||||
mixins = descendants.filter(
|
||||
(m) =>
|
||||
@ -106,7 +105,7 @@
|
||||
)
|
||||
}
|
||||
|
||||
$: getMixins(parentClass, object, showAllMixins)
|
||||
$: getMixins(object, showAllMixins)
|
||||
|
||||
let ignoreKeys: string[] = []
|
||||
let activityOptions = { enabled: true, showInput: true }
|
||||
@ -172,7 +171,6 @@
|
||||
$: getEditorOrDefault(realObjectClass, _id)
|
||||
|
||||
async function getEditorOrDefault (_class: Ref<Class<Doc>>, _id: Ref<Doc>): Promise<void> {
|
||||
parentClass = hierarchy.getParentClass(_class)
|
||||
await updateKeys()
|
||||
mainEditor = getEditor(_class)
|
||||
}
|
||||
@ -232,10 +230,8 @@
|
||||
}
|
||||
|
||||
async function getHeaderEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent | undefined> {
|
||||
const clazz = hierarchy.getClass(_class)
|
||||
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditorHeader)
|
||||
if (editorMixin.editor != null) return editorMixin.editor
|
||||
if (clazz.extends != null) return getHeaderEditor(clazz.extends)
|
||||
const editorMixin = hierarchy.classHierarchyMixin(_class, view.mixin.ObjectEditorHeader)
|
||||
return editorMixin?.editor
|
||||
}
|
||||
|
||||
let headerEditor: AnyComponent | undefined = undefined
|
||||
@ -265,7 +261,7 @@
|
||||
ignoreMixins = new Set(ev.detail.ignoreMixins)
|
||||
allowedCollections = ev.detail.allowedCollections ?? []
|
||||
collectionArrays = ev.detail.collectionArrays ?? []
|
||||
getMixins(parentClass, object, showAllMixins)
|
||||
getMixins(object, showAllMixins)
|
||||
updateKeys()
|
||||
}
|
||||
</script>
|
||||
|
@ -129,20 +129,13 @@
|
||||
if (result.findIndex((p) => p.value === attribute.name) !== -1) return
|
||||
if (result.findIndex((p) => p.value === value) !== -1) return
|
||||
const { attrClass, category } = getAttributePresenterClass(hierarchy, attribute)
|
||||
const typeClass = hierarchy.getClass(attrClass)
|
||||
const mixin =
|
||||
category === 'object'
|
||||
? view.mixin.ObjectPresenter
|
||||
: category === 'collection'
|
||||
? view.mixin.CollectionPresenter
|
||||
: view.mixin.AttributePresenter
|
||||
let presenter = hierarchy.as(typeClass, mixin).presenter
|
||||
let parent = typeClass.extends
|
||||
while (presenter === undefined && parent !== undefined) {
|
||||
const pclazz = hierarchy.getClass(parent)
|
||||
presenter = hierarchy.as(pclazz, mixin).presenter
|
||||
parent = pclazz.extends
|
||||
}
|
||||
const presenter = hierarchy.classHierarchyMixin(attrClass, mixin)?.presenter
|
||||
if (presenter === undefined) return
|
||||
const clazz = hierarchy.getClass(attribute.attributeOf)
|
||||
|
||||
|
@ -70,13 +70,8 @@ export async function getObjectPresenter (
|
||||
const hierarchy = client.getHierarchy()
|
||||
const mixin = isCollectionAttr ? view.mixin.CollectionPresenter : view.mixin.ObjectPresenter
|
||||
const clazz = hierarchy.getClass(_class)
|
||||
let mixinClazz = hierarchy.getClass(_class)
|
||||
let presenterMixin = hierarchy.as(clazz, mixin)
|
||||
while (presenterMixin.presenter === undefined && mixinClazz.extends !== undefined) {
|
||||
presenterMixin = hierarchy.as(mixinClazz, mixin)
|
||||
mixinClazz = hierarchy.getClass(mixinClazz.extends)
|
||||
}
|
||||
if (presenterMixin.presenter === undefined) {
|
||||
const presenterMixin = hierarchy.classHierarchyMixin(_class, mixin)
|
||||
if (presenterMixin?.presenter === undefined) {
|
||||
throw new Error(
|
||||
`object presenter not found for class=${_class}, mixin=${mixin}, preserve key ${JSON.stringify(preserveKey)}`
|
||||
)
|
||||
@ -141,16 +136,8 @@ async function getAttributePresenter (
|
||||
const presenterClass = getAttributePresenterClass(hierarchy, attribute)
|
||||
const isCollectionAttr = presenterClass.category === 'collection'
|
||||
const mixin = isCollectionAttr ? view.mixin.CollectionPresenter : view.mixin.AttributePresenter
|
||||
const clazz = hierarchy.getClass(presenterClass.attrClass)
|
||||
let presenterMixin = hierarchy.as(clazz, mixin)
|
||||
let parent = clazz.extends
|
||||
while (presenterMixin.presenter === undefined && parent !== undefined) {
|
||||
const pclazz = hierarchy.getClass(parent)
|
||||
presenterClass.attrClass = parent
|
||||
presenterMixin = hierarchy.as(pclazz, mixin)
|
||||
parent = pclazz.extends
|
||||
}
|
||||
if (presenterMixin.presenter === undefined) {
|
||||
const presenterMixin = hierarchy.classHierarchyMixin(presenterClass.attrClass, mixin)
|
||||
if (presenterMixin?.presenter === undefined) {
|
||||
throw new Error('attribute presenter not found for ' + JSON.stringify(preserveKey))
|
||||
}
|
||||
const resultKey = preserveKey.sortingKey ?? preserveKey.key
|
||||
@ -657,14 +644,8 @@ export async function moveToSpace (
|
||||
*/
|
||||
export function getAdditionalHeader (client: TxOperations, _class: Ref<Class<Doc>>): AnyComponent[] | undefined {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const clazz = hierarchy.getClass(_class)
|
||||
let mixinClazz = hierarchy.getClass(_class)
|
||||
let presenterMixin = hierarchy.as(clazz, view.mixin.ListHeaderExtra)
|
||||
while (presenterMixin.presenters === undefined && mixinClazz.extends !== undefined) {
|
||||
presenterMixin = hierarchy.as(mixinClazz, view.mixin.ListHeaderExtra)
|
||||
mixinClazz = hierarchy.getClass(mixinClazz.extends)
|
||||
}
|
||||
return presenterMixin.presenters
|
||||
const presenterMixin = hierarchy.classHierarchyMixin(_class, view.mixin.ListHeaderExtra)
|
||||
return presenterMixin?.presenters
|
||||
}
|
||||
|
||||
export async function getObjectLinkFragment (
|
||||
@ -673,12 +654,7 @@ export async function getObjectLinkFragment (
|
||||
props: Record<string, any> = {},
|
||||
component: AnyComponent = view.component.EditDoc
|
||||
): Promise<Location> {
|
||||
let clazz = hierarchy.getClass(object._class)
|
||||
let provider = hierarchy.as(clazz, view.mixin.LinkProvider)
|
||||
while (provider.encode === undefined && clazz.extends !== undefined) {
|
||||
clazz = hierarchy.getClass(clazz.extends)
|
||||
provider = hierarchy.as(clazz, view.mixin.LinkProvider)
|
||||
}
|
||||
const provider = hierarchy.classHierarchyMixin(object._class, view.mixin.LinkProvider)
|
||||
if (provider?.encode !== undefined) {
|
||||
const f = await getResource(provider.encode)
|
||||
const res = await f(object, props)
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import type { Doc, Ref, Space } from '@hcengineering/core'
|
||||
import core from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import notification, { LastView } from '@hcengineering/notification'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import preference from '@hcengineering/preference'
|
||||
@ -107,9 +107,9 @@
|
||||
$: clazz = hierarchy.getClass(model.spaceClass)
|
||||
$: lastEditMixin = hierarchy.as(clazz, notification.mixin.SpaceLastEdit)
|
||||
|
||||
function isChanged (space: Space, lastViews: Map<Ref<Doc>, number>): boolean {
|
||||
function isChanged (space: Space, lastViews: LastView): boolean {
|
||||
const field = lastEditMixin?.lastEditField
|
||||
const lastView = lastViews.get(space._id)
|
||||
const lastView = lastViews[space._id]
|
||||
if (lastView === undefined || lastView === -1) return false
|
||||
if (field === undefined) return false
|
||||
const value = (space as any)[field]
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import type { Doc, Ref, Space } from '@hcengineering/core'
|
||||
import core from '@hcengineering/core'
|
||||
import notification from '@hcengineering/notification'
|
||||
import notification, { LastView } from '@hcengineering/notification'
|
||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||
import { getResource, IntlString } from '@hcengineering/platform'
|
||||
import preference from '@hcengineering/preference'
|
||||
@ -77,11 +77,11 @@
|
||||
const lastViews = notificationClient.getLastViews()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
function isChanged (space: Space, lastViews: Map<Ref<Doc>, number>): boolean {
|
||||
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.get(space._id)
|
||||
const lastView = lastViews[space._id]
|
||||
if (lastView === undefined || lastView === -1) return false
|
||||
if (field === undefined) return false
|
||||
const value = (space as any)[field]
|
||||
|
@ -166,7 +166,7 @@ async function processRefArrAttribute<T extends Doc> (
|
||||
const res: Tx[] = []
|
||||
if (attr.type._class === core.class.ArrOf) {
|
||||
const arrOf = (attr.type as ArrOf<RefTo<Doc>>).of
|
||||
if (arrOf._class === core.class.ArrOf) {
|
||||
if (arrOf._class === core.class.RefTo) {
|
||||
if (targetClasses.includes((arrOf as RefTo<Doc>).to)) {
|
||||
const docs = await control.findAll(clazz, { [key]: oldValue })
|
||||
for (const doc of docs) {
|
||||
|
@ -18,44 +18,46 @@ import chunter, { Backlink } from '@hcengineering/chunter'
|
||||
import contact, { Employee, EmployeeAccount, formatName } from '@hcengineering/contact'
|
||||
import core, {
|
||||
Account,
|
||||
AnyAttribute,
|
||||
ArrOf,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Data,
|
||||
Doc,
|
||||
generateId,
|
||||
Hierarchy,
|
||||
Obj,
|
||||
IdMap,
|
||||
Ref,
|
||||
RefTo,
|
||||
Space,
|
||||
Timestamp,
|
||||
toIdMap,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxFactory,
|
||||
TxProcessor
|
||||
TxMixin,
|
||||
TxProcessor,
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import notification, {
|
||||
ClassCollaborators,
|
||||
Collaborators,
|
||||
EmailNotification,
|
||||
NotificationAction,
|
||||
Notification,
|
||||
NotificationProvider,
|
||||
NotificationStatus,
|
||||
NotificationType
|
||||
} from '@hcengineering/notification'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import type { TriggerControl } from '@hcengineering/server-core'
|
||||
import serverNotification, {
|
||||
HTMLPresenter,
|
||||
TextPresenter,
|
||||
createLastViewTx,
|
||||
getEmployeeAccount,
|
||||
getEmployeeAccountById,
|
||||
getUpdateLastViewTx,
|
||||
getEmployee
|
||||
HTMLPresenter,
|
||||
TextPresenter
|
||||
} from '@hcengineering/server-notification'
|
||||
import { replaceAll } from './utils'
|
||||
import { Content } from './types'
|
||||
import { replaceAll } from './utils'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -63,16 +65,35 @@ import { Content } from './types'
|
||||
export async function OnBacklinkCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const hierarchy = control.hierarchy
|
||||
const ptx = tx as TxCollectionCUD<Doc, Backlink>
|
||||
let res: Tx[] = []
|
||||
|
||||
if (!checkTx(ptx, hierarchy)) return []
|
||||
|
||||
const receiver = await getEmployeeAccount(ptx.objectId as Ref<Employee>, control)
|
||||
if (receiver === undefined) return []
|
||||
|
||||
const sender = await getEmployeeAccountById(ptx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
const backlink = getBacklink(ptx)
|
||||
const doc = await getBacklinkDoc(backlink, control)
|
||||
return await createNotificationTxes(
|
||||
if (doc !== undefined) {
|
||||
const collab = hierarchy.as(doc, notification.mixin.Collaborators)
|
||||
if (!collab.collaborators.includes(receiver._id)) {
|
||||
const collabTx = control.txFactory.createTxMixin(
|
||||
doc._id,
|
||||
doc._class,
|
||||
doc.space,
|
||||
notification.mixin.Collaborators,
|
||||
{
|
||||
$push: {
|
||||
collaborators: receiver._id
|
||||
}
|
||||
}
|
||||
)
|
||||
res.push(collabTx)
|
||||
}
|
||||
}
|
||||
const notifyTx = await createNotificationTxes(
|
||||
control,
|
||||
ptx,
|
||||
notification.ids.MentionNotification,
|
||||
@ -81,6 +102,8 @@ export async function OnBacklinkCreate (tx: Tx, control: TriggerControl): Promis
|
||||
receiver,
|
||||
backlink.message
|
||||
)
|
||||
res = [...res, ...notifyTx]
|
||||
return res
|
||||
}
|
||||
|
||||
function checkTx (ptx: TxCollectionCUD<Doc, Backlink>, hierarchy: Hierarchy): boolean {
|
||||
@ -142,23 +165,11 @@ async function getHtmlPart (doc: Doc, control: TriggerControl): Promise<string |
|
||||
}
|
||||
|
||||
function getHTMLPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy): HTMLPresenter | undefined {
|
||||
let clazz: Ref<Class<Obj>> | undefined = _class
|
||||
while (clazz !== undefined) {
|
||||
const _class = hierarchy.getClass(clazz)
|
||||
const presenter = hierarchy.as(_class, serverNotification.mixin.HTMLPresenter)
|
||||
if (presenter.presenter != null) return presenter
|
||||
clazz = _class.extends
|
||||
}
|
||||
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.HTMLPresenter)
|
||||
}
|
||||
|
||||
function getTextPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy): TextPresenter | undefined {
|
||||
let clazz: Ref<Class<Obj>> | undefined = _class
|
||||
while (clazz !== undefined) {
|
||||
const _class = hierarchy.getClass(clazz)
|
||||
const presenter = hierarchy.as(_class, serverNotification.mixin.TextPresenter)
|
||||
if (presenter.presenter != null) return presenter
|
||||
clazz = _class.extends
|
||||
}
|
||||
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.TextPresenter)
|
||||
}
|
||||
|
||||
function fillTemplate (template: string, sender: string, doc: string, data: string): string {
|
||||
@ -204,8 +215,7 @@ export async function createNotificationTxes (
|
||||
doc: Doc | undefined,
|
||||
sender: EmployeeAccount,
|
||||
receiver: EmployeeAccount,
|
||||
data: string = '',
|
||||
action?: NotificationAction
|
||||
data: string = ''
|
||||
): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
|
||||
@ -213,22 +223,6 @@ export async function createNotificationTxes (
|
||||
|
||||
const content = await getContent(doc, senderName, type, control, data)
|
||||
|
||||
if (await isAllowed(control, receiver, notification.ids.PlatformNotification)) {
|
||||
const target = await getEmployee(receiver.employee, control)
|
||||
if (target !== undefined) {
|
||||
const createNotificationTx = await getPlatformNotificationTx(
|
||||
ptx,
|
||||
type,
|
||||
control.txFactory,
|
||||
target,
|
||||
content?.text,
|
||||
action
|
||||
)
|
||||
|
||||
res.push(createNotificationTx)
|
||||
}
|
||||
}
|
||||
|
||||
if (content !== undefined && (await isAllowed(control, receiver, notification.ids.EmailNotification))) {
|
||||
const emailTx = await getEmailNotificationTx(ptx, senderName, content.text, content.html, content.subject, receiver)
|
||||
if (emailTx !== undefined) {
|
||||
@ -239,41 +233,6 @@ export async function createNotificationTxes (
|
||||
return res
|
||||
}
|
||||
|
||||
async function getPlatformNotificationTx (
|
||||
ptx: TxCollectionCUD<Doc, AttachedDoc>,
|
||||
type: Ref<NotificationType>,
|
||||
txFactory: TxFactory,
|
||||
target: Employee,
|
||||
text?: string,
|
||||
action?: NotificationAction
|
||||
): Promise<TxCollectionCUD<Doc, Notification>> {
|
||||
const createTx: TxCreateDoc<Notification> = {
|
||||
objectClass: notification.class.Notification,
|
||||
objectSpace: notification.space.Notifications,
|
||||
objectId: generateId(),
|
||||
modifiedOn: ptx.modifiedOn,
|
||||
modifiedBy: ptx.modifiedBy,
|
||||
space: ptx.space,
|
||||
_id: generateId(),
|
||||
_class: core.class.TxCreateDoc,
|
||||
attributes: {
|
||||
tx: ptx._id,
|
||||
status: NotificationStatus.New,
|
||||
type
|
||||
} as unknown as Data<Notification>
|
||||
}
|
||||
|
||||
if (text !== undefined) {
|
||||
createTx.attributes.text = text
|
||||
}
|
||||
|
||||
if (action !== undefined) {
|
||||
createTx.attributes.action = action
|
||||
}
|
||||
|
||||
return txFactory.createTxCollectionCUD(target._class, target._id, target.space, 'notifications', createTx)
|
||||
}
|
||||
|
||||
async function getEmailNotificationTx (
|
||||
ptx: TxCollectionCUD<Doc, AttachedDoc>,
|
||||
sender: string,
|
||||
@ -312,7 +271,7 @@ async function getUpdateLastViewTxes (
|
||||
): Promise<Tx[]> {
|
||||
const updatedUsers: Set<Ref<Account>> = new Set<Ref<Account>>()
|
||||
const result: Tx[] = []
|
||||
const tx = await getUpdateLastViewTx(control.findAll, _id, _class, modifiedOn, user)
|
||||
const tx = await getUpdateLastViewTx(control.findAll, _id, modifiedOn, user)
|
||||
if (tx !== undefined) {
|
||||
updatedUsers.add(user)
|
||||
result.push(tx)
|
||||
@ -323,10 +282,10 @@ async function getUpdateLastViewTxes (
|
||||
const value = (doc as any)[field]
|
||||
if (value != null) {
|
||||
for (const employeeId of Array.isArray(value) ? value : [value]) {
|
||||
const account = (await control.modelDb.findAll(core.class.Account, { employee: employeeId }, { limit: 1 }))[0]
|
||||
const account = await getEmployeeAccount(employeeId, control)
|
||||
if (account !== undefined) {
|
||||
if (updatedUsers.has(account._id)) continue
|
||||
const assigneeTx = await createLastViewTx(control.findAll, _id, _class, account._id)
|
||||
const assigneeTx = await createLastViewTx(control.findAll, _id, account._id)
|
||||
if (assigneeTx !== undefined) {
|
||||
updatedUsers.add(account._id)
|
||||
result.push(assigneeTx)
|
||||
@ -343,7 +302,11 @@ async function getUpdateLastViewTxes (
|
||||
*/
|
||||
export async function UpdateLastView (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (![core.class.TxUpdateDoc, core.class.TxCreateDoc, core.class.TxMixin].includes(actualTx._class)) {
|
||||
if (
|
||||
![core.class.TxUpdateDoc, core.class.TxCreateDoc, core.class.TxMixin, core.class.TxRemoveDoc].includes(
|
||||
actualTx._class
|
||||
)
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
@ -356,21 +319,22 @@ export async function UpdateLastView (tx: Tx, control: TriggerControl): Promise<
|
||||
switch (actualTx._class) {
|
||||
case core.class.TxCreateDoc: {
|
||||
const createTx = actualTx as TxCreateDoc<Doc>
|
||||
if (control.hierarchy.isDerived(createTx.objectClass, notification.class.LastView)) {
|
||||
return []
|
||||
}
|
||||
if (control.hierarchy.isDerived(createTx.objectClass, core.class.AttachedDoc)) {
|
||||
const doc = TxProcessor.createDoc2Doc(createTx as TxCreateDoc<AttachedDoc>)
|
||||
const attachedTxes = await getUpdateLastViewTxes(
|
||||
doc,
|
||||
doc.attachedTo,
|
||||
doc.attachedToClass,
|
||||
createTx.modifiedOn,
|
||||
createTx.modifiedBy,
|
||||
control
|
||||
)
|
||||
const docClass = control.hierarchy.getClass(doc._class)
|
||||
if (!control.hierarchy.hasMixin(docClass, notification.mixin.LastViewAttached)) return attachedTxes
|
||||
if (control.hierarchy.classHierarchyMixin(doc.attachedToClass, notification.mixin.TrackedDoc) !== undefined) {
|
||||
const attachedTxes = await getUpdateLastViewTxes(
|
||||
doc,
|
||||
doc.attachedTo,
|
||||
doc.attachedToClass,
|
||||
createTx.modifiedOn,
|
||||
createTx.modifiedBy,
|
||||
control
|
||||
)
|
||||
result.push(...attachedTxes)
|
||||
}
|
||||
}
|
||||
if (control.hierarchy.classHierarchyMixin(createTx.objectClass, notification.mixin.TrackedDoc) !== undefined) {
|
||||
const doc = TxProcessor.createDoc2Doc(createTx)
|
||||
const parentTxes = await getUpdateLastViewTxes(
|
||||
doc,
|
||||
doc._id,
|
||||
@ -379,21 +343,34 @@ export async function UpdateLastView (tx: Tx, control: TriggerControl): Promise<
|
||||
createTx.modifiedBy,
|
||||
control
|
||||
)
|
||||
return [...attachedTxes, ...parentTxes]
|
||||
} else {
|
||||
const doc = TxProcessor.createDoc2Doc(createTx)
|
||||
return await getUpdateLastViewTxes(doc, doc._id, doc._class, createTx.modifiedOn, createTx.modifiedBy, control)
|
||||
result.push(...parentTxes)
|
||||
}
|
||||
return result
|
||||
}
|
||||
case core.class.TxUpdateDoc:
|
||||
case core.class.TxMixin: {
|
||||
const tx = actualTx as TxCUD<Doc>
|
||||
const doc = (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
||||
if (doc !== undefined) {
|
||||
return await getUpdateLastViewTxes(doc, doc._id, doc._class, tx.modifiedOn, tx.modifiedBy, control)
|
||||
if (control.hierarchy.classHierarchyMixin(tx.objectClass, notification.mixin.TrackedDoc) !== undefined) {
|
||||
const doc = (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
||||
if (doc !== undefined) {
|
||||
return await getUpdateLastViewTxes(doc, doc._id, doc._class, tx.modifiedOn, tx.modifiedBy, control)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case core.class.TxRemoveDoc: {
|
||||
const tx = actualTx as TxCUD<Doc>
|
||||
const lastViews = await control.findAll(notification.class.LastView, { [tx.objectId]: { $exists: true } })
|
||||
for (const lastView of lastViews) {
|
||||
const clearTx = control.txFactory.createTxUpdateDoc(lastView._class, lastView.space, lastView._id, {
|
||||
$unset: {
|
||||
[tx.objectId]: ''
|
||||
}
|
||||
})
|
||||
result.push(clearTx)
|
||||
}
|
||||
return result
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -417,12 +394,241 @@ async function getBacklinkDoc (backlink: Backlink, control: TriggerControl): Pro
|
||||
)[0]
|
||||
}
|
||||
|
||||
async function getValueCollaborators (
|
||||
value: any,
|
||||
attr: AnyAttribute,
|
||||
control: TriggerControl
|
||||
): Promise<EmployeeAccount[]> {
|
||||
const hierarchy = control.hierarchy
|
||||
if (attr.type._class === core.class.RefTo) {
|
||||
const to = (attr.type as RefTo<Doc>).to
|
||||
if (hierarchy.isDerived(to, contact.class.Employee)) {
|
||||
const acc = await getEmployeeAccount(value, control)
|
||||
return acc !== undefined ? [acc] : []
|
||||
} else if (hierarchy.isDerived(to, core.class.Account)) {
|
||||
const acc = await getEmployeeAccountById(value, control)
|
||||
return acc !== undefined ? [acc] : []
|
||||
}
|
||||
} else if (attr.type._class === core.class.ArrOf) {
|
||||
const arrOf = (attr.type as ArrOf<RefTo<Doc>>).of
|
||||
if (arrOf._class === core.class.RefTo) {
|
||||
const to = (arrOf as RefTo<Doc>).to
|
||||
if (hierarchy.isDerived(to, contact.class.Employee)) {
|
||||
const employeeAccounts = await control.modelDb.findAll(contact.class.EmployeeAccount, {
|
||||
employee: { $in: Array.isArray(value) ? value : [value] }
|
||||
})
|
||||
return employeeAccounts
|
||||
} else if (hierarchy.isDerived(to, core.class.Account)) {
|
||||
const employeeAccounts = await control.modelDb.findAll(contact.class.EmployeeAccount, {
|
||||
_id: { $in: Array.isArray(value) ? value : [value] }
|
||||
})
|
||||
return employeeAccounts
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
async function getKeyCollaborators (
|
||||
doc: Doc,
|
||||
value: any,
|
||||
field: string,
|
||||
control: TriggerControl
|
||||
): Promise<EmployeeAccount[] | undefined> {
|
||||
if (value !== undefined && value !== null) {
|
||||
const attr = control.hierarchy.findAttribute(doc._class, field)
|
||||
if (attr !== undefined) {
|
||||
return await getValueCollaborators(value, attr, control)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getDocCollaborators (
|
||||
doc: Doc,
|
||||
mixin: ClassCollaborators,
|
||||
control: TriggerControl
|
||||
): Promise<EmployeeAccount[]> {
|
||||
const collaborators: IdMap<EmployeeAccount> = new Map()
|
||||
for (const field of mixin.fields) {
|
||||
const value = (doc as any)[field]
|
||||
const newCollaborators = await getKeyCollaborators(doc, value, field, control)
|
||||
if (newCollaborators !== undefined) {
|
||||
for (const newCollaborator of newCollaborators) {
|
||||
collaborators.set(newCollaborator._id, newCollaborator)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(collaborators.values())
|
||||
}
|
||||
|
||||
function getMixinTx (
|
||||
actualTx: TxCUD<Doc>,
|
||||
control: TriggerControl,
|
||||
collaborators: EmployeeAccount[]
|
||||
): TxMixin<Doc, Collaborators> {
|
||||
return control.txFactory.createTxMixin(
|
||||
actualTx.objectId,
|
||||
actualTx.objectClass,
|
||||
actualTx.objectSpace,
|
||||
notification.mixin.Collaborators,
|
||||
{
|
||||
collaborators: collaborators.map((p) => p._id)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function CreateCollaboratorDoc (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
const hierarchy = control.hierarchy
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<Doc>
|
||||
|
||||
if (actualTx._class !== core.class.TxCreateDoc) return []
|
||||
const mixin = hierarchy.classHierarchyMixin(actualTx.objectClass, notification.mixin.ClassCollaborators)
|
||||
if (mixin !== undefined) {
|
||||
const doc = TxProcessor.createDoc2Doc(actualTx)
|
||||
const collaborators = await getDocCollaborators(doc, mixin, control)
|
||||
|
||||
const mixinTx = getMixinTx(actualTx, control, collaborators)
|
||||
res.push(mixinTx)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
async function getNewCollaborators (
|
||||
actualTx: TxUpdateDoc<Doc>,
|
||||
mixin: ClassCollaborators,
|
||||
doc: Doc,
|
||||
control: TriggerControl
|
||||
): Promise<EmployeeAccount[]> {
|
||||
const newCollaborators: IdMap<EmployeeAccount> = new Map()
|
||||
if (actualTx.operations.$push !== undefined) {
|
||||
for (const key in actualTx.operations.$push) {
|
||||
if (mixin.fields.includes(key)) {
|
||||
const value = (actualTx.operations.$push as any)[key]
|
||||
const newCollabs = await getKeyCollaborators(doc, value, key, control)
|
||||
if (newCollabs !== undefined) {
|
||||
for (const newCollab of newCollabs) {
|
||||
newCollaborators.set(newCollab._id, newCollab)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key in actualTx.operations) {
|
||||
if (key.startsWith('$')) continue
|
||||
if (mixin.fields.includes(key)) {
|
||||
const value = (actualTx.operations as any)[key]
|
||||
const newCollabs = await getKeyCollaborators(doc, value, key, control)
|
||||
if (newCollabs !== undefined) {
|
||||
for (const newCollab of newCollabs) {
|
||||
newCollaborators.set(newCollab._id, newCollab)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(newCollaborators.values())
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function UpdateCollaboratorDoc (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const hierarchy = control.hierarchy
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxUpdateDoc<Doc>
|
||||
|
||||
if (actualTx._class !== core.class.TxUpdateDoc) return []
|
||||
const clazz = hierarchy.getClass(actualTx.objectClass)
|
||||
if (!hierarchy.hasMixin(clazz, notification.mixin.ClassCollaborators)) return []
|
||||
const mixin = hierarchy.as(clazz, notification.mixin.ClassCollaborators)
|
||||
const doc = (await control.findAll(actualTx.objectClass, { _id: actualTx.objectId }, { limit: 1 }))[0]
|
||||
if (doc === undefined) return []
|
||||
let collaborators: EmployeeAccount[] = []
|
||||
let mixinTx: TxMixin<Doc, Collaborators> | undefined
|
||||
if (hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
|
||||
// we should handle change field and subscribe new collaborators
|
||||
const collabMixin = hierarchy.as(doc, notification.mixin.Collaborators)
|
||||
const oldCollaborators = await control.modelDb.findAll(contact.class.EmployeeAccount, {
|
||||
_id: { $in: collabMixin.collaborators as Ref<EmployeeAccount>[] }
|
||||
})
|
||||
const collabs = toIdMap(oldCollaborators)
|
||||
const newCollaborators = (await getNewCollaborators(actualTx, mixin, doc, control)).filter(
|
||||
(p) => !collabs.has(p._id)
|
||||
)
|
||||
|
||||
if (newCollaborators.length > 0) {
|
||||
mixinTx = control.txFactory.createTxMixin(
|
||||
actualTx.objectId,
|
||||
actualTx.objectClass,
|
||||
actualTx.objectSpace,
|
||||
notification.mixin.Collaborators,
|
||||
{
|
||||
$push: {
|
||||
collaborators: {
|
||||
$each: newCollaborators.map((p) => p._id),
|
||||
$position: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
collaborators = await getDocCollaborators(doc, mixin, control)
|
||||
mixinTx = getMixinTx(actualTx, control, collaborators)
|
||||
}
|
||||
|
||||
return mixinTx !== undefined ? [mixinTx] : []
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
|
||||
export * from './types'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
OnBacklinkCreate,
|
||||
UpdateLastView
|
||||
UpdateLastView,
|
||||
CreateCollaboratorDoc,
|
||||
UpdateCollaboratorDoc,
|
||||
OnAddCollborator
|
||||
}
|
||||
})
|
||||
|
@ -31,7 +31,6 @@ export const serverNotificationId = 'server-notification' as Plugin
|
||||
export async function getUpdateLastViewTx (
|
||||
findAll: TriggerControl['findAll'],
|
||||
attachedTo: Ref<Doc>,
|
||||
attachedToClass: Ref<Class<Doc>>,
|
||||
lastView: number,
|
||||
user: Ref<Account>
|
||||
): Promise<TxUpdateDoc<LastView> | TxCreateDoc<LastView> | undefined> {
|
||||
@ -39,8 +38,6 @@ export async function getUpdateLastViewTx (
|
||||
await findAll(
|
||||
notification.class.LastView,
|
||||
{
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
user
|
||||
},
|
||||
{ limit: 1 }
|
||||
@ -48,21 +45,18 @@ export async function getUpdateLastViewTx (
|
||||
)[0]
|
||||
const factory = new TxFactory(user)
|
||||
if (current !== undefined) {
|
||||
if (current.lastView === -1) {
|
||||
if (current[attachedTo] === -1 || current[attachedTo] >= lastView) {
|
||||
return
|
||||
}
|
||||
const u = factory.createTxUpdateDoc(current._class, current.space, current._id, {
|
||||
lastView
|
||||
[attachedTo]: lastView
|
||||
})
|
||||
u.space = core.space.DerivedTx
|
||||
return u
|
||||
} else {
|
||||
const u = factory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, {
|
||||
user,
|
||||
lastView,
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
collection: 'lastViews'
|
||||
[attachedTo]: lastView
|
||||
})
|
||||
u.space = core.space.DerivedTx
|
||||
return u
|
||||
@ -129,28 +123,28 @@ export async function getEmployee (employee: Ref<Employee>, control: TriggerCont
|
||||
export async function createLastViewTx (
|
||||
findAll: TriggerControl['findAll'],
|
||||
attachedTo: Ref<Doc>,
|
||||
attachedToClass: Ref<Class<Doc>>,
|
||||
user: Ref<Account>
|
||||
): Promise<TxCreateDoc<LastView> | undefined> {
|
||||
): Promise<TxCreateDoc<LastView> | TxUpdateDoc<LastView> | undefined> {
|
||||
const current = (
|
||||
await findAll(
|
||||
notification.class.LastView,
|
||||
{
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
user
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0]
|
||||
const factory = new TxFactory(user)
|
||||
if (current === undefined) {
|
||||
const factory = new TxFactory(user)
|
||||
const u = factory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, {
|
||||
user,
|
||||
lastView: 1,
|
||||
attachedTo,
|
||||
attachedToClass,
|
||||
collection: 'lastViews'
|
||||
[attachedTo]: 1
|
||||
})
|
||||
u.space = core.space.DerivedTx
|
||||
return u
|
||||
} else if (current[attachedTo] === undefined) {
|
||||
const u = factory.createTxUpdateDoc(current._class, current.space, current._id, {
|
||||
[attachedTo]: 1
|
||||
})
|
||||
u.space = core.space.DerivedTx
|
||||
return u
|
||||
@ -186,6 +180,9 @@ export default plugin(serverNotificationId, {
|
||||
},
|
||||
trigger: {
|
||||
OnBacklinkCreate: '' as Resource<TriggerFunc>,
|
||||
UpdateLastView: '' as Resource<TriggerFunc>
|
||||
UpdateLastView: '' as Resource<TriggerFunc>,
|
||||
CreateCollaboratorDoc: '' as Resource<TriggerFunc>,
|
||||
UpdateCollaboratorDoc: '' as Resource<TriggerFunc>,
|
||||
OnAddCollborator: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
|
@ -14,22 +14,12 @@
|
||||
//
|
||||
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import core, {
|
||||
AttachedDoc,
|
||||
concatLink,
|
||||
Doc,
|
||||
Ref,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxProcessor,
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import { NotificationAction } from '@hcengineering/notification'
|
||||
import { getMetadata, Resource } from '@hcengineering/platform'
|
||||
import { AttachedDoc, concatLink, Doc, Ref, Tx, TxCollectionCUD } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import { getEmployeeAccount, getEmployeeAccountById, getUpdateLastViewTx } from '@hcengineering/server-notification'
|
||||
import { getEmployeeAccount, getEmployeeAccountById } from '@hcengineering/server-notification'
|
||||
import { createNotificationTxes } from '@hcengineering/server-notification-resources'
|
||||
import task, { Issue, Task, taskId } from '@hcengineering/task'
|
||||
import task, { Issue, taskId } from '@hcengineering/task'
|
||||
import view from '@hcengineering/view'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
@ -60,8 +50,7 @@ export async function addAssigneeNotification (
|
||||
res: Tx[],
|
||||
issue: Doc,
|
||||
assignee: Ref<Employee>,
|
||||
ptx: TxCollectionCUD<AttachedDoc, AttachedDoc>,
|
||||
component?: Resource<string>
|
||||
ptx: TxCollectionCUD<AttachedDoc, AttachedDoc>
|
||||
): Promise<void> {
|
||||
const sender = await getEmployeeAccountById(ptx.modifiedBy, control)
|
||||
if (sender === undefined) {
|
||||
@ -73,74 +62,11 @@ export async function addAssigneeNotification (
|
||||
return
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
const action: NotificationAction = {
|
||||
component: component ?? view.component.EditDoc,
|
||||
objectId: issue._id,
|
||||
objectClass: issue._class
|
||||
} as NotificationAction
|
||||
|
||||
const result = await createNotificationTxes(
|
||||
control,
|
||||
ptx,
|
||||
task.ids.AssigneedNotification,
|
||||
issue,
|
||||
sender,
|
||||
receiver,
|
||||
undefined,
|
||||
action
|
||||
)
|
||||
const result = await createNotificationTxes(control, ptx, task.ids.AssigneedNotification, issue, sender, receiver)
|
||||
|
||||
res.push(...result)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnTaskUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (actualTx._class !== core.class.TxUpdateDoc) {
|
||||
return []
|
||||
}
|
||||
|
||||
const updateTx = actualTx as TxUpdateDoc<Task>
|
||||
|
||||
if (!control.hierarchy.isDerived(updateTx.objectClass, task.class.Task)) {
|
||||
return []
|
||||
}
|
||||
const txes: Tx[] = []
|
||||
|
||||
const mainTx = await getUpdateLastViewTx(
|
||||
control.findAll,
|
||||
updateTx.objectId,
|
||||
updateTx.objectClass,
|
||||
updateTx.modifiedOn,
|
||||
updateTx.modifiedBy
|
||||
)
|
||||
if (mainTx !== undefined) {
|
||||
txes.push(mainTx)
|
||||
}
|
||||
if (updateTx.operations.assignee != null) {
|
||||
const assignee = (
|
||||
await control.modelDb.findAll(core.class.Account, { employee: updateTx.operations.assignee }, { limit: 1 })
|
||||
)[0]
|
||||
if (assignee !== undefined) {
|
||||
const assigneeTx = await getUpdateLastViewTx(
|
||||
control.findAll,
|
||||
updateTx.objectId,
|
||||
updateTx.objectClass,
|
||||
updateTx.modifiedOn,
|
||||
assignee._id
|
||||
)
|
||||
if (assigneeTx !== undefined) {
|
||||
txes.push(assigneeTx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return txes
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
function: {
|
||||
|
@ -31,7 +31,6 @@ import core, {
|
||||
WithLookup
|
||||
} from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { Resource } from '@hcengineering/platform/lib/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import { addAssigneeNotification } from '@hcengineering/server-task-resources'
|
||||
import tracker, { Component, Issue, IssueParentInfo, Project, TimeSpendReport, trackerId } from '@hcengineering/tracker'
|
||||
@ -82,14 +81,7 @@ export async function addTrackerAssigneeNotification (
|
||||
assignee: Ref<Employee>,
|
||||
ptx: TxCollectionCUD<Issue, AttachedDoc>
|
||||
): Promise<void> {
|
||||
await addAssigneeNotification(
|
||||
control,
|
||||
res,
|
||||
issue,
|
||||
assignee,
|
||||
ptx,
|
||||
tracker.component.EditIssue as unknown as Resource<string>
|
||||
)
|
||||
await addAssigneeNotification(control, res, issue, assignee, ptx)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,7 +161,7 @@ export async function OnIssueUpdate (tx: Tx, control: TriggerControl): Promise<T
|
||||
if (control.hierarchy.isDerived(createTx.objectClass, tracker.class.Issue)) {
|
||||
const issue = TxProcessor.createDoc2Doc(createTx)
|
||||
const res: Tx[] = []
|
||||
await updateIssueParentEstimations(issue, res, control, [], issue.parents)
|
||||
updateIssueParentEstimations(issue, res, control, [], issue.parents)
|
||||
|
||||
if (issue.assignee != null) {
|
||||
await addTrackerAssigneeNotification(
|
||||
|
@ -115,9 +115,11 @@ test('my-issues', async ({ page }) => {
|
||||
await expect(page.locator('.antiPanel-component')).toContainText(name)
|
||||
await openIssue(page, name)
|
||||
// click "Don't track"
|
||||
await page.click('.buttons-group > div > .button')
|
||||
await page.click('button:has-text("Appleseed John") >> nth=1')
|
||||
await page.click('.selectPopup >> button:has-text("Appleseed John")')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(page.locator('.antiPanel-component')).not.toContainText(name)
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user