Cleanup code (#5736)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-06-05 19:35:38 +04:00 committed by GitHub
parent 14c1977660
commit 0f79b9fed7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 91 additions and 2719 deletions

View File

@ -32,7 +32,6 @@ import {
type IgnoreActivity,
type Reaction,
type SavedMessage,
type TxViewlet,
type ReplyProvider
} from '@hcengineering/activity'
import contact, { type Person } from '@hcengineering/contact'
@ -93,21 +92,6 @@ export class TActivityAttributeUpdatesPresenter extends TClass implements Activi
@Mixin(activity.mixin.IgnoreActivity, core.class.Class)
export class TIgnoreActivity extends TClass implements IgnoreActivity {}
@Model(activity.class.TxViewlet, core.class.Doc, DOMAIN_MODEL)
export class TTxViewlet extends TDoc implements TxViewlet {
icon!: Asset
objectClass!: Ref<Class<Doc>>
txClass!: Ref<Class<Tx>>
// Component to display on.
component!: AnyComponent
// Filter
match?: DocumentQuery<Tx>
label!: IntlString
display!: 'inline' | 'content' | 'emphasized'
editable!: boolean
hideOnRemove!: boolean
}
@Model(activity.class.ActivityMessage, core.class.AttachedDoc, DOMAIN_ACTIVITY)
export class TActivityMessage extends TAttachedDoc implements ActivityMessage {
@Prop(TypeBoolean(), activity.string.Pinned)
@ -267,7 +251,6 @@ export class TUserMentionInfo extends TAttachedDoc {
export function createModel (builder: Builder): void {
builder.createModel(
TTxViewlet,
TActivityDoc,
TActivityMessagesFilter,
TActivityMessage,

View File

@ -97,34 +97,6 @@ export function createModel (builder: Builder): void {
editor: attachment.component.Photos
})
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: attachment.class.Attachment,
icon: attachment.icon.Attachment,
txClass: core.class.TxCreateDoc,
component: attachment.activity.TxAttachmentCreate,
label: attachment.string.AddAttachment,
display: 'emphasized'
},
attachment.ids.TxAttachmentCreate
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: attachment.class.Attachment,
icon: attachment.icon.Attachment,
txClass: core.class.TxRemoveDoc,
component: attachment.activity.TxAttachmentCreate,
label: attachment.string.RemovedAttachment,
display: 'inline'
},
attachment.ids.TxAttachmentRemove
)
builder.createDoc(
activity.class.DocUpdateMessageViewlet,
core.space.Model,

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import type { TxViewlet, ActivityMessage, DocUpdateMessageViewlet } from '@hcengineering/activity'
import type { ActivityMessage, DocUpdateMessageViewlet } from '@hcengineering/activity'
import { attachmentId } from '@hcengineering/attachment'
import attachment from '@hcengineering/attachment-resources/src/plugin'
import type { Ref, Doc } from '@hcengineering/core'
@ -44,13 +44,10 @@ export default mergeIds(attachmentId, attachment, {
ContentType: '' as IntlString
},
ids: {
TxAttachmentCreate: '' as Ref<TxViewlet>,
TxAttachmentRemove: '' as Ref<TxViewlet>,
AttachmentCreatedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
AttachmentRemovedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>
},
activity: {
TxAttachmentCreate: '' as AnyComponent,
AttachmentsUpdatedMessage: '' as AnyComponent
},
category: {

View File

@ -239,22 +239,6 @@ export function createModel (builder: Builder): void {
calendar.ids.ReminderNotification
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: calendar.class.Event,
icon: calendar.icon.Reminder,
txClass: core.class.TxUpdateDoc,
label: calendar.string.Reminder,
component: calendar.activity.ReminderViewlet,
display: 'emphasized',
editable: false,
hideOnRemove: true
},
calendar.ids.ReminderViewlet
)
builder.createDoc(
activity.class.DocUpdateMessageViewlet,
core.space.Model,

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import { type DocUpdateMessageViewlet, type TxViewlet } from '@hcengineering/activity'
import { type DocUpdateMessageViewlet } from '@hcengineering/activity'
import { calendarId } from '@hcengineering/calendar'
import calendar from '@hcengineering/calendar-resources/src/plugin'
import { type Ref } from '@hcengineering/core'
@ -65,7 +65,6 @@ export default mergeIds(calendarId, calendar, {
CalendarEvent: '' as Ref<Viewlet>
},
ids: {
ReminderViewlet: '' as Ref<TxViewlet>,
UpdateRemainderActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
CalendarNotificationGroup: '' as Ref<NotificationGroup>
}

View File

@ -17,39 +17,30 @@ import activity, { type ActivityMessage } from '@hcengineering/activity'
import {
type Channel,
chunterId,
type ChunterMessage,
type ChunterMessageExtension,
type DirectMessage,
type Message,
type DirectMessageInput,
type ChatMessage,
type ChatMessageViewlet,
type ChunterSpace,
type ObjectChatPanel,
type ThreadMessage
} from '@hcengineering/chunter'
import contact, { type Person } from '@hcengineering/contact'
import contact from '@hcengineering/contact'
import {
type Account,
type Class,
type Doc,
type Domain,
DOMAIN_MODEL,
type Ref,
type Space,
type Timestamp,
IndexKind
} from '@hcengineering/core'
import {
ArrOf,
type Builder,
Collection as PropCollection,
Collection,
Index,
Mixin,
Model,
Prop,
ReadOnly,
TypeMarkup,
TypeRef,
TypeString,
@ -57,11 +48,10 @@ import {
UX
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@hcengineering/model-core'
import core, { TClass, TDoc, TSpace } from '@hcengineering/model-core'
import notification, { notificationActionTemplates } from '@hcengineering/model-notification'
import view, { createAction, template, actionTemplates as viewTemplates } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import { type AnyComponent } from '@hcengineering/ui/src/types'
import type { IntlString } from '@hcengineering/platform'
import { TActivityMessage } from '@hcengineering/model-activity'
@ -73,10 +63,7 @@ export { chunterOperation } from './migration'
export const DOMAIN_CHUNTER = 'chunter' as Domain
@Model(chunter.class.ChunterSpace, core.class.Space)
export class TChunterSpace extends TSpace implements ChunterSpace {
@Prop(TypeTimestamp(), chunter.string.LastMessage)
lastMessage?: Timestamp
}
export class TChunterSpace extends TSpace implements ChunterSpace {}
@Model(chunter.class.Channel, chunter.class.ChunterSpace)
@UX(chunter.string.Channel, chunter.icon.Hashtag, undefined, undefined, undefined, chunter.string.Channels)
@ -90,50 +77,6 @@ export class TChannel extends TChunterSpace implements Channel {
@UX(chunter.string.DirectMessage, contact.icon.Person, undefined, undefined, undefined, chunter.string.DirectMessages)
export class TDirectMessage extends TChunterSpace implements DirectMessage {}
@Model(chunter.class.ChunterMessage, core.class.AttachedDoc, DOMAIN_CHUNTER)
export class TChunterMessage extends TAttachedDoc implements ChunterMessage {
@Prop(TypeMarkup(), chunter.string.Content)
@Index(IndexKind.FullText)
content!: string
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number
@Prop(TypeRef(core.class.Account), chunter.string.CreateBy)
@ReadOnly()
createBy!: Ref<Account>
@Prop(TypeTimestamp(), chunter.string.Edit)
editedOn?: Timestamp
@Prop(Collection(activity.class.Reaction), activity.string.Reactions)
reactions?: number
}
@Mixin(chunter.mixin.ChunterMessageExtension, chunter.class.ChunterMessage)
export class TChunterMessageExtension extends TChunterMessage implements ChunterMessageExtension {}
@Model(chunter.class.Message, chunter.class.ChunterMessage)
@UX(chunter.string.Message, undefined, 'MSG')
export class TMessage extends TChunterMessage implements Message {
declare attachedTo: Ref<Space>
declare attachedToClass: Ref<Class<Space>>
@Prop(ArrOf(TypeRef(contact.class.Person)), chunter.string.Replies)
replies?: Ref<Person>[]
repliesCount?: number
@Prop(TypeTimestamp(), activity.string.LastReply)
lastReply?: Timestamp
}
@Mixin(chunter.mixin.DirectMessageInput, core.class.Class)
export class TDirectMessageInput extends TClass implements DirectMessageInput {
component!: AnyComponent
}
@Model(chunter.class.ChatMessage, activity.class.ActivityMessage)
@UX(chunter.string.Message, chunter.icon.Thread, undefined, undefined, undefined, chunter.string.Threads)
export class TChatMessage extends TActivityMessage implements ChatMessage {
@ -206,11 +149,7 @@ export function createModel (builder: Builder): void {
builder.createModel(
TChunterSpace,
TChannel,
TMessage,
TChunterMessage,
TChunterMessageExtension,
TDirectMessage,
TDirectMessageInput,
TChatMessage,
TThreadMessage,
TChatMessageViewlet,
@ -233,12 +172,6 @@ export function createModel (builder: Builder): void {
encode: chunter.function.GetChunterSpaceLinkFragment
})
builder.mixin(spaceClass, core.class.Class, workbench.mixin.SpaceView, {
view: {
class: chunter.class.Message
}
})
builder.mixin(spaceClass, core.class.Class, view.mixin.ObjectEditor, {
editor: chunter.component.EditChannel
})
@ -248,10 +181,6 @@ export function createModel (builder: Builder): void {
})
})
builder.mixin(chunter.class.Message, core.class.Class, notification.mixin.ClassCollaborators, {
fields: ['createdBy', 'replies']
})
builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.ObjectTitle, {
titleProvider: chunter.function.DirectTitleProvider
})
@ -276,22 +205,6 @@ export function createModel (builder: Builder): void {
presenter: chunter.component.ChannelPreview
})
builder.mixin(chunter.class.DirectMessage, core.class.Class, chunter.mixin.DirectMessageInput, {
component: chunter.component.DirectMessageInput
})
builder.mixin(chunter.class.Message, core.class.Class, notification.mixin.NotificationObjectPresenter, {
presenter: chunter.component.ThreadParentPresenter
})
builder.mixin(chunter.class.Message, core.class.Class, view.mixin.ObjectPanel, {
component: chunter.component.ThreadViewPanel
})
builder.mixin(chunter.class.Message, core.class.Class, view.mixin.ObjectPresenter, {
presenter: chunter.component.MessagePresenter
})
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ObjectPresenter, {
presenter: chunter.component.ChannelPresenter
})
@ -438,10 +351,6 @@ export function createModel (builder: Builder): void {
chunter.action.CopyChatMessageLink
)
builder.mixin(chunter.class.ChunterMessage, core.class.Class, view.mixin.ClassFilters, {
filters: ['space', '_class']
})
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ClassFilters, {
filters: []
})

View File

@ -21,7 +21,8 @@ import core, {
type Doc,
type Domain,
type Ref,
type Space
type Space,
DOMAIN_TX
} from '@hcengineering/core'
import {
tryMigrate,
@ -35,6 +36,7 @@ import notification from '@hcengineering/notification'
import contactPlugin, { type PersonAccount } from '@hcengineering/contact'
import chunter from './plugin'
import { DOMAIN_CHUNTER } from './index'
export const DOMAIN_COMMENT = 'comment' as Domain
@ -175,6 +177,21 @@ async function removeBacklinks (client: MigrationClient): Promise<void> {
})
}
async function removeOldClasses (client: MigrationClient): Promise<void> {
const classes = [
'chunter:class:ChunterMessage',
'chunter:class:Message',
'chunter:class:Comment',
'chunter:class:Backlink'
] as Ref<Class<Doc>>[]
for (const _class of classes) {
await client.deleteMany(DOMAIN_CHUNTER, { _class })
await client.deleteMany(DOMAIN_TX, { objectClass: _class })
await client.deleteMany(DOMAIN_TX, { 'tx.objectClass': _class })
}
}
export const chunterOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await tryMigrate(client, chunterId, [
@ -207,6 +224,12 @@ export const chunterOperation: MigrateOperation = {
(msg) => (msg as ThreadMessage).objectClass
)
}
},
{
state: 'remove-old-classes',
func: async (client) => {
await removeOldClasses(client)
}
}
])
},

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import type { ActivityMessage, TxViewlet } from '@hcengineering/activity'
import type { ActivityMessage } from '@hcengineering/activity'
import { chunterId, type Channel } from '@hcengineering/chunter'
import chunter from '@hcengineering/chunter-resources/src/plugin'
import { type Client, type Doc, type Ref } from '@hcengineering/core'
@ -26,8 +26,6 @@ import type { Action, ActionCategory, ViewAction, Viewlet, ViewletDescriptor } f
export default mergeIds(chunterId, chunter, {
component: {
ChannelPresenter: '' as AnyComponent,
DirectMessagePresenter: '' as AnyComponent,
MessagePresenter: '' as AnyComponent,
DmPresenter: '' as AnyComponent,
ChannelsPanel: '' as AnyComponent,
Chat: '' as AnyComponent,
@ -80,17 +78,8 @@ export default mergeIds(chunterId, chunter, {
Channels: '' as Ref<Viewlet>
},
ids: {
TxCommentCreate: '' as Ref<TxViewlet>,
TxCommentRemove: '' as Ref<TxViewlet>,
TxMessageCreate: '' as Ref<TxViewlet>,
TxChatMessageCreate: '' as Ref<TxViewlet>,
TxChatMessageRemove: '' as Ref<TxViewlet>,
ChunterNotificationGroup: '' as Ref<NotificationGroup>
},
activity: {
TxCommentCreate: '' as AnyComponent,
TxMessageCreate: '' as AnyComponent
},
space: {
General: '' as Ref<Channel>,
Random: '' as Ref<Channel>

View File

@ -376,15 +376,6 @@ export function createModel (builder: Builder): void {
index: 100
})
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
objectClass: contact.class.Person,
icon: contact.icon.Person,
txClass: core.class.TxUpdateDoc,
labelComponent: contact.activity.TxNameChange,
display: 'inline',
match: { 'operations.name': { $exists: true } }
})
builder.createDoc(activity.class.DocUpdateMessageViewlet, core.space.Model, {
objectClass: contact.class.Person,
action: 'update',

View File

@ -27,7 +27,6 @@ import { type ChatMessageViewlet } from '@hcengineering/chunter'
export default mergeIds(contactId, contact, {
activity: {
TxNameChange: '' as AnyComponent,
NameChangedActivityMessage: '' as AnyComponent
},
component: {

View File

@ -244,21 +244,6 @@ export function createModel (builder: Builder): void {
)
// Workflow
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: documents.class.DocumentRequest,
icon: request.icon.Requests,
txClass: core.class.TxCreateDoc,
component: request.activity.TxCreateRequest,
label: request.string.CreatedRequest,
labelComponent: request.activity.RequestLabel,
display: 'emphasized'
},
request.ids.TxRequestCreate
)
builder.createDoc(
view.class.Viewlet,
core.space.Model,

View File

@ -162,20 +162,6 @@ export function createModel (builder: Builder): void {
gmail.integrationType.Gmail
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: gmail.class.Message,
icon: contact.icon.Email,
txClass: core.class.TxCreateDoc,
label: gmail.string.HaveWrittenEmail,
labelComponent: gmail.activity.TxWriteMessage,
display: 'inline'
},
gmail.ids.TxSharedCreate
)
builder.createDoc(
activity.class.DocUpdateMessageViewlet,
core.space.Model,
@ -189,22 +175,6 @@ export function createModel (builder: Builder): void {
gmail.ids.GmailWriteMessageActivityViewlet
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: gmail.class.SharedMessages,
icon: contact.icon.Email,
txClass: core.class.TxCreateDoc,
component: gmail.activity.TxSharedCreate,
label: gmail.string.SharedMessages,
display: 'content',
editable: true,
hideOnRemove: true
},
gmail.ids.TxSharedCreate
)
builder.createDoc(
activity.class.DocUpdateMessageViewlet,
core.space.Model,
@ -272,24 +242,6 @@ export function createModel (builder: Builder): void {
gmail.ids.EmailNotification
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: gmail.class.Message,
icon: contact.icon.Email,
txClass: core.class.TxCreateDoc,
match: {
'attributes.incoming': true
},
label: gmail.string.NewIncomingMessage,
display: 'inline',
editable: false,
hideOnRemove: true
},
gmail.ids.NewMessageNotification
)
builder.mixin(gmail.class.Message, core.class.Class, core.mixin.FullTextSearchContext, {
parentPropagate: false
})

View File

@ -19,7 +19,7 @@ import { type IntlString, mergeIds, type Resource } from '@hcengineering/platfor
import { gmailId } from '@hcengineering/gmail'
import gmail from '@hcengineering/gmail-resources/src/plugin'
import type { AnyComponent } from '@hcengineering/ui/src/types'
import type { DocUpdateMessageViewlet, TxViewlet } from '@hcengineering/activity'
import type { DocUpdateMessageViewlet } from '@hcengineering/activity'
import { type Action } from '@hcengineering/view'
import { type NotificationGroup } from '@hcengineering/notification'
@ -43,15 +43,11 @@ export default mergeIds(gmailId, gmail, {
ConfigDescription: '' as IntlString
},
ids: {
TxSharedCreate: '' as Ref<TxViewlet>,
NewMessageNotification: '' as Ref<TxViewlet>,
EmailNotificationGroup: '' as Ref<NotificationGroup>,
GmailSharedMessageActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
GmailWriteMessageActivityViewlet: '' as Ref<DocUpdateMessageViewlet>
},
activity: {
TxSharedCreate: '' as AnyComponent,
TxWriteMessage: '' as AnyComponent,
GmailSharedMessage: '' as AnyComponent,
GmailWriteMessage: '' as AnyComponent
},

View File

@ -32,7 +32,8 @@ import {
type Ref,
type Space,
type Timestamp,
type Tx
type Tx,
type IndexingConfiguration
} from '@hcengineering/core'
import {
ArrOf,
@ -61,8 +62,6 @@ import {
type CommonInboxNotification,
type CommonNotificationType,
type DocNotifyContext,
type DocUpdateTx,
type DocUpdates,
type InboxNotification,
type MentionInboxNotification,
type NotificationContextPresenter,
@ -78,7 +77,7 @@ import {
type PushSubscription,
type PushSubscriptionKeys
} from '@hcengineering/notification'
import { getEmbeddedLabel, type Asset, type IntlString, type Resource } from '@hcengineering/platform'
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
import setting from '@hcengineering/setting'
import { type AnyComponent, type Location } from '@hcengineering/ui/src/types'
@ -185,25 +184,6 @@ export class TNotificationContextPresenter extends TClass implements Notificatio
labelPresenter?: AnyComponent
}
@Model(notification.class.DocUpdates, core.class.Doc, DOMAIN_NOTIFICATION)
export class TDocUpdates extends TDoc implements DocUpdates {
@Prop(TypeRef(core.class.Account), core.string.Account)
@Index(IndexKind.Indexed)
user!: Ref<Account>
@Prop(TypeRef(core.class.Account), core.string.AttachedTo)
@Index(IndexKind.Indexed)
attachedTo!: Ref<Doc>
@Prop(TypeRef(core.class.Account), getEmbeddedLabel('Hidden'))
// @Index(IndexKind.Indexed)
hidden!: boolean
attachedToClass!: Ref<Class<Doc>>
lastTxTime?: Timestamp
txes!: DocUpdateTx[]
}
@Model(notification.class.DocNotifyContext, core.class.Doc, DOMAIN_NOTIFICATION)
export class TDocNotifyContext extends TDoc implements DocNotifyContext {
@Prop(TypeRef(core.class.Account), core.string.Account)
@ -339,7 +319,6 @@ export function createModel (builder: Builder): void {
TNotificationPreferencesGroup,
TClassCollaborators,
TCollaborators,
TDocUpdates,
TNotificationObjectPresenter,
TNotificationPreview,
TDocNotifyContext,
@ -455,36 +434,6 @@ export function createModel (builder: Builder): void {
notification.ids.CollaboratoAddNotification
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: notification.mixin.Collaborators,
icon: notification.icon.Notifications,
txClass: core.class.TxMixin,
component: notification.activity.TxCollaboratorsChange,
display: 'inline',
editable: false,
hideOnRemove: true
},
notification.ids.TxCollaboratorsChange
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: chunter.class.DirectMessage,
icon: chunter.icon.Chunter,
txClass: core.class.TxCreateDoc,
component: notification.activity.TxDmCreation,
display: 'inline',
editable: false,
hideOnRemove: true
},
notification.ids.TxDmCreation
)
builder.createDoc(notification.class.ActivityNotificationViewlet, core.space.Model, {
presenter: notification.component.NotificationCollaboratorsChanged,
messageMatch: {
@ -688,6 +637,16 @@ export function createModel (builder: Builder): void {
indexes: [{ user: 1, archived: 1 }],
disabled: [{ modifiedOn: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { isViewed: 1 }, { hidden: 1 }]
})
builder.mixin<Class<DocNotifyContext>, IndexingConfiguration<DocNotifyContext>>(
notification.class.DocNotifyContext,
core.class.Class,
core.mixin.IndexConfiguration,
{
searchDisabled: true,
indexes: []
}
)
}
export function generateClassNotificationTypes (

View File

@ -13,20 +13,7 @@
// limitations under the License.
//
import activity, { type ActivityMessage, type DocUpdateMessage } from '@hcengineering/activity'
import {
DOMAIN_TX,
TxProcessor,
generateId,
type AttachedDoc,
type Doc,
type Domain,
type Ref,
type TxCUD,
type TxCollectionCUD,
type Class,
type DocumentQuery
} from '@hcengineering/core'
import { type Doc, type Ref, type Class, type DocumentQuery, DOMAIN_TX } from '@hcengineering/core'
import {
createDefaultSpace,
tryMigrate,
@ -35,189 +22,10 @@ import {
type MigrationClient,
type MigrationUpgradeClient
} from '@hcengineering/model'
import notification, {
notificationId,
type ActivityInboxNotification,
type DocNotifyContext,
type DocUpdateTx,
type DocUpdates
} from '@hcengineering/notification'
import notification, { notificationId, type DocNotifyContext } from '@hcengineering/notification'
import { DOMAIN_NOTIFICATION } from './index'
interface InboxData {
context: DocNotifyContext
notifications: ActivityInboxNotification[]
}
const DOMAIN_ACTIVITY = 'activity' as Domain
async function getActivityMessages (
client: MigrationClient,
contexts: {
context: DocNotifyContext
txes: DocUpdateTx[]
}[]
): Promise<ActivityInboxNotification[]> {
const result: ActivityInboxNotification[] = []
const txes = contexts.flatMap((it) => it.txes)
const docUpdateMessages = await client.find<DocUpdateMessage>(DOMAIN_ACTIVITY, {
_class: activity.class.DocUpdateMessage,
txId: { $in: txes.map((it) => it._id) },
attachedTo: { $in: contexts.map((it) => it.context.attachedTo) }
})
if (docUpdateMessages.length > 0) {
docUpdateMessages.forEach((message) => {
const ctx = contexts.find((it) => it.context.attachedTo === message.attachedTo)
if (ctx === undefined) {
return
}
const tx = ctx.txes.find((it) => it._id === (message.txId as any))
if (tx == null) {
return
}
result.push({
_id: generateId(),
_class: notification.class.ActivityInboxNotification,
space: ctx.context.space,
user: ctx.context.user,
isViewed: !tx.isNew,
attachedTo: message._id,
attachedToClass: message._class,
docNotifyContext: ctx.context._id,
title: tx.title,
body: tx.body,
intlParams: tx.intlParams,
intlParamsNotLocalized: tx.intlParamsNotLocalized,
modifiedOn: tx.modifiedOn,
modifiedBy: tx.modifiedBy,
createdOn: tx.modifiedOn,
createdBy: tx.modifiedBy
})
})
}
const originTx: TxCUD<Doc>[] = await client.find<TxCUD<Doc>>(DOMAIN_TX, { _id: { $in: txes.map((it) => it._id) } })
if (originTx.length === 0) {
return result
}
const innerTx = originTx.map((it) => TxProcessor.extractTx(it as TxCollectionCUD<Doc, AttachedDoc>) as TxCUD<Doc>)
;(
await client.find<ActivityMessage>(DOMAIN_ACTIVITY, {
_id: { $in: innerTx.map((it) => it.objectId as Ref<ActivityMessage>) }
})
)
.filter(({ _class }) => client.hierarchy.isDerived(_class, activity.class.ActivityMessage))
.forEach((message) => {
const tx = originTx.find((q) => (TxProcessor.extractTx(q) as TxCUD<Doc>).objectId === message._id)
if (tx == null) {
return
}
const ctx = contexts.find((it) => it.context.attachedTo === message.attachedTo)
if (ctx === undefined) {
return
}
const docTx = ctx.txes.find((it) => it._id === tx._id)
if (docTx == null) {
return
}
result.push({
_id: generateId(),
_class: notification.class.ActivityInboxNotification,
space: ctx.context.space,
user: ctx.context.user,
isViewed: !docTx.isNew,
attachedTo: message._id,
attachedToClass: message._class,
docNotifyContext: ctx.context._id,
title: docTx.title,
body: docTx.body,
intlParams: docTx.intlParams,
intlParamsNotLocalized: docTx.intlParamsNotLocalized,
modifiedOn: docTx.modifiedOn,
modifiedBy: docTx.modifiedBy,
createdOn: docTx.modifiedOn,
createdBy: docTx.modifiedBy
})
})
return result
}
async function getInboxData (client: MigrationClient, docUpdates: DocUpdates[]): Promise<InboxData[]> {
const toProcess = docUpdates.filter((it) => !it.hidden && client.hierarchy.hasClass(it.attachedToClass))
const contexts = toProcess.map((docUpdate) => {
const newTxIndex = docUpdate.txes.findIndex(({ isNew }) => isNew)
const context: DocNotifyContext = {
_id: docUpdate._id,
_class: notification.class.DocNotifyContext,
space: docUpdate.space,
user: docUpdate.user,
attachedTo: docUpdate.attachedTo,
attachedToClass: docUpdate.attachedToClass,
hidden: docUpdate.hidden,
lastViewedTimestamp: newTxIndex !== -1 ? docUpdate.txes[newTxIndex - 1]?.modifiedOn : docUpdate.lastTxTime,
lastUpdateTimestamp: docUpdate.lastTxTime,
modifiedBy: docUpdate.modifiedBy,
modifiedOn: docUpdate.modifiedOn,
createdBy: docUpdate.createdBy,
createdOn: docUpdate.createdOn
}
return {
context,
txes: docUpdate.txes
}
})
const notifications = await getActivityMessages(client, contexts)
return contexts.map((it) => ({
context: it.context,
notifications: notifications.filter((nit) => nit.docNotifyContext === it.context._id)
}))
}
async function migrateInboxNotifications (client: MigrationClient): Promise<void> {
let processing = 0
while (true) {
const docUpdates = await client.find<DocUpdates>(
DOMAIN_NOTIFICATION,
{
_class: notification.class.DocUpdates
},
{ limit: 1000 }
)
console.log('notifications processing:', processing)
if (docUpdates.length === 0) {
return
}
processing += docUpdates.length
const data: InboxData[] = (await getInboxData(client, docUpdates)).filter(
(data): data is InboxData => data !== undefined
)
await client.deleteMany(DOMAIN_NOTIFICATION, { _id: { $in: docUpdates.map(({ _id }) => _id) } })
await client.create(
DOMAIN_NOTIFICATION,
data.map(({ context }) => context)
)
await client.create(
DOMAIN_NOTIFICATION,
data.flatMap(({ notifications }) => notifications)
)
}
}
export async function removeNotifications (
client: MigrationClient,
query: DocumentQuery<DocNotifyContext>
@ -262,12 +70,6 @@ export async function removeNotifications (
export const notificationOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await tryMigrate(client, notificationId, [
{
state: 'inbox-notifications',
func: migrateInboxNotifications
}
])
await tryMigrate(client, notificationId, [
{
state: 'delete-hidden-notifications',
@ -284,6 +86,15 @@ export const notificationOperation: MigrateOperation = {
}
}
])
await tryMigrate(client, notificationId, [
{
state: 'remove-old-classes',
func: async (client) => {
await client.deleteMany(DOMAIN_NOTIFICATION, { _class: 'notification:class:DocUpdates' as Ref<Class<Doc>> })
await client.deleteMany(DOMAIN_TX, { objectClass: 'notification:class:DocUpdates' as Ref<Class<Doc>> })
}
}
])
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await tryUpgrade(client, notificationId, [

View File

@ -20,7 +20,7 @@ import { type IntlString, type Resource, mergeIds } from '@hcengineering/platfor
import { type AnyComponent } from '@hcengineering/ui/src/types'
import { type Action, type ActionCategory, type ViewAction } from '@hcengineering/view'
import { type Application } from '@hcengineering/workbench'
import { type DocUpdateMessageViewlet, type TxViewlet } from '@hcengineering/activity'
import { type DocUpdateMessageViewlet } from '@hcengineering/activity'
export default mergeIds(notificationId, notification, {
string: {
@ -41,12 +41,7 @@ export default mergeIds(notificationId, notification, {
Notification: '' as Ref<Application>,
Inbox: '' as Ref<Application>
},
activity: {
TxDmCreation: '' as AnyComponent
},
ids: {
TxCollaboratorsChange: '' as Ref<TxViewlet>,
TxDmCreation: '' as Ref<TxViewlet>,
CollaboratorsChangedMessage: '' as Ref<DocUpdateMessageViewlet>
},
component: {

View File

@ -151,20 +151,6 @@ export function createModel (builder: Builder): void {
['comments', 'approved', 'rejected', 'status']
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: request.class.Request,
icon: request.icon.Requests,
txClass: core.class.TxCreateDoc,
component: request.activity.TxCreateRequest,
label: request.string.CreatedRequest,
labelComponent: request.activity.RequestLabel,
display: 'emphasized'
},
request.ids.TxRequestCreate
)
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_REQUEST,
disabled: [

View File

@ -19,20 +19,14 @@ import { mergeIds } from '@hcengineering/platform'
import { requestId } from '@hcengineering/request'
import request from '@hcengineering/request-resources/src/plugin'
import { type AnyComponent } from '@hcengineering/ui/src/types'
import type { TxViewlet } from '@hcengineering/activity'
import type { NotificationGroup, NotificationType } from '@hcengineering/notification'
export default mergeIds(requestId, request, {
activity: {
TxCreateRequest: '' as AnyComponent,
RequestLabel: '' as AnyComponent
},
component: {
EditRequest: '' as AnyComponent,
NotificationRequestView: '' as AnyComponent
},
ids: {
TxRequestCreate: '' as Ref<TxViewlet>,
RequestNotificationGroup: '' as Ref<NotificationGroup>,
CreateRequestNotification: '' as Ref<NotificationType>
},

View File

@ -355,22 +355,6 @@ export function createModel (builder: Builder): void {
setting.ids.SettingApp
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: setting.class.Integration,
icon: setting.icon.Integrations,
txClass: core.class.TxUpdateDoc,
label: setting.string.IntegrationWith,
labelComponent: setting.activity.TxIntegrationDisable,
display: 'inline',
editable: false,
hideOnRemove: true
},
setting.ids.TxIntegrationDisable
)
builder.createDoc(
activity.class.DocUpdateMessageViewlet,
core.space.Model,

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import type { DocUpdateMessageViewlet, TxViewlet } from '@hcengineering/activity'
import type { DocUpdateMessageViewlet } from '@hcengineering/activity'
import { type Doc, type Ref } from '@hcengineering/core'
import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform'
import { settingId } from '@hcengineering/setting'
@ -24,11 +24,7 @@ import { type TemplateFieldFunc } from '@hcengineering/templates'
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
export default mergeIds(settingId, setting, {
activity: {
TxIntegrationDisable: '' as AnyComponent
},
ids: {
TxIntegrationDisable: '' as Ref<TxViewlet>,
EnumSetting: '' as Ref<Doc>,
Configure: '' as Ref<Doc>,
SettingNotificationGroup: '' as Ref<NotificationGroup>,

View File

@ -123,19 +123,6 @@ export function createModel (builder: Builder): void {
telegram.templateField.IntegrationOwnerTG
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: telegram.class.Message,
icon: contact.icon.Telegram,
txClass: core.class.TxCreateDoc,
component: telegram.activity.TxMessage,
display: 'inline'
},
telegram.ids.TxMessage
)
builder.createDoc(
activity.class.DocUpdateMessageViewlet,
core.space.Model,
@ -177,22 +164,6 @@ export function createModel (builder: Builder): void {
telegram.integrationType.Telegram
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: telegram.class.SharedMessages,
icon: contact.icon.Telegram,
txClass: core.class.TxCreateDoc,
component: telegram.activity.TxSharedCreate,
label: telegram.string.SharedMessages,
display: 'content',
editable: true,
hideOnRemove: true
},
telegram.ids.TxSharedCreate
)
builder.createDoc(
activity.class.DocUpdateMessageViewlet,
core.space.Model,
@ -235,24 +206,6 @@ export function createModel (builder: Builder): void {
telegram.ids.NewMessageNotification
)
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: telegram.class.Message,
icon: contact.icon.Telegram,
txClass: core.class.TxCreateDoc,
match: {
'attributes.incoming': true
},
label: telegram.string.NewIncomingMessage,
display: 'inline',
editable: false,
hideOnRemove: true
},
telegram.ids.NewMessageNotificationViewlet
)
builder.mixin(telegram.class.Message, core.class.Class, core.mixin.FullTextSearchContext, {
parentPropagate: false,
childProcessingAllowed: true

View File

@ -19,7 +19,7 @@ import { type IntlString, type Resource, mergeIds } from '@hcengineering/platfor
import { telegramId } from '@hcengineering/telegram'
import telegram from '@hcengineering/telegram-resources/src/plugin'
import type { AnyComponent } from '@hcengineering/ui/src/types'
import type { DocUpdateMessageViewlet, TxViewlet } from '@hcengineering/activity'
import type { DocUpdateMessageViewlet } from '@hcengineering/activity'
import { type TemplateFieldFunc } from '@hcengineering/templates'
import { type NotificationGroup } from '@hcengineering/notification'
@ -39,9 +39,6 @@ export default mergeIds(telegramId, telegram, {
NewIncomingMessage: '' as IntlString
},
ids: {
TxMessage: '' as Ref<TxViewlet>,
TxSharedCreate: '' as Ref<TxViewlet>,
NewMessageNotificationViewlet: '' as Ref<TxViewlet>,
NotificationGroup: '' as Ref<NotificationGroup>,
TelegramMessageSharedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
TelegramMessageCreatedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>
@ -51,8 +48,6 @@ export default mergeIds(telegramId, telegram, {
GetIntegrationOwnerTG: '' as Resource<TemplateFieldFunc>
},
activity: {
TxMessage: '' as AnyComponent,
TxSharedCreate: '' as AnyComponent,
TelegramMessageCreated: '' as AnyComponent
}
})

View File

@ -21,7 +21,6 @@ import { type Action, type ActionCategory } from '@hcengineering/view'
import { timeId } from '@hcengineering/time'
import time from '@hcengineering/time-resources/src/plugin'
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
import { type TxViewlet } from '@hcengineering/activity'
export default mergeIds(timeId, time, {
action: {
@ -59,8 +58,7 @@ export default mergeIds(timeId, time, {
},
ids: {
TimeNotificationGroup: '' as Ref<NotificationGroup>,
ToDoCreated: '' as Ref<NotificationType>,
TxToDoCreated: '' as Ref<TxViewlet>
ToDoCreated: '' as Ref<NotificationType>
},
function: {
ToDoTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>

View File

@ -540,19 +540,6 @@ export function createModel (builder: Builder): void {
component: tracker.component.EditIssueTemplate
})
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,
{
objectClass: tracker.class.Issue,
icon: tracker.icon.Issue,
txClass: core.class.TxCreateDoc,
labelComponent: tracker.activity.TxIssueCreated,
display: 'inline'
},
tracker.ids.TxIssueCreated
)
builder.createDoc(
activity.class.DocUpdateMessageViewlet,
core.space.Model,

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type DocUpdateMessageViewlet, type TxViewlet } from '@hcengineering/activity'
import { type DocUpdateMessageViewlet } from '@hcengineering/activity'
import { type ChatMessageViewlet } from '@hcengineering/chunter'
import { type StatusCategory, type Doc, type Ref } from '@hcengineering/core'
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/model-presentation'
@ -48,7 +48,6 @@ export default mergeIds(trackerId, tracker, {
Extensions: '' as IntlString
},
activity: {
TxIssueCreated: '' as AnyComponent,
StatusIcon: '' as AnyComponent,
PriorityIcon: '' as AnyComponent
},
@ -77,7 +76,6 @@ export default mergeIds(trackerId, tracker, {
ProjectList: '' as Ref<Viewlet>
},
ids: {
TxIssueCreated: '' as Ref<TxViewlet>,
TrackerNotificationGroup: '' as Ref<NotificationGroup>,
AssigneeNotification: '' as Ref<NotificationType>,
BaseProjectType: '' as Ref<ProjectType>,

View File

@ -1,58 +1,8 @@
import activity, { type DisplayTx, type SavedMessage } from '@hcengineering/activity'
import core, {
type Class,
type Doc,
type Hierarchy,
type Ref,
SortingOrder,
type Tx,
type TxCreateDoc,
type TxCUD,
type TxMixin,
TxProcessor,
type TxUpdateDoc,
type WithLookup
} from '@hcengineering/core'
import activity, { type SavedMessage } from '@hcengineering/activity'
import { SortingOrder, type WithLookup } from '@hcengineering/core'
import { writable } from 'svelte/store'
import { createQuery, getClient } from '@hcengineering/presentation'
// TODO: remove old code
/**
* @public
*/
export type ActivityKey = string
/**
* @public
*/
// TODO: remove old code
export function activityKey (objectClass: Ref<Class<Doc>>, txClass: Ref<Class<Tx>>): ActivityKey {
return objectClass + ':' + txClass
}
// TODO: remove old code
export function newDisplayTx (
tx: TxCUD<Doc>,
hierarchy: Hierarchy,
isOwnTx: boolean,
originTx: TxCUD<Doc> = tx
): DisplayTx {
const createTx = hierarchy.isDerived(tx._class, core.class.TxCreateDoc) ? (tx as TxCreateDoc<Doc>) : undefined
return {
tx,
isOwnTx,
txes: [],
createTx,
updateTx: hierarchy.isDerived(tx._class, core.class.TxUpdateDoc) ? (tx as TxUpdateDoc<Doc>) : undefined,
updated: false,
removed: false,
mixin: false,
mixinTx: hierarchy.isDerived(tx._class, core.class.TxMixin) ? (tx as TxMixin<Doc, Doc>) : undefined,
doc: createTx !== undefined ? TxProcessor.createDoc2Doc(createTx) : undefined,
originTx
}
}
export const savedMessagesStore = writable<Array<WithLookup<SavedMessage>>>([])
const savedMessagesQuery = createQuery(true)

View File

@ -1,411 +1,14 @@
import { get } from 'svelte/store'
import type { ActivityMessage, DisplayTx, Reaction, TxViewlet } from '@hcengineering/activity'
import core, {
type AttachedDoc,
type Class,
type Client,
type Collection,
type Doc,
type Hierarchy,
type Obj,
type Ref,
type TxCUD,
type TxCollectionCUD,
type TxCreateDoc,
type TxMixin,
type TxOperations,
TxProcessor,
type TxUpdateDoc,
matchQuery,
getCurrentAccount,
isOtherHour
} from '@hcengineering/core'
import { type Asset, type IntlString, getResource, translate } from '@hcengineering/platform'
import { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
import {
type AnyComponent,
type AnySvelteComponent,
ErrorPresenter,
themeStore,
type Location,
getEventPositionElement,
closePopup,
showPopup,
EmojiPopup
} from '@hcengineering/ui'
import view, { type AttributeModel, type BuildModelKey, type BuildModelOptions } from '@hcengineering/view'
import { getObjectPresenter } from '@hcengineering/view-resources'
import type { ActivityMessage, Reaction } from '@hcengineering/activity'
import core, { type Doc, type Ref, type TxOperations, getCurrentAccount, isOtherHour } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { type Location, getEventPositionElement, closePopup, showPopup, EmojiPopup } from '@hcengineering/ui'
import { type AttributeModel } from '@hcengineering/view'
import preference from '@hcengineering/preference'
import { type ActivityKey, activityKey, savedMessagesStore } from './activity'
import { savedMessagesStore } from './activity'
import activity from './plugin'
const valueTypes: ReadonlyArray<Ref<Class<Doc>>> = [
core.class.TypeString,
core.class.EnumOf,
core.class.TypeNumber,
core.class.TypeDate,
core.class.TypeMarkup,
core.class.TypeCollaborativeMarkup
]
export type TxDisplayViewlet =
| (Pick<TxViewlet, 'icon' | 'label' | 'display' | 'editable' | 'hideOnRemove' | 'labelComponent' | 'labelParams'> & {
component?: AnyComponent | AnySvelteComponent
pseudo: boolean
})
| undefined
async function createPseudoViewlet (
client: TxOperations,
dtx: DisplayTx,
label: IntlString,
display: 'inline' | 'content' | 'emphasized' = 'inline'
): Promise<TxDisplayViewlet> {
const docClass: Class<Doc> = client.getModel().getObject(dtx.tx.objectClass)
const language = get(themeStore).language
let trLabel = docClass.label !== undefined ? await translate(docClass.label, {}, language) : undefined
if (dtx.collectionAttribute !== undefined) {
const itemLabel = (dtx.collectionAttribute.type as Collection<AttachedDoc>).itemLabel
if (itemLabel !== undefined) {
trLabel = await translate(itemLabel, {}, language)
}
}
// Check if it is attached doc and collection have title override.
const presenter = await getObjectPresenter(client, dtx.tx.objectClass, { key: 'doc-presenter' }, false, false)
if (presenter !== undefined) {
let collection = ''
if (dtx.collectionAttribute?.label !== undefined) {
collection = await translate(dtx.collectionAttribute.label, {}, language)
}
return {
display,
icon: docClass.icon ?? activity.icon.Activity,
label,
labelParams: {
_class: trLabel,
collection
},
component: presenter.presenter,
pseudo: true
}
}
}
export function getDTxProps (dtx: DisplayTx): any {
return { tx: dtx.tx, value: dtx.doc, isOwnTx: dtx.isOwnTx, prevValue: dtx.prevDoc }
}
function getViewlet (
viewlets: Map<ActivityKey, TxViewlet[]>,
dtx: DisplayTx,
hierarchy: Hierarchy
): TxDisplayViewlet | undefined {
let key: string
if (dtx.mixinTx?.mixin !== undefined && dtx.tx._id === dtx.mixinTx._id) {
key = activityKey(dtx.mixinTx.mixin, dtx.tx._class)
} else {
key = activityKey(dtx.tx.objectClass, dtx.tx._class)
}
const vl = viewlets.get(key)
if (vl !== undefined) {
for (const viewlet of vl) {
if (viewlet.match === undefined) {
return { ...viewlet, pseudo: false }
}
const res = matchQuery([dtx.tx], viewlet.match, dtx.tx._class, hierarchy)
if (res.length > 0) {
return { ...viewlet, pseudo: false }
}
}
}
}
export async function updateViewlet (
client: TxOperations,
viewlets: Map<ActivityKey, TxViewlet[]>,
dtx: DisplayTx
): Promise<{
viewlet: TxDisplayViewlet
id: Ref<TxCUD<Doc>>
model: AttributeModel[]
props: any
modelIcon: Asset | undefined
iconComponent: AnyComponent | undefined
}> {
let viewlet = getViewlet(viewlets, dtx, client.getHierarchy())
const props = getDTxProps(dtx)
let model: AttributeModel[] = []
let modelIcon: Asset | undefined
let iconComponent: AnyComponent | undefined
if (viewlet === undefined) {
;({ viewlet, model } = await checkInlineViewlets(dtx, viewlet, client, model, dtx.isOwnTx))
if (model !== undefined) {
// Check for State attribute
for (const a of model) {
if (a.icon !== undefined) {
modelIcon = a.icon
break
}
}
for (const a of model) {
if (a.attribute?.iconComponent !== undefined) {
iconComponent = a.attribute?.iconComponent
break
}
}
}
}
return { viewlet, id: dtx.tx._id, model, props, modelIcon, iconComponent }
}
async function checkInlineViewlets (
dtx: DisplayTx,
viewlet: TxDisplayViewlet,
client: TxOperations,
model: AttributeModel[],
isOwn: boolean
): Promise<{ viewlet: TxDisplayViewlet, model: AttributeModel[] }> {
if (dtx.collectionAttribute !== undefined && (dtx.txDocIds?.size ?? 0) > 1) {
// Check if we have a class presenter we could have a pseudo viewlet based on class presenter.
viewlet = await createPseudoViewlet(client, dtx, activity.string.CollectionUpdated, 'inline')
} else if (dtx.tx._class === core.class.TxCreateDoc) {
// Check if we have a class presenter we could have a pseudo viewlet based on class presenter.
viewlet = await createPseudoViewlet(client, dtx, isOwn ? activity.string.DocCreated : activity.string.DocAdded)
} else if (dtx.tx._class === core.class.TxRemoveDoc) {
viewlet = await createPseudoViewlet(client, dtx, activity.string.DocDeleted)
} else if (dtx.tx._class === core.class.TxUpdateDoc || dtx.tx._class === core.class.TxMixin) {
model = await createUpdateModel(dtx, client, model)
}
return { viewlet, model }
}
async function getAttributePresenter (
client: Client,
_class: Ref<Class<Obj>>,
key: string,
preserveKey: BuildModelKey
): Promise<AttributeModel> {
const hierarchy = client.getHierarchy()
const attribute = hierarchy.getAttribute(_class, key)
const presenterClass = getAttributePresenterClass(hierarchy, attribute)
const isCollectionAttr = presenterClass.category === 'collection'
const mixin = isCollectionAttr ? view.mixin.CollectionPresenter : view.mixin.ActivityAttributePresenter
let presenterMixin = hierarchy.classHierarchyMixin(presenterClass.attrClass, mixin)
if (presenterMixin?.presenter === undefined && mixin === view.mixin.ActivityAttributePresenter) {
presenterMixin = hierarchy.classHierarchyMixin(presenterClass.attrClass, view.mixin.AttributePresenter)
if (presenterMixin?.presenter === undefined) {
throw new Error('attribute presenter not found for ' + JSON.stringify(preserveKey))
}
} else if (presenterMixin?.presenter === undefined) {
throw new Error('attribute presenter not found for ' + JSON.stringify(preserveKey))
}
const resultKey = preserveKey.sortingKey ?? preserveKey.key
const sortingKey = Array.isArray(resultKey)
? resultKey
: attribute.type._class === core.class.ArrOf
? resultKey + '.length'
: resultKey
const presenter = await getResource(presenterMixin.presenter)
return {
key: preserveKey.key,
sortingKey,
_class: presenterClass.attrClass,
label: preserveKey.label ?? attribute.shortLabel ?? attribute.label,
presenter,
props: preserveKey.props,
icon: presenterMixin.icon,
attribute,
collectionAttr: isCollectionAttr,
isLookup: false
}
}
async function buildModel (options: BuildModelOptions): Promise<AttributeModel[]> {
// eslint-disable-next-line array-callback-return
const model = options.keys
.map((key) => (typeof key === 'string' ? { key } : key))
.map(async (key) => {
try {
return await getAttributePresenter(options.client, options._class, key.key, key)
} catch (err: any) {
if (options.ignoreMissing ?? false) {
return undefined
}
const stringKey = key.label ?? key.key
console.error('Failed to find presenter for', key, err)
const errorPresenter: AttributeModel = {
key: '',
sortingKey: '',
presenter: ErrorPresenter,
label: stringKey as IntlString,
_class: core.class.TypeString,
props: { error: err },
collectionAttr: false,
isLookup: false
}
return errorPresenter
}
})
return (await Promise.all(model)).filter((a) => a !== undefined) as AttributeModel[]
}
async function createUpdateModel (
dtx: DisplayTx,
client: TxOperations,
model: AttributeModel[]
): Promise<AttributeModel[]> {
if (dtx.updateTx !== undefined) {
const _class = dtx.updateTx.objectClass
const ops = {
client,
_class,
keys: Object.entries(dtx.updateTx.operations)
.flatMap(([id, val]) => (['$push', '$pull'].includes(id) ? Object.keys(val) : id))
.filter((id) => !id.startsWith('$')),
ignoreMissing: true
}
const hiddenAttrs = getHiddenAttrs(client, _class)
model = (await buildModel(ops)).filter((x) => !hiddenAttrs.has(x.key))
} else if (dtx.mixinTx !== undefined) {
const _class = dtx.mixinTx.mixin
const ops = {
client,
_class,
keys: Object.keys(dtx.mixinTx.attributes).filter((id) => !id.startsWith('$')),
ignoreMissing: true
}
const hiddenAttrs = getHiddenAttrs(client, _class)
model = (await buildModel(ops)).filter((x) => !hiddenAttrs.has(x.key))
}
return model
}
function getHiddenAttrs (client: TxOperations, _class: Ref<Class<Doc>>): Set<string> {
return new Set(
[...client.getHierarchy().getAllAttributes(_class).entries()]
.filter(([, attr]) => attr.hidden === true)
.map(([k]) => k)
)
}
function getModifiedAttributes (tx: DisplayTx): any[] {
if (tx.tx._class === core.class.TxUpdateDoc) {
return ([tx.tx, ...tx.txes.map(({ tx }) => tx)] as unknown as Array<TxUpdateDoc<Doc>>).map(
({ operations }) => operations
)
}
if (tx.tx._class === core.class.TxMixin) {
return ([tx.tx, ...tx.txes.map(({ tx }) => tx)] as unknown as Array<TxMixin<Doc, Doc>>).map(
({ attributes }) => attributes
)
}
return [{}]
}
async function buildRemovedDoc (
client: TxOperations,
objectId: Ref<Doc>,
_class: Ref<Class<Doc>>
): Promise<Doc | undefined> {
const isAttached = client.getHierarchy().isDerived(_class, core.class.AttachedDoc)
const txes = await client.findAll<TxCUD<Doc>>(
isAttached ? core.class.TxCollectionCUD : core.class.TxCUD,
isAttached
? { 'tx.objectId': objectId as Ref<AttachedDoc> }
: {
objectId
},
{ sort: { modifiedOn: 1 } }
)
const createTx = isAttached
? txes.find((tx) => (tx as TxCollectionCUD<Doc, AttachedDoc>).tx._class === core.class.TxCreateDoc)
: txes.find((tx) => tx._class === core.class.TxCreateDoc)
if (createTx === undefined) return
let doc = TxProcessor.createDoc2Doc(createTx as TxCreateDoc<Doc>)
for (let tx of txes) {
tx = TxProcessor.extractTx(tx) as TxCUD<Doc>
if (tx._class === core.class.TxUpdateDoc) {
doc = TxProcessor.updateDoc2Doc(doc, tx as TxUpdateDoc<Doc>)
} else if (tx._class === core.class.TxMixin) {
const mixinTx = tx as TxMixin<Doc, Doc>
doc = TxProcessor.updateMixin4Doc(doc, mixinTx)
}
}
return doc
}
async function getAllRealValues (
client: TxOperations,
values: any[],
_class: Ref<Class<Doc>>
): Promise<[any[], boolean]> {
if (values.length === 0) return [[], false]
if (values.some((value) => typeof value !== 'string')) {
return [values, false]
}
if (valueTypes.includes(_class)) {
return [values, false]
}
const realValues = await client.findAll(_class, { _id: { $in: values } })
const realValuesIds = realValues.map(({ _id }) => _id)
const res = [
...realValues,
...(await Promise.all(
values
.filter((value) => !realValuesIds.includes(value))
.map(async (value) => await buildRemovedDoc(client, value, _class))
))
].filter((v) => v != null)
return [res, true]
}
function combineAttributes (attributes: any[], key: string, operator: string, arrayKey: string): any[] {
return Array.from(
new Set(
attributes.flatMap((attr) =>
Array.isArray(attr[operator]?.[key]?.[arrayKey]) ? attr[operator]?.[key]?.[arrayKey] : attr[operator]?.[key]
)
)
).filter((v) => v != null)
}
interface TxAttributeValue {
set: any
isObjectSet: boolean
added: any[]
isObjectAdded: boolean
removed: any[]
isObjectRemoved: boolean
}
export async function getValue (client: TxOperations, m: AttributeModel, tx: DisplayTx): Promise<TxAttributeValue> {
const utxs = getModifiedAttributes(tx)
const added = await getAllRealValues(client, combineAttributes(utxs, m.key, '$push', '$each'), m._class)
const removed = await getAllRealValues(client, combineAttributes(utxs, m.key, '$pull', '$in'), m._class)
const value: TxAttributeValue = {
set: utxs[0][m.key],
isObjectSet: false,
added: added[0],
isObjectAdded: added[1],
removed: removed[0],
isObjectRemoved: removed[1]
}
if (value.set !== undefined) {
const res = await getAllRealValues(client, [value.set], m._class)
value.set = res[0][0]
value.isObjectSet = res[1]
}
return value
}
export async function updateDocReactions (
client: TxOperations,
reactions: Reaction[],

View File

@ -17,9 +17,7 @@ import { Person } from '@hcengineering/contact'
import {
Account,
AttachedDoc,
Attribute,
Class,
Collection,
Doc,
DocumentQuery,
Mixin,
@ -27,83 +25,13 @@ import {
type RelatedDocument,
Timestamp,
Tx,
TxCreateDoc,
TxCUD,
TxMixin,
TxUpdateDoc
TxCUD
} from '@hcengineering/core'
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { Preference } from '@hcengineering/preference'
import type { AnyComponent } from '@hcengineering/ui'
// TODO: remove TxViewlet
/**
* Define an display for all transaction kinds for particular class.
* @public
*/
export interface TxViewlet extends Doc {
icon: Asset
objectClass: Ref<Class<Doc>>
txClass: Ref<Class<Tx>>
// Component to display on.
component?: AnyComponent
// If defined, will be added to label displayed
labelComponent?: AnyComponent
// Filter
match?: DocumentQuery<Tx>
// Label will be displayed right after author
label?: IntlString
labelParams?: any
// Do component need to be emphasized or not.
display: 'inline' | 'content' | 'emphasized'
// If defined and true, will show context menu with Edit action, and will pass 'edit:true' to viewlet properties.
editable?: boolean
// If defined and true, will hide all transactions from object in case it is deleted.
hideOnRemove?: boolean
}
// TODO: remove DisplayTx
/**
* Transaction being displayed.
* @public
*/
export interface DisplayTx {
// Source tx
tx: TxCUD<Doc>
// A set of collapsed transactions.
txes: DisplayTx[]
txDocIds?: Set<Ref<Doc>>
// type check for createTx
createTx?: TxCreateDoc<Doc>
// Type check for updateTx
updateTx?: TxUpdateDoc<Doc>
// Type check for updateTx
mixinTx?: TxMixin<Doc, Doc>
// Document in case it is required.
doc?: Doc
// Previous document in case it is required.
prevDoc?: Doc
updated: boolean
mixin: boolean
removed: boolean
isOwnTx: boolean
collectionAttribute?: Attribute<Collection<AttachedDoc>>
originTx: TxCUD<Doc>
}
/**
* @public
*/
@ -323,7 +251,6 @@ export default plugin(activityId, {
IgnoreActivity: '' as Ref<Mixin<IgnoreActivity>>
},
class: {
TxViewlet: '' as Ref<Class<TxViewlet>>,
DocUpdateMessage: '' as Ref<Class<DocUpdateMessage>>,
ActivityMessage: '' as Ref<Class<ActivityMessage>>,
ActivityInfoMessage: '' as Ref<Class<ActivityInfoMessage>>,

View File

@ -1,31 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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 type { Attachment } from '@hcengineering/attachment'
import core, { TxCUD, TxCreateDoc, TxProcessor } from '@hcengineering/core'
import AttachmentPresenter from '../AttachmentPresenter.svelte'
import AttachmentName from '../AttachmentName.svelte'
export let tx: TxCUD<Attachment>
export let value: any
const doc = tx._class === core.class.TxCreateDoc ? TxProcessor.createDoc2Doc(tx as TxCreateDoc<Attachment>) : value
</script>
{#if tx._class === core.class.TxRemoveDoc}
<AttachmentName value={doc} />
{:else}
<AttachmentPresenter value={doc} />
{/if}

View File

@ -20,7 +20,6 @@ import preference from '@hcengineering/preference'
import { PDFViewer, deleteFile, getClient, uploadFile } from '@hcengineering/presentation'
import activity, { type ActivityMessage, type DocUpdateMessage } from '@hcengineering/activity'
import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte'
import AddAttachment from './components/AddAttachment.svelte'
import AttachmentDocList from './components/AttachmentDocList.svelte'
import AttachmentDroppable from './components/AttachmentDroppable.svelte'
@ -261,7 +260,6 @@ export default async (): Promise<Resources> => ({
PDFViewer
},
activity: {
TxAttachmentCreate,
AttachmentsUpdatedMessage
},
helper: {

View File

@ -1,52 +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 { Event } from '@hcengineering/calendar'
import { Ref, TxUpdateDoc } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { DateTimePresenter, showPanel } from '@hcengineering/ui'
import view from '@hcengineering/view'
import calendar from '../../plugin'
export let tx: TxUpdateDoc<Event>
const client = getClient()
async function getEvent (_id: Ref<Event>): Promise<Event | undefined> {
return await client.findOne(calendar.class.Event, { _id })
}
function click (event: Event): void {
showPanel(view.component.EditDoc, event._id, event._class, 'content')
}
</script>
<div class="flex">
{#await getEvent(tx.objectId) then event}
{#if event}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="over-underline caption-color flex-row-center"
on:click={() => {
click(event)
}}
>{event.title}
</span>
&nbsp
<DateTimePresenter value={event.date} />
{/if}
{/await}
</div>

View File

@ -29,7 +29,6 @@ import IntegrationConnect from './components/IntegrationConnect.svelte'
import PersonsPresenter from './components/PersonsPresenter.svelte'
import SaveEventReminder from './components/SaveEventReminder.svelte'
import UpdateRecInstancePopup from './components/UpdateRecInstancePopup.svelte'
import ReminderViewlet from './components/activity/ReminderViewlet.svelte'
import CalendarIntegrationIcon from './components/icons/Calendar.svelte'
import EventElement from './components/EventElement.svelte'
import CalendarEventPresenter from './components/CalendarEventPresenter.svelte'
@ -192,9 +191,6 @@ export default async (): Promise<Resources> => ({
IntegrationConfigure,
ConnectApp
},
activity: {
ReminderViewlet
},
actionImpl: {
SaveEventReminder: saveEventReminder,
DeleteRecEvent: deleteRecEvent

View File

@ -15,12 +15,8 @@
import calendar, { calendarId } from '@hcengineering/calendar'
import { type IntlString, mergeIds } from '@hcengineering/platform'
import { type AnyComponent } from '@hcengineering/ui'
export default mergeIds(calendarId, calendar, {
activity: {
ReminderViewlet: '' as AnyComponent
},
string: {
Events: '' as IntlString,
RemindMeAt: '' as IntlString,

View File

@ -1,63 +0,0 @@
<script lang="ts">
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
import chunter, { DirectMessage, Message, getDirectChannel } from '@hcengineering/chunter'
import { PersonAccount } from '@hcengineering/contact'
import { Ref, generateId, getCurrentAccount } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
export let account: PersonAccount
export let loading: boolean = true
const client = getClient()
const me = getCurrentAccount()._id
const _class = chunter.class.Message
let messageId: Ref<Message> = generateId()
let space: Ref<DirectMessage> | undefined
$: _getDirectChannel(account?._id)
async function _getDirectChannel (account?: Ref<PersonAccount>): Promise<void> {
if (account === undefined) {
return
}
space = await getDirectChannel(client, me as Ref<PersonAccount>, account)
}
async function onMessage (event: CustomEvent) {
if (space === undefined) {
return
}
const { message, attachments } = event.detail
await client.addCollection(
_class,
space,
space,
chunter.class.DirectMessage,
'messages',
{
content: message,
createBy: me,
attachments
},
messageId
)
messageId = generateId()
}
</script>
{#if space !== undefined}
<div class="reference">
<AttachmentRefInput bind:loading {space} {_class} objectId={messageId} on:message={onMessage} />
</div>
{/if}
<style lang="scss">
.reference {
margin: 1.25rem 2.5rem;
}
</style>

View File

@ -1,25 +0,0 @@
<script lang="ts">
import { chunterId, DirectMessage, Message } from '@hcengineering/chunter'
import { createQuery, getClient, MessageViewer } from '@hcengineering/presentation'
import { NavLink } from '@hcengineering/view-resources'
import chunter from '../plugin'
import { getDmName } from '../utils'
export let value: Message
const client = getClient()
let dm: DirectMessage | undefined
const query = createQuery()
$: query.query(chunter.class.DirectMessage, { _id: value.space }, (result) => {
dm = result[0]
})
</script>
{#if dm}
{#await getDmName(client, dm) then name}
<NavLink app={chunterId} space={value.space}>
<span class="label">{name}</span>
</NavLink>
<div><MessageViewer message={value.content} /></div>
{/await}
{/if}

View File

@ -26,10 +26,8 @@ import ChunterBrowser from './components/chat/specials/ChunterBrowser.svelte'
import ConvertDmToPrivateChannelModal from './components/ConvertDmToPrivateChannel.svelte'
import CreateChannel from './components/chat/create/CreateChannel.svelte'
import CreateDirectChat from './components/chat/create/CreateDirectChat.svelte'
import DirectMessagePresenter from './components/DirectMessagePresenter.svelte'
import DmHeader from './components/DmHeader.svelte'
import DmPresenter from './components/DmPresenter.svelte'
import DirectMessageInput from './components/DirectMessageInput.svelte'
import EditChannel from './components/EditChannel.svelte'
import ChannelPreview from './components/ChannelPreview.svelte'
import ThreadView from './components/threads/ThreadView.svelte'
@ -160,12 +158,10 @@ export default async (): Promise<Resources> => ({
ChannelHeader,
ChannelPanel,
ChannelPresenter,
DirectMessagePresenter,
ChannelPreview,
ChunterBrowser,
DmHeader,
DmPresenter,
DirectMessageInput,
EditChannel,
ThreadView,
SavedMessages,

View File

@ -32,7 +32,6 @@ export default mergeIds(chunterId, chunter, {
EditChannel: '' as AnyComponent,
ChannelPreview: '' as AnyComponent,
MessagePreview: '' as AnyComponent,
DirectMessageInput: '' as AnyComponent,
CreateDocChannel: '' as AnyComponent,
SavedMessages: '' as AnyComponent,
Threads: '' as AnyComponent,

View File

@ -14,8 +14,7 @@
//
import { ActivityMessage, ActivityMessageViewlet } from '@hcengineering/activity'
import type { Person } from '@hcengineering/contact'
import type { Account, AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@hcengineering/core'
import type { Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@hcengineering/core'
import { NotificationType } from '@hcengineering/notification'
import type { Asset, Plugin } from '@hcengineering/platform'
import { IntlString, plugin } from '@hcengineering/platform'
@ -25,9 +24,7 @@ import { Action } from '@hcengineering/view'
/**
* @public
*/
export interface ChunterSpace extends Space {
lastMessage?: Timestamp
}
export interface ChunterSpace extends Space {}
/**
* @public
@ -41,42 +38,6 @@ export interface Channel extends ChunterSpace {
*/
export interface DirectMessage extends ChunterSpace {}
/**
* @public
* @deprecated use ChatMessage instead
*/
export interface ChunterMessage extends AttachedDoc {
content: Markup
attachments?: number
createBy: Ref<Account>
editedOn?: Timestamp
reactions?: number
}
/**
* @public
*/
export interface ChunterMessageExtension extends ChunterMessage {}
/**
* @public
* @deprecated use ChatMessage instead
*/
export interface Message extends ChunterMessage {
attachedTo: Ref<Space>
attachedToClass: Ref<Class<Space>>
replies?: Ref<Person>[]
repliesCount?: number
lastReply?: Timestamp
}
/**
* @public
*/
export interface DirectMessageInput extends Class<Doc> {
component: AnyComponent
}
/**
* @public
*/
@ -141,8 +102,6 @@ export default plugin(chunterId, {
ThreadMessagePreview: '' as AnyComponent
},
class: {
Message: '' as Ref<Class<Message>>,
ChunterMessage: '' as Ref<Class<ChunterMessage>>,
ThreadMessage: '' as Ref<Class<ThreadMessage>>,
ChunterSpace: '' as Ref<Class<ChunterSpace>>,
Channel: '' as Ref<Class<Channel>>,
@ -151,8 +110,6 @@ export default plugin(chunterId, {
ChatMessageViewlet: '' as Ref<Class<ChatMessageViewlet>>
},
mixin: {
DirectMessageInput: '' as Ref<Mixin<DirectMessageInput>>,
ChunterMessageExtension: '' as Ref<Mixin<ChunterMessageExtension>>,
ObjectChatPanel: '' as Ref<Mixin<ObjectChatPanel>>
},
string: {

View File

@ -1,34 +0,0 @@
<!--
// Copyright © 2023 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 { Person, formatName } from '@hcengineering/contact'
import { TxUpdateDoc } from '@hcengineering/core'
import contact from '../../plugin'
import { Label } from '@hcengineering/ui'
import activity from '@hcengineering/activity'
export let tx: TxUpdateDoc<Person>
const val = tx.operations.name
</script>
{#if val}
<span class="lower"><Label label={activity.string.Changed} /></span>
<span class="lower"><Label label={contact.string.Name} /></span>
<span class="lower"><Label label={activity.string.To} /></span>
<span class="strong overflow-label">
{formatName(val)}
</span>
{/if}

View File

@ -117,7 +117,6 @@ import UsersList from './components/UsersList.svelte'
import UsersPopup from './components/UsersPopup.svelte'
import ActivityChannelPresenter from './components/activity/ActivityChannelPresenter.svelte'
import NameChangedActivityMessage from './components/activity/NameChangedActivityMessage.svelte'
import TxNameChange from './components/activity/TxNameChange.svelte'
import IconAddMember from './components/icons/AddMember.svelte'
import ExpandRightDouble from './components/icons/ExpandRightDouble.svelte'
import IconMembers from './components/icons/Members.svelte'
@ -320,7 +319,6 @@ export default async (): Promise<Resources> => ({
OpenChannel: openChannelURL
},
activity: {
TxNameChange,
NameChangedActivityMessage
},
component: {

View File

@ -1,40 +0,0 @@
<!--
//
// Copyright © 2023 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 { Ref } from '@hcengineering/core'
import { Request } from '@hcengineering/request'
import { createQuery } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import { ControlledDocument } from '@hcengineering/controlled-documents'
import documents from '../../plugin'
export let value: Request
let controlledDoc: ControlledDocument | undefined
const docQuery = createQuery()
$: docQuery.query(documents.class.ControlledDocument, { _id: value.attachedTo as Ref<ControlledDocument> }, (rev) => {
;[controlledDoc] = rev
})
</script>
{#if controlledDoc}
<span>
<Label label={documents.string.DocumentApprovalRequest} />
"{controlledDoc.title}"
</span>
{/if}

View File

@ -1,40 +0,0 @@
<!--
//
// Copyright © 2023 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 { Ref } from '@hcengineering/core'
import { Request } from '@hcengineering/request'
import { createQuery } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import { ControlledDocument } from '@hcengineering/controlled-documents'
import documents from '../../plugin'
export let value: Request
let controlledDoc: ControlledDocument | undefined
const docQuery = createQuery()
$: docQuery.query(documents.class.ControlledDocument, { _id: value.attachedTo as Ref<ControlledDocument> }, (rev) => {
;[controlledDoc] = rev
})
</script>
{#if controlledDoc}
<span>
<Label label={documents.string.DocumentReviewRequest} />
"{controlledDoc.title}"
</span>
{/if}

View File

@ -80,10 +80,8 @@ import DocumentVersionPresenter from './components/document/presenters/DocumentV
import DocumentSectionDeletePopup from './components/DocumentSectionDeletePopup.svelte'
import TxCreateReviewRequest from './components/activity/TxCreateReviewRequest.svelte'
import DocumentReviewRequest from './components/requests/DocumentReviewRequest.svelte'
import DocumentReviewRequestPresenter from './components/requests/DocumentReviewRequestPresenter.svelte'
import TxCreateApprovalRequest from './components/activity/TxCreateApprovalRequest.svelte'
import DocumentApprovalRequest from './components/requests/DocumentApprovalRequest.svelte'
import DocumentApprovalRequestPresenter from './components/requests/DocumentApprovalRequestPresenter.svelte'
import ControlledStateFilterValuePresenter from './components/document/presenters/ControlledStateFilterValuePresenter.svelte'
@ -331,10 +329,8 @@ export default async (): Promise<Resources> => ({
DocumentSectionDeletePopup,
CollaborativeSectionPresenter,
AttachmentsSectionPresenter,
TxCreateReviewRequest,
DocumentReviewRequest,
DocumentReviewRequestPresenter,
TxCreateApprovalRequest,
DocumentApprovalRequest,
DocumentApprovalRequestPresenter,
StateFilterValuePresenter,

View File

@ -42,11 +42,9 @@ export default mergeIds(documentsId, documents, {
DocumentReviewRequest: '' as AnyComponent,
DocumentReviewRequestPresenter: '' as AnyComponent,
TxCreateReviewRequest: '' as AnyComponent,
DocumentApprovalRequest: '' as AnyComponent,
DocumentApprovalRequestPresenter: '' as AnyComponent,
TxCreateApprovalRequest: '' as AnyComponent,
CreateDocumentsSpace: '' as AnyComponent
},

View File

@ -1,4 +1,3 @@
import { TxViewlet } from '@hcengineering/activity'
import {
type Class,
type Doc,
@ -282,8 +281,6 @@ export const documentsPlugin = plugin(documentsId, {
ConfigDescription: '' as IntlString
},
ids: {
TxReviewRequestCreated: '' as Ref<TxViewlet>,
TxApprovalRequestCreated: '' as Ref<TxViewlet>,
NoParent: '' as Ref<DocumentMeta>,
NoProject: '' as Ref<Project>
},

View File

@ -1,25 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 type { TxCreateDoc } from '@hcengineering/core'
import { TxProcessor } from '@hcengineering/core'
import { SharedMessages } from '@hcengineering/gmail'
import SharedMessagesView from '../SharedMessages.svelte'
export let tx: TxCreateDoc<SharedMessages>
</script>
<SharedMessagesView value={TxProcessor.createDoc2Doc(tx)} />

View File

@ -1,34 +0,0 @@
<!--
// Copyright © 2023 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 { Message } from '@hcengineering/gmail'
import { getClient } from '@hcengineering/presentation'
import { showPopup } from '@hcengineering/ui'
import Main from '../Main.svelte'
export let value: Message
async function click () {
const client = getClient()
const channel = await client.findOne(value.attachedToClass, { _id: value.attachedTo })
if (channel !== undefined) {
showPopup(Main, { _id: channel.attachedTo, _class: channel.attachedToClass, message: value }, 'float')
}
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span class="over-underline overflow-label" on:click={click}>{value.subject}</span>

View File

@ -17,8 +17,6 @@
import { concatLink } from '@hcengineering/core'
import { getMetadata, type Resources } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import TxSharedCreate from './components/activity/TxSharedCreate.svelte'
import TxWriteMessage from './components/activity/TxWriteMessage.svelte'
import GmailWriteMessage from './components/activity/GmailWriteMessage.svelte'
import GmailSharedMessage from './components/activity/GmailSharedMessage.svelte'
import Configure from './components/Configure.svelte'
@ -38,8 +36,6 @@ export default async (): Promise<Resources> => ({
Configure
},
activity: {
TxSharedCreate,
TxWriteMessage,
GmailWriteMessage,
GmailSharedMessage
},

View File

@ -1,249 +0,0 @@
<!--
// Copyright © 2023 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 { createEventDispatcher } from 'svelte'
import activity, { TxViewlet } from '@hcengineering/activity'
import { activityKey, ActivityKey } from '@hcengineering/activity-resources'
import chunter from '@hcengineering/chunter'
import { Employee, getName, PersonAccount } from '@hcengineering/contact'
import { Avatar, employeeByIdStore, personAccountByIdStore } from '@hcengineering/contact-resources'
import core, { Account, Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
import notification, { DocUpdates } from '@hcengineering/notification'
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
import { AnySvelteComponent, Button, Loading, Scroller } from '@hcengineering/ui'
import NotificationView from './NotificationView.svelte'
import { getResource } from '@hcengineering/platform'
export let accountId: Ref<Account>
const dispatch = createEventDispatcher()
const query = createQuery()
let docs: DocUpdates[] = []
let filteredDocs: DocUpdates[] = []
let loading = true
const me = getCurrentAccount()._id
$: query.query(
notification.class.DocUpdates,
{
user: me,
hidden: false
},
(res) => {
docs = res
updateDocs(accountId, true)
loading = false
},
{
sort: {
lastTxTime: -1
}
}
)
$: updateDocs(accountId)
function updateDocs (accountId: Ref<Account>, forceUpdate = false) {
if (loading && !forceUpdate) {
return
}
const filtered: DocUpdates[] = []
for (const doc of docs) {
if (doc.txes.length === 0) continue
const txes = doc.txes.filter((p) => p.modifiedBy === accountId)
if (txes.length > 0) {
filtered.push({
...doc,
txes
})
}
}
filteredDocs = filtered
}
function markAsRead (index: number) {
if (filteredDocs[index] !== undefined) {
filteredDocs[index].txes.forEach((p) => (p.isNew = false))
filteredDocs[index].txes = filteredDocs[index].txes
filteredDocs = filteredDocs
}
}
function changeSelected (index: number) {
if (filteredDocs[index] !== undefined) {
dispatch('change', filteredDocs[index])
markAsRead(index)
} else if (filteredDocs.length > 0) {
if (index < filteredDocs.length - 1) {
selected++
} else {
selected--
}
changeSelected(selected)
} else {
selected = 0
dispatch('change', undefined)
}
}
let viewlets: Map<ActivityKey, TxViewlet[]>
const descriptors = createQuery()
descriptors.query(activity.class.TxViewlet, {}, (result) => {
viewlets = new Map()
for (const res of result) {
const key = activityKey(res.objectClass, res.txClass)
const arr = viewlets.get(key) ?? []
arr.push(res)
viewlets.set(key, arr)
}
viewlets = viewlets
})
let selected = 0
let employee: Employee | undefined = undefined
$: newTxes = filteredDocs.reduce((acc, cur) => acc + cur.txes.filter((p) => p.isNew).length, 0) // items.length
$: account = $personAccountByIdStore.get(accountId as Ref<PersonAccount>)
$: employee = account ? $employeeByIdStore.get(account.person as Ref<Employee>) : undefined
const client = getClient()
const hierarchy = client.getHierarchy()
async function openDM () {
const res = await client.findAll(chunter.class.DirectMessage, { members: accountId })
const current = res.find((p) => p.members.includes(me) && p.members.length === 2)
if (current !== undefined) {
dispatch('dm', current._id)
} else {
const id = await client.createDoc(chunter.class.DirectMessage, core.space.Space, {
name: '',
description: '',
private: true,
archived: false,
members: [me, accountId]
})
dispatch('dm', id)
}
}
function onKeydown (key: KeyboardEvent): void {
if (key.code === 'ArrowUp') {
key.stopPropagation()
key.preventDefault()
selected--
changeSelected(selected)
}
if (key.code === 'ArrowDown') {
key.stopPropagation()
key.preventDefault()
selected++
changeSelected(selected)
}
if (key.code === 'Enter') {
key.preventDefault()
key.stopPropagation()
changeSelected(selected)
}
}
let dmInput: AnySvelteComponent | undefined = undefined
$: dmInputRes = hierarchy.classHierarchyMixin(
chunter.class.DirectMessage as Ref<Class<Doc>>,
chunter.mixin.DirectMessageInput
)?.component
$: if (dmInputRes) {
getResource(dmInputRes).then((res) => (dmInput = res))
}
</script>
<ActionContext
context={{
mode: 'browser'
}}
/>
<div class="flex-between header bottom-divider">
<div class="flex-row-center">
{#if employee}
<Avatar size={'smaller'} person={employee} name={employee.name} />
<span class="font-medium mx-2">{getName(client.getHierarchy(), employee)}</span>
{/if}
{#if newTxes > 0}
<span class="counter">
{newTxes}
</span>
{/if}
</div>
{#if me !== accountId}
<Button label={chunter.string.Message} kind="primary" on:click={openDM} />
{/if}
</div>
<div class="inbox-activity">
<Scroller noStretch>
{#if loading}
<Loading />
{:else}
{#each filteredDocs as item, i (item._id)}
<div class="with-hover">
<NotificationView
value={item}
selected={false}
{viewlets}
on:keydown={onKeydown}
on:click={() => {
selected = i
changeSelected(selected)
}}
preview
/>
</div>
{/each}
{/if}
</Scroller>
</div>
{#if dmInput && account}
<svelte:component this={dmInput} {account} bind:loading />
{/if}
<style lang="scss">
.header {
flex-shrink: 0;
padding: 0.625rem 1.25rem 0.625rem 1.75rem;
min-width: 0;
min-height: 3.25rem;
background-color: var(--theme-comp-header-color);
}
.counter {
display: flex;
align-items: center;
justify-content: center;
height: 1.375rem;
width: 1.375rem;
color: var(--theme-inbox-people-notify);
background-color: var(--theme-inbox-people-counter-bgcolor);
border-radius: 50%;
}
.with-hover {
&:hover {
background-color: var(--theme-inbox-activitymsg-bgcolor);
}
}
</style>

View File

@ -1,187 +0,0 @@
<!--
// Copyright © 2023 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 { createEventDispatcher } from 'svelte'
import activity, { TxViewlet } from '@hcengineering/activity'
import { activityKey, ActivityKey } from '@hcengineering/activity-resources'
import { PersonAccount } from '@hcengineering/contact'
import { personAccountByIdStore } from '@hcengineering/contact-resources'
import { Account, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
import notification, { DocUpdates } from '@hcengineering/notification'
import { createQuery } from '@hcengineering/presentation'
import { Loading, Scroller } from '@hcengineering/ui'
import PeopleNotificationView from './PeopleNotificationsView.svelte'
export let filter: 'all' | 'read' | 'unread' = 'all'
export let _id: Ref<Doc> | undefined
const query = createQuery()
let docs: DocUpdates[] = []
let map = new Map<Ref<Account>, DocUpdates[]>()
let accounts: PersonAccount[] = []
let loading = true
$: query.query(
notification.class.DocUpdates,
{
user: getCurrentAccount()._id,
hidden: false
},
async (res) => {
docs = res
await getFiltered(docs, filter)
loading = false
},
{
sort: {
lastTxTime: -1
}
}
)
async function getFiltered (docs: DocUpdates[], filter: 'all' | 'read' | 'unread'): Promise<void> {
const filtered: DocUpdates[] = []
for (const doc of docs) {
if (doc.txes.length === 0) continue
if (filter === 'read') {
const txes = doc.txes.filter((p) => !p.isNew)
if (txes.length > 0) {
filtered.push({
...doc,
txes
})
}
} else if (filter === 'unread') {
const txes = doc.txes.filter((p) => p.isNew)
if (txes.length > 0) {
filtered.push({
...doc,
txes
})
}
} else {
filtered.push(doc)
}
}
map.clear()
for (const item of filtered) {
for (const tx of item.txes) {
const arr = map.get(tx.modifiedBy) ?? []
if (arr.findIndex((p) => p._id === item._id) === -1) {
arr.push(item)
map.set(tx.modifiedBy, arr)
}
}
}
map = map
accounts = Array.from(map.keys())
.map((p) => $personAccountByIdStore.get(p as Ref<PersonAccount>))
.filter((p) => p !== undefined) as PersonAccount[]
if (_id === undefined) {
changeSelected(selected)
} else {
const index = filtered.findIndex((p) => p.attachedTo === _id)
if (index === -1) {
changeSelected(selected)
} else {
const doc = filtered[index]
const acc = accounts.findIndex((p) => p._id === doc.txes[doc.txes.length - 1].modifiedBy)
if (acc !== -1) {
selected = acc
changeSelected(selected)
}
}
}
}
$: getFiltered(docs, filter)
function changeSelected (index: number) {
if (accounts[index] === undefined) {
if (accounts.length > 0) {
if (index < accounts.length - 1) {
selected++
} else {
selected--
}
changeSelected(selected)
} else {
selected = 0
}
}
}
let viewlets: Map<ActivityKey, TxViewlet[]>
const descriptors = createQuery()
descriptors.query(activity.class.TxViewlet, {}, (result) => {
viewlets = new Map()
for (const res of result) {
const key = activityKey(res.objectClass, res.txClass)
const arr = viewlets.get(key) ?? []
arr.push(res)
viewlets.set(key, arr)
}
viewlets = viewlets
})
let selected = 0
const dispatch = createEventDispatcher()
function onKeydown (key: KeyboardEvent): void {
if (key.code === 'ArrowUp') {
key.stopPropagation()
key.preventDefault()
selected--
changeSelected(selected)
}
if (key.code === 'ArrowDown') {
key.stopPropagation()
key.preventDefault()
selected++
changeSelected(selected)
}
if (key.code === 'Enter') {
key.preventDefault()
key.stopPropagation()
dispatch('open', accounts[selected]._id)
}
}
</script>
<div class="inbox-activity py-2">
<Scroller noStretch>
{#if loading}
<Loading />
{:else}
{#each accounts as account, i}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<PeopleNotificationView
value={account}
items={map.get(account._id) ?? []}
selected={selected === i}
{viewlets}
on:keydown={onKeydown}
on:open
on:open={() => {
selected = i
}}
/>
{/each}
{/if}
</Scroller>
</div>

View File

@ -1,128 +0,0 @@
<!--
// Copyright © 2023 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 { createEventDispatcher } from 'svelte'
import { TxViewlet } from '@hcengineering/activity'
import { ActivityKey } from '@hcengineering/activity-resources'
import { PersonAccount, getName } from '@hcengineering/contact'
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
import core, { Doc, TxCUD, TxProcessor } from '@hcengineering/core'
import notification, { DocUpdates } from '@hcengineering/notification'
import { getResource } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { ActionIcon, AnySvelteComponent, Label, TimeSince } from '@hcengineering/ui'
import view from '@hcengineering/view'
import TxView from './TxView.svelte'
import ArrowRight from './icons/ArrowRight.svelte'
export let value: PersonAccount
export let items: DocUpdates[]
export let viewlets: Map<ActivityKey, TxViewlet[]>
export let selected: boolean
$: firstItem = items[0]
$: employee = $personByIdStore.get(value.person)
$: newTxes = items.reduce((acc, cur) => acc + cur.txes.filter((p) => p.isNew && p.modifiedBy === value._id).length, 0) // items.length
const dispatch = createEventDispatcher()
let doc: Doc | undefined = undefined
let tx: TxCUD<Doc> | undefined = undefined
const client = getClient()
const hierarchy = client.getHierarchy()
$: txRef = firstItem ? firstItem.txes[firstItem.txes.length - 1]._id : undefined
$: txRef &&
client.findOne(core.class.TxCUD, { _id: txRef }).then((res) => {
if (res !== undefined) {
tx = TxProcessor.extractTx(res) as TxCUD<Doc>
} else {
tx = res
}
})
let presenter: AnySvelteComponent | undefined = undefined
$: presenterRes =
hierarchy.classHierarchyMixin(firstItem.attachedToClass, notification.mixin.NotificationObjectPresenter)
?.presenter ?? hierarchy.classHierarchyMixin(firstItem.attachedToClass, view.mixin.ObjectPresenter)?.presenter
$: if (presenterRes) {
getResource(presenterRes).then((res) => (presenter = res))
}
const docQuery = createQuery()
$: docQuery.query(firstItem.attachedToClass, { _id: firstItem.attachedTo }, (res) => {
;[doc] = res
})
let div: HTMLDivElement
$: if (selected && div != null) div.focus()
</script>
{#if doc}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="inbox-activity__container"
on:keydown
class:selected
tabindex="-1"
bind:this={div}
on:click={() => dispatch('open', value._id)}
>
{#if newTxes > 0 && !selected}<div class="notify people" />{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="inbox-activity__content shrink flex-grow clear-mins" class:read={newTxes === 0}>
<div class="flex-row-center gap-2">
<Avatar person={employee} size={'small'} name={employee?.name} />
{#if employee}
<span class="font-medium">{getName(client.getHierarchy(), employee)}</span>
{:else}
<span class="font-medium"><Label label={core.string.System} /></span>
{/if}
{#if newTxes > 0}
<div class="counter people">
{newTxes}
</div>
{/if}
<div class="arrow">
<ActionIcon
icon={ArrowRight}
size="medium"
action={() => {
dispatch('open', value._id)
}}
/>
</div>
</div>
<div class="clear-mins flex-no-shrink mt-4">
{#if presenter}
<svelte:component this={presenter} value={doc} inline disabled inbox />
{/if}
</div>
<div class="flex-between flex-baseline mt-3">
{#if tx && firstItem}
<TxView {tx} {viewlets} objectId={firstItem.attachedTo} />
{/if}
<div class="time">
<TimeSince value={tx?.modifiedOn} />
</div>
</div>
</div>
</div>
{/if}

View File

@ -1,81 +0,0 @@
<!--
// Copyright © 2023 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 { PersonAccount } from '@hcengineering/contact'
import { PersonAccountRefPresenter } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { Collaborators } from '@hcengineering/notification'
import { getClient } from '@hcengineering/presentation'
import { IconAdd, IconDelete, Label } from '@hcengineering/ui'
import notification from '../../plugin'
// export let tx: TxMixin<Doc, Collaborators>
export let value: Collaborators
export let prevValue: Collaborators | undefined = undefined
interface Diff {
added: Ref<PersonAccount>[]
removed: Ref<PersonAccount>[]
}
const client = getClient()
const hierarchy = client.getHierarchy()
function buildDiff (value: Collaborators, prev: Collaborators | undefined): Diff | undefined {
if (prev === undefined) return
const added: Ref<PersonAccount>[] = []
const removed: Ref<PersonAccount>[] = []
const mixin = hierarchy.as(value, notification.mixin.Collaborators)
const prevMixin = hierarchy.as(prev, notification.mixin.Collaborators)
const prevSet = new Set(prevMixin?.collaborators ?? [])
const newSet = new Set(mixin.collaborators)
for (const newCollab of mixin.collaborators) {
if (!prevSet.has(newCollab)) added.push(newCollab as Ref<PersonAccount>)
}
for (const oldCollab of prevMixin?.collaborators ?? []) {
if (!newSet.has(oldCollab)) removed.push(oldCollab as Ref<PersonAccount>)
}
return {
added,
removed
}
}
$: diff = buildDiff(value, prevValue)
</script>
{#if diff}
<Label label={notification.string.ChangeCollaborators} />
{#if diff.added.length > 0}
<div class="antiHSpacer" />
<IconAdd size={'x-small'} fill={'var(--theme-trans-color)'} />
{#each diff.added as add}
<PersonAccountRefPresenter value={add} disabled inline />
<div class="antiHSpacer" />
{/each}
{/if}
{#if diff.removed.length > 0}
<div class="antiHSpacer" />
<IconDelete size={'x-small'} fill={'var(--theme-trans-color)'} />
{#each diff.removed as removed}
<PersonAccountRefPresenter value={removed} disabled inline />
<div class="antiHSpacer" />
{/each}
{/if}
{:else}
<Label label={notification.string.YouAddedCollaborators} />
{/if}

View File

@ -1,24 +0,0 @@
<!--
// Copyright © 2023 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 { Label } from '@hcengineering/ui'
import notification from '../../plugin'
// export let tx: TxCreateDoc<DirectMessage>
// export let value: DirectMessage
</script>
<Label label={notification.string.YouHaveJoinedTheConversation} />

View File

@ -19,8 +19,6 @@ import { type Resources } from '@hcengineering/platform'
import Inbox from './components/inbox/Inbox.svelte'
import NotificationSettings from './components/NotificationSettings.svelte'
import NotificationPresenter from './components/NotificationPresenter.svelte'
import TxCollaboratorsChange from './components/activity/TxCollaboratorsChange.svelte'
import TxDmCreation from './components/activity/TxDmCreation.svelte'
import DocNotifyContextPresenter from './components/DocNotifyContextPresenter.svelte'
import CollaboratorsChanged from './components/activity/CollaboratorsChanged.svelte'
import ActivityInboxNotificationPresenter from './components/inbox/ActivityInboxNotificationPresenter.svelte'
@ -67,10 +65,6 @@ export default async (): Promise<Resources> => ({
NotificationCollaboratorsChanged,
ReactionNotificationPresenter
},
activity: {
TxCollaboratorsChange,
TxDmCreation
},
function: {
// eslint-disable-next-line @typescript-eslint/unbound-method
GetInboxNotificationsClient: InboxNotificationsClientImpl.getClient,

View File

@ -27,7 +27,6 @@ import {
Space,
Timestamp,
Tx,
TxCUD,
TxOperations
} from '@hcengineering/core'
import type { Asset, IntlString, Metadata, Plugin, Resource } from '@hcengineering/platform'
@ -193,34 +192,6 @@ export interface Collaborators extends Doc {
collaborators: Ref<Account>[]
}
/**
* @public
* @deprecated
*/
export interface DocUpdateTx {
_id: Ref<TxCUD<Doc>>
modifiedBy: Ref<Account>
modifiedOn: Timestamp
isNew: boolean
title?: IntlString
body?: IntlString
intlParams?: Record<string, string | number>
intlParamsNotLocalized?: Record<string, IntlString>
}
/**
* @public
* @deprecated
*/
export interface DocUpdates extends Doc {
user: Ref<Account>
attachedTo: Ref<Doc>
attachedToClass: Ref<Class<Doc>>
hidden: boolean
lastTxTime?: Timestamp
txes: DocUpdateTx[]
}
/**
* @public
*/
@ -362,7 +333,6 @@ const notification = plugin(notificationId, {
CommonNotificationType: '' as Ref<Class<CommonNotificationType>>,
NotificationProvider: '' as Ref<Class<NotificationProvider>>,
NotificationSetting: '' as Ref<Class<NotificationSetting>>,
DocUpdates: '' as Ref<Class<DocUpdates>>,
NotificationGroup: '' as Ref<Class<NotificationGroup>>,
NotificationPreferencesGroup: '' as Ref<Class<NotificationPreferencesGroup>>,
DocNotifyContext: '' as Ref<Class<DocNotifyContext>>,
@ -397,9 +367,6 @@ const notification = plugin(notificationId, {
NotificationCollaboratorsChanged: '' as AnyComponent,
ReactionNotificationPresenter: '' as AnyComponent
},
activity: {
TxCollaboratorsChange: '' as AnyComponent
},
action: {
PinDocNotifyContext: '' as Ref<Action>,
UnpinDocNotifyContext: '' as Ref<Action>,

View File

@ -20,7 +20,6 @@
import request from '../plugin'
import RequestActions from './RequestActions.svelte'
import RequestDetail from './RequestDetail.svelte'
import TxView from './TxView.svelte'
import { createQuery } from '@hcengineering/presentation'
import { Doc } from '@hcengineering/core'
@ -52,7 +51,6 @@
</DocNavLink>
</span>
{/if}
<TxView tx={object.tx} />
</div>
<RequestDetail value={object} />
<RequestActions value={object} />

View File

@ -13,13 +13,26 @@
// limitations under the License.
-->
<script lang="ts">
import { Request } from '@hcengineering/request'
import TxView from './TxView.svelte'
import request, { Request } from '@hcengineering/request'
import { getClient } from '@hcengineering/presentation'
import { Doc } from '@hcengineering/core'
import { ObjectPresenter } from '@hcengineering/view-resources'
import { Label } from '@hcengineering/ui'
export let value: Request
export let inline: boolean = false
const client = getClient()
let object: Doc | undefined
$: void client.findOne(value.attachedToClass, { _id: value.attachedTo }).then((res) => {
object = res
})
</script>
<div class="flex">
<TxView tx={value.tx} />
<div class="flex-row-center flex-gap-2">
<Label label={request.string.Request} />
{#if object}
<ObjectPresenter objectId={object._id} _class={object._class} props={{ disableClick: true }} />
{/if}
</div>

View File

@ -1,226 +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 { DisplayTx, TxViewlet } from '@hcengineering/activity'
import { ActivityKey, getValue, newDisplayTx, updateViewlet } from '@hcengineering/activity-resources'
import activity from '@hcengineering/activity-resources/src/plugin'
import { PersonAccount } from '@hcengineering/contact'
import { personAccountByIdStore } from '@hcengineering/contact-resources'
import core, { AnyAttribute, Doc, Ref, Tx, TxCUD } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import type { AttributeModel } from '@hcengineering/view'
import { ObjectPresenter } from '@hcengineering/view-resources'
import request from '../plugin'
export let tx: Tx
const viewlets: Map<ActivityKey, TxViewlet[]> = new Map<ActivityKey, TxViewlet[]>()
const client = getClient()
let ptx: DisplayTx | undefined
let employee: PersonAccount | undefined
let model: AttributeModel[] = []
$: if (tx._id !== ptx?.tx._id) {
ptx = newDisplayTx(tx as TxCUD<Doc>, client.getHierarchy(), false)
if (tx.modifiedBy !== employee?._id) {
employee = undefined
}
model = []
}
$: ptx &&
updateViewlet(client, viewlets, ptx).then((result) => {
if (result.id === tx._id) {
model = result.model
}
})
$: employee = $personAccountByIdStore.get(tx.modifiedBy as Ref<PersonAccount>)
function isMessageType (attr?: AnyAttribute): boolean {
return attr?.type._class === core.class.TypeMarkup
}
async function updateMessageType (model: AttributeModel[], tx: DisplayTx): Promise<boolean> {
for (const m of model) {
if (isMessageType(m.attribute)) {
return true
}
const val = await getValue(client, m, tx)
if (val.added.length > 1 || val.removed.length > 1) {
return true
}
}
return false
}
let hasMessageType = false
$: ptx &&
updateMessageType(model, ptx).then((res) => {
hasMessageType = res
})
</script>
{#if model.length > 0}
<div class="flex-between msgactivity-container">
<div class="flex-grow flex-col clear-mins" class:mention={isMessageType(model[0]?.attribute)}>
<div class="flex-between">
<div class="flex-row-center flex-grow label">
{#if ptx?.updateTx}
{#each model as m}
{#await getValue(client, m, ptx) then value}
{#if value.set === null || value.set === undefined}
<span class="lower"><Label label={activity.string.Unset} /><Label label={m.label} /></span>
{:else if value.added.length}
<span class="lower" class:flex-grow={hasMessageType}>
<Label label={request.string.Add} />
<Label label={activity.string.To} />
<Label label={m.label} />
</span>
<div class="strong">
<div class="flex flex-wrap gap-2" class:emphasized={value.added.length > 1}>
{#each value.added as cvalue}
{#if value.isObjectAdded}
<ObjectPresenter value={cvalue} />
{:else}
<svelte:component this={m.presenter} value={cvalue} />
{/if}
{/each}
</div>
</div>
{:else if value.removed.length}
<span class="lower" class:flex-grow={hasMessageType}>
<Label label={request.string.Remove} />
<Label label={activity.string.From} />
<Label label={m.label} />
</span>
<div class="strong">
<div class="flex flex-wrap gap-2 flex-grow" class:emphasized={value.removed.length > 1}>
{#each value.removed as cvalue}
{#if value.isObjectRemoved}
<ObjectPresenter value={cvalue} />
{:else}
<svelte:component this={m.presenter} value={cvalue} />
{/if}
{/each}
</div>
</div>
{:else}
<span class="lower" class:flex-grow={hasMessageType}>
<Label label={request.string.Change} />
<Label label={m.label} />
<Label label={activity.string.To} />
</span>
<div
class="strong"
class:message={isMessageType(m.attribute)}
class:emphasized={isMessageType(m.attribute)}
>
{#if value.isObjectSet}
<ObjectPresenter value={value.set} />
{:else}
<svelte:component this={m.presenter} value={value.set} />
{/if}
</div>
{/if}
{/await}
{/each}
{:else if ptx?.mixinTx}
{#each model as m}
{#await getValue(client, m, ptx) then value}
{#if value.set === null}
<span>
<Label label={activity.string.Unset} /> <span class="lower"><Label label={m.label} /></span>
</span>
{:else}
<span>
<Label label={request.string.Change} />
<span class="lower"><Label label={m.label} /></span>
<Label label={activity.string.To} />
</span>
<div
class="strong"
class:message={isMessageType(m.attribute)}
class:emphasized={isMessageType(m.attribute)}
>
<svelte:component this={m.presenter} value={value.set} />
</div>
{/if}
{/await}
{/each}
{/if}
</div>
</div>
</div>
</div>
{/if}
<style lang="scss">
.mention {
position: relative;
margin-top: 0.25rem;
&::after {
content: '';
position: absolute;
bottom: -0.75rem;
left: -0.625rem;
right: -0.625rem;
background-color: var(--accent-bg-color);
border: 1px solid var(--divider-color);
border-radius: 0.5rem;
z-index: -1;
}
}
.mention::after {
top: -0.5rem;
}
.msgactivity-container {
position: relative;
min-width: 0;
}
.emphasized {
margin-top: 0.5rem;
background-color: var(--theme-bg-color);
border: 1px solid var(--divider-color);
border-radius: 0.5rem;
padding: 0.75rem 1rem;
}
.label {
display: flex;
align-items: center;
flex-wrap: wrap;
& > * {
margin-right: 0.5rem;
}
& > *:last-child {
margin-right: 0;
}
.strong {
font-weight: 500;
color: var(--accent-color);
}
}
.message {
flex-basis: 100%;
}
</style>

View File

@ -1,25 +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 { Request } from '@hcengineering/request'
import RequestPresenter from '../RequestPresenter.svelte'
export let value: Request
export let isOwnTx: boolean
</script>
{#if !isOwnTx}
<RequestPresenter {value} />
{/if}

View File

@ -1,32 +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 { createQuery } from '@hcengineering/presentation'
import { Request } from '@hcengineering/request'
import RequestLabel from '../RequestLabel.svelte'
export let value: Request
export let isOwnTx: boolean
let request: Request | undefined = undefined
const query = createQuery()
query.query(value._class, { _id: value._id }, (res) => {
;[request] = res
})
</script>
{#if request}
<RequestLabel value={request} {isOwnTx} />
{/if}

View File

@ -14,8 +14,6 @@
//
import { type Resources } from '@hcengineering/platform'
import TxCreateRequest from './components/activity/TxCreateRequest.svelte'
import RequestLabel from './components/activity/TxRequestLabel.svelte'
import EditRequest from './components/EditRequest.svelte'
import RequestPresenter from './components/RequestPresenter.svelte'
import RequestView from './components/RequestView.svelte'
@ -25,10 +23,6 @@ export { default as RequestStatusPresenter } from './components/RequestStatusPre
export { default as RequestDetailPopup } from './components/RequestDetailPopup.svelte'
export default async (): Promise<Resources> => ({
activity: {
RequestLabel,
TxCreateRequest
},
component: {
EditRequest,
RequestPresenter,

View File

@ -29,7 +29,6 @@ export default mergeIds(requestId, request, {
Remove: '' as IntlString,
Completed: '' as IntlString,
Reject: '' as IntlString,
Request: '' as IntlString,
Rejected: '' as IntlString,
Comment: '' as IntlString,
PleaseTypeMessage: '' as IntlString,

View File

@ -78,7 +78,8 @@ const request = plugin(requestId, {
RequestView: '' as AnyComponent
},
string: {
Requests: '' as IntlString
Requests: '' as IntlString,
Request: '' as IntlString
},
icon: {
Requests: '' as Asset

View File

@ -1,42 +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 { TxUpdateDoc } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { Integration } from '@hcengineering/setting'
import { Label } from '@hcengineering/ui'
import setting from '../../plugin'
export let tx: TxUpdateDoc<Integration>
// export let doc: Integration
const client = getClient()
async function getTypeLabel (tx: TxUpdateDoc<Integration>): Promise<IntlString | undefined> {
const doc = await client.findOne(setting.class.Integration, { _id: tx.objectId })
if (doc === undefined) return
const type = await client.findOne(setting.class.IntegrationType, { _id: doc.type })
return type?.label
}
</script>
&nbsp;
{#await getTypeLabel(tx) then typeLabel}
{#if typeLabel}
<Label label={typeLabel} />
{/if}
{/await}
<Label label={setting.string.IntegrationDisabled} />

View File

@ -18,7 +18,6 @@ import { type Resources } from '@hcengineering/platform'
import { getClient, MessageBox } from '@hcengineering/presentation'
import { showPopup } from '@hcengineering/ui'
import { deleteObject } from '@hcengineering/view-resources/src/utils'
import TxIntegrationDisable from './components/activity/TxIntegrationDisable.svelte'
import ClassSetting from './components/ClassSetting.svelte'
import CreateMixin from './components/CreateMixin.svelte'
import EditEnum from './components/EditEnum.svelte'
@ -89,9 +88,6 @@ async function DeleteMixin (object: Mixin<Class<Doc>>): Promise<void> {
}
export default async (): Promise<Resources> => ({
activity: {
TxIntegrationDisable
},
component: {
Settings,
Spaces,

View File

@ -1,31 +0,0 @@
<!--
// Copyright © 2023 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 { HTMLViewer } from '@hcengineering/presentation'
import { TelegramMessage } from '@hcengineering/telegram'
export let value: TelegramMessage
</script>
<div class="content lines-limit-2">
<HTMLViewer value={value.content} />
</div>
<style lang="scss">
.content {
min-width: 0;
min-height: 1rem;
max-height: 2.125rem;
}
</style>

View File

@ -1,25 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 type { TxCreateDoc } from '@hcengineering/core'
import { TxProcessor } from '@hcengineering/core'
import { SharedTelegramMessages } from '@hcengineering/telegram'
import SharedMessages from '../SharedMessages.svelte'
export let tx: TxCreateDoc<SharedTelegramMessages>
</script>
<SharedMessages value={TxProcessor.createDoc2Doc(tx)} />

View File

@ -21,9 +21,7 @@ import presentation from '@hcengineering/presentation'
import Chat from './components/Chat.svelte'
import Connect from './components/Connect.svelte'
import Reconnect from './components/Reconnect.svelte'
import TxMessage from './components/activity/TxMessage.svelte'
import IconTelegram from './components/icons/TelegramColor.svelte'
import TxSharedCreate from './components/activity/TxSharedCreate.svelte'
import TelegramMessageCreated from './components/activity/TelegramMessageCreated.svelte'
import MessagePresenter from './components/MessagePresenter.svelte'
@ -41,8 +39,6 @@ export default async (): Promise<Resources> => ({
MessagePresenter
},
activity: {
TxSharedCreate,
TxMessage,
TelegramMessageCreated
},
function: {

View File

@ -1,32 +0,0 @@
<!--
// Copyright © 2023 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 { Issue } from '@hcengineering/tracker'
import { Label } from '@hcengineering/ui'
import tracker from '../../plugin'
import IssuePresenter from '../issues/IssuePresenter.svelte'
export let value: Issue
export let isOwnTx: boolean
$: isSub = value.attachedTo !== tracker.ids.NoParent && !isOwnTx
</script>
<div class="lower">
<Label label={isSub ? tracker.string.CreatedSubIssue : tracker.string.CreatedIssue} />
</div>
{#if !isOwnTx}
<IssuePresenter {value} />
{/if}

View File

@ -145,7 +145,6 @@ import {
import { ComponentAggregationManager, grouppingComponentManager } from './component'
import PriorityIcon from './components/activity/PriorityIcon.svelte'
import StatusIcon from './components/activity/StatusIcon.svelte'
import TxIssueCreated from './components/activity/TxIssueCreated.svelte'
import DeleteComponentPresenter from './components/components/DeleteComponentPresenter.svelte'
import IssueStatusIcon from './components/issues/IssueStatusIcon.svelte'
import MoveIssues from './components/issues/Move.svelte'
@ -593,7 +592,6 @@ export async function importTasks (tasks: File, space: Ref<Project>): Promise<vo
export default async (): Promise<Resources> => ({
activity: {
TxIssueCreated,
PriorityIcon,
StatusIcon
},