diff --git a/models/chunter/src/index.ts b/models/chunter/src/index.ts index 7be1cf5ec6..52a60cf306 100644 --- a/models/chunter/src/index.ts +++ b/models/chunter/src/index.ts @@ -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 }) diff --git a/models/contact/package.json b/models/contact/package.json index 54f76d5b6f..a79bcb7f5a 100644 --- a/models/contact/package.json +++ b/models/contact/package.json @@ -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", diff --git a/models/contact/src/index.ts b/models/contact/src/index.ts index d9ec0182e5..5dbb7ba47f 100644 --- a/models/contact/src/index.ts +++ b/models/contact/src/index.ts @@ -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 }) diff --git a/models/document/package.json b/models/document/package.json index 996d743b32..93ecade677 100644 --- a/models/document/package.json +++ b/models/document/package.json @@ -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", diff --git a/models/document/src/index.ts b/models/document/src/index.ts index 820cc620cc..95aae9e2b5 100644 --- a/models/document/src/index.ts +++ b/models/document/src/index.ts @@ -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 }) diff --git a/models/inventory/src/index.ts b/models/inventory/src/index.ts index a391dc7660..cea2561d7c 100644 --- a/models/inventory/src/index.ts +++ b/models/inventory/src/index.ts @@ -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, diff --git a/models/notification/src/index.ts b/models/notification/src/index.ts index c1b5de82a9..a6d814f25c 100644 --- a/models/notification/src/index.ts +++ b/models/notification/src/index.ts @@ -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 } @@ -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[] +} 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, diff --git a/models/notification/src/migration.ts b/models/notification/src/migration.ts index e159ffedeb..2f92e3fec5 100644 --- a/models/notification/src/migration.ts +++ b/models/notification/src/migration.ts @@ -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 { @@ -79,9 +91,104 @@ async function createSpace (client: MigrationUpgradeClient): Promise { } } +async function migrateLastView (client: MigrationClient): Promise { + // 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>>() + trackedClasses.forEach((p) => h.getDescendants(p).forEach((a) => allowedClasses.add(a))) + + const removeTxes = await client.find>( + DOMAIN_TX, + { + _class: core.class.TxRemoveDoc + }, + { projection: { objectId: 1 } } + ) + + const removedDocs: Set> = new Set(removeTxes.map((p) => p.objectId)) + const removedCollectionTxes = await client.find>( + DOMAIN_TX, + { + _class: core.class.TxCollectionCUD, + 'tx._class': core.class.TxRemoveDoc + }, + { projection: { tx: 1 } } + ) + removedCollectionTxes.forEach((p) => p.tx.objectId) + + const newLastView: Map, LastView> = new Map() + let total = 0 + while (true) { + const lastViews = await client.find( + 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 { + 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 { await fillNotificationText(client) + await migrateLastView(client) + await fillCollaborators(client) }, async upgrade (client: MigrationUpgradeClient): Promise { await createSpace(client) diff --git a/models/notification/src/plugin.ts b/models/notification/src/plugin.ts index 6351df4170..0c79e0dfbc 100644 --- a/models/notification/src/plugin.ts +++ b/models/notification/src/plugin.ts @@ -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 diff --git a/models/recruit/package.json b/models/recruit/package.json index 967adcefb9..da503d9a4e 100644 --- a/models/recruit/package.json +++ b/models/recruit/package.json @@ -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", diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index 64166c7fce..56eb51d2c2 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -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 }) diff --git a/models/server-notification/src/index.ts b/models/server-notification/src/index.ts index af8f1d3307..ad67a93184 100644 --- a/models/server-notification/src/index.ts +++ b/models/server-notification/src/index.ts @@ -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 + }) } diff --git a/models/task/src/index.ts b/models/task/src/index.ts index b6cd5bbe14..f27527a7cf 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -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'] }) diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 11bb929846..9a641421f6 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -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'] }) diff --git a/packages/core/src/hierarchy.ts b/packages/core/src/hierarchy.ts index e43fd9c126..9ee8a12ef4 100644 --- a/packages/core/src/hierarchy.ts +++ b/packages/core/src/hierarchy.ts @@ -74,6 +74,17 @@ export class Hierarchy { return typeof (d as any)[mixin] === 'object' } + classHierarchyMixin(_class: Ref>, mixin: Ref>): 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>): boolean { const data = this.classifiers.get(_class) return data !== undefined && this._isMixin(data) diff --git a/packages/core/src/operator.ts b/packages/core/src/operator.ts index b6925505d9..2f4f5b3f1e 100644 --- a/packages/core/src/operator.ts +++ b/packages/core/src/operator.ts @@ -150,13 +150,24 @@ function $inc (document: Doc, keyval: Record): void { } } +function $unset (document: Doc, keyval: Record): 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 = { $push, $pull, $update, $move, $pushMixin, - $inc + $inc, + $unset } /** diff --git a/packages/core/src/tx.ts b/packages/core/src/tx.ts index a5d06d29ba..86d8e25fab 100644 --- a/packages/core/src/tx.ts +++ b/packages/core/src/tx.ts @@ -201,6 +201,20 @@ export interface PushOptions { $move?: Partial>>> } +/** + * @public + */ +export interface UnsetProperties { + [key: string]: any +} + +/** + * @public + */ +export interface UnsetOptions { + $unset?: UnsetProperties +} + /** * @public */ diff --git a/packages/model/src/migration.ts b/packages/model/src/migration.ts index 16a60aff6e..3c99b2fa76 100644 --- a/packages/model/src/migration.ts +++ b/packages/model/src/migration.ts @@ -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 { - $unset?: Partial>>> -} - /** * @public */ export type MigrateUpdate = Partial & Omit, '$move'> & IncOptions & -UnsetOptions & { +UnsetOptions & { // For any other mongo stuff [key: string]: any } diff --git a/packages/panel/package.json b/packages/panel/package.json index 92f59ba186..4e3dcfc466 100644 --- a/packages/panel/package.json +++ b/packages/panel/package.json @@ -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": { diff --git a/packages/panel/src/components/Panel.svelte b/packages/panel/src/components/Panel.svelte index 9531a63ac1..66768351db 100644 --- a/packages/panel/src/components/Panel.svelte +++ b/packages/panel/src/components/Panel.svelte @@ -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 @@ - {#if isUtils && $$slots.utils}
diff --git a/packages/presentation/src/components/AttributeBarEditor.svelte b/packages/presentation/src/components/AttributeBarEditor.svelte index ca93c45f5c..569a43c2ee 100644 --- a/packages/presentation/src/components/AttributeBarEditor.svelte +++ b/packages/presentation/src/components/AttributeBarEditor.svelte @@ -15,8 +15,8 @@ --> {#if editor} - {#await editor then instance} - {#if instance} - {#if showHeader} - -
- -
- {:else} -
- -
- {/if} - {/if} - {/await} + {#if showHeader} + +
+ +
+ {:else} +
+ +
+ {/if} {/if} diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index e7dbec7130..8ddd47d416 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -312,6 +312,7 @@ export async function getAttributeEditor ( _class: Ref>, key: KeyedAttribute | string ): Promise { + 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> 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 { diff --git a/plugins/activity-resources/src/components/Activity.svelte b/plugins/activity-resources/src/components/Activity.svelte index 1fad244ea2..5a7126f389 100644 --- a/plugins/activity-resources/src/components/Activity.svelte +++ b/plugins/activity-resources/src/components/Activity.svelte @@ -13,32 +13,32 @@ // limitations under the License. --> diff --git a/plugins/chunter-resources/src/components/Channel.svelte b/plugins/chunter-resources/src/components/Channel.svelte index d1ad537b43..a3c9e4aa46 100644 --- a/plugins/chunter-resources/src/components/Channel.svelte +++ b/plugins/chunter-resources/src/components/Channel.svelte @@ -15,7 +15,8 @@ diff --git a/plugins/notification-resources/src/components/LastViewEditor.svelte b/plugins/notification-resources/src/components/LastViewEditor.svelte deleted file mode 100644 index ff4ee40547..0000000000 --- a/plugins/notification-resources/src/components/LastViewEditor.svelte +++ /dev/null @@ -1,39 +0,0 @@ - - - -
-
diff --git a/plugins/notification-resources/src/components/NotificationPresenter.svelte b/plugins/notification-resources/src/components/NotificationPresenter.svelte index a7fa04dec0..09bebe29b3 100644 --- a/plugins/notification-resources/src/components/NotificationPresenter.svelte +++ b/plugins/notification-resources/src/components/NotificationPresenter.svelte @@ -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 diff --git a/plugins/notification-resources/src/index.ts b/plugins/notification-resources/src/index.ts index 9dffaefd6c..5e95e00c96 100644 --- a/plugins/notification-resources/src/index.ts +++ b/plugins/notification-resources/src/index.ts @@ -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 => ({ component: { NotificationsPopup, NotificationPresenter, - NotificationSettings, - LastViewEditor + NotificationSettings }, function: { GetNotificationClient: NotificationClientImpl.getClient diff --git a/plugins/notification-resources/src/utils.ts b/plugins/notification-resources/src/utils.ts index 343fe6f540..bc326deb61 100644 --- a/plugins/notification-resources/src/utils.ts +++ b/plugins/notification-resources/src/utils.ts @@ -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, LastView>() - private readonly lastViewsStore = writable(new Map, Timestamp>()) + private readonly lastViewsStore = writable() private readonly lastViewQuery = createQuery() + private readonly user: Ref private constructor () { - this.lastViewQuery.query(notification.class.LastView, { user: getCurrentAccount()._id }, (result) => { - const res: Map, Timestamp> = new Map, Timestamp>() - const lastViews: Map, LastView> = new Map, 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, Timestamp>> { + getLastViews (): Writable { return this.lastViewsStore } @@ -64,39 +66,35 @@ export class NotificationClientImpl implements NotificationClient { force: boolean = false ): Promise { 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): Promise { 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) } } } diff --git a/plugins/notification/src/index.ts b/plugins/notification/src/index.ts index c4715f1ab1..a2e8de3309 100644 --- a/plugins/notification/src/index.ts +++ b/plugins/notification/src/index.ts @@ -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 -} - -/** - * @public - */ -export interface NotificationAction { - component: AnyComponent - objectId: Ref - objectClass: Ref> + [key: string]: any } /** @@ -46,9 +37,6 @@ export interface Notification extends AttachedDoc { status: NotificationStatus text: string type: Ref - - // Defined to open particular item if required. - action?: NotificationAction } /** @@ -111,13 +99,27 @@ export interface SpaceLastEdit extends Class { /** * @public */ -export interface LastViewAttached extends Class {} +export interface AnotherUserNotifications extends Class { + fields: string[] +} /** * @public */ -export interface AnotherUserNotifications extends Class { - fields: string[] +export interface ClassCollaborators extends Class { + fields: string[] // Ref | Ref | Ref[] | Ref[] +} + +/** + * @public + */ +export interface TrackedDoc extends Class {} + +/** + * @public + */ +export interface Collaborators extends Doc { + collaborators: Ref[] } /** @@ -129,7 +131,7 @@ export const notificationId = 'notification' as Plugin * @public */ export interface NotificationClient { - getLastViews: () => Writable, Timestamp>> + getLastViews: () => Writable updateLastView: (_id: Ref, _class: Ref>, time?: Timestamp, force?: boolean) => Promise unsubscribe: (_id: Ref) => Promise } @@ -146,7 +148,9 @@ const notification = plugin(notificationId, { mixin: { SpaceLastEdit: '' as Ref>, AnotherUserNotifications: '' as Ref>, - LastViewAttached: '' as Ref> + ClassCollaborators: '' as Ref>, + Collaborators: '' as Ref>, + TrackedDoc: '' as Ref> }, class: { LastView: '' as Ref>, @@ -159,6 +163,7 @@ const notification = plugin(notificationId, { ids: { MentionNotification: '' as Ref, DMNotification: '' as Ref, + CollaboratorNotification: '' as Ref, PlatformNotification: '' as Ref, BrowserNotification: '' as Ref, EmailNotification: '' as Ref, @@ -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, diff --git a/plugins/recruit-resources/src/components/EditVacancy.svelte b/plugins/recruit-resources/src/components/EditVacancy.svelte index babfe06e49..532221faf7 100644 --- a/plugins/recruit-resources/src/components/EditVacancy.svelte +++ b/plugins/recruit-resources/src/components/EditVacancy.svelte @@ -15,15 +15,14 @@ --> {#if object} @@ -88,11 +102,10 @@ {#if dir === 'column'}
-
diff --git a/plugins/tracker-resources/src/components/issues/edit/ControlPanel.svelte b/plugins/tracker-resources/src/components/issues/edit/ControlPanel.svelte index e2c380d0f2..3d22a51062 100644 --- a/plugins/tracker-resources/src/components/issues/edit/ControlPanel.svelte +++ b/plugins/tracker-resources/src/components/issues/edit/ControlPanel.svelte @@ -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) => diff --git a/plugins/tracker-resources/src/components/myissues/MyIssues.svelte b/plugins/tracker-resources/src/components/myissues/MyIssues.svelte index f89de79a6f..e44aebb737 100644 --- a/plugins/tracker-resources/src/components/myissues/MyIssues.svelte +++ b/plugins/tracker-resources/src/components/myissues/MyIssues.svelte @@ -14,8 +14,7 @@ --> diff --git a/plugins/view-resources/src/components/ViewletSetting.svelte b/plugins/view-resources/src/components/ViewletSetting.svelte index fcbc29a9be..8c4441256d 100644 --- a/plugins/view-resources/src/components/ViewletSetting.svelte +++ b/plugins/view-resources/src/components/ViewletSetting.svelte @@ -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) diff --git a/plugins/view-resources/src/utils.ts b/plugins/view-resources/src/utils.ts index 5721d41d28..02545fa23a 100644 --- a/plugins/view-resources/src/utils.ts +++ b/plugins/view-resources/src/utils.ts @@ -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>): 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 = {}, component: AnyComponent = view.component.EditDoc ): Promise { - 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) diff --git a/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte b/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte index 237b12bf9e..18f8fe8e99 100644 --- a/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte +++ b/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte @@ -15,7 +15,7 @@