From 7f6f5e28a608af9ae6ff2f98f515366d266ca57b Mon Sep 17 00:00:00 2001 From: Kristina Date: Thu, 11 Jan 2024 18:46:11 +0400 Subject: [PATCH] UBERF-4360: rewrite chat (#4265) Signed-off-by: Kristina Fefelova --- models/activity/package.json | 6 +- models/activity/src/index.ts | 78 +- models/activity/src/plugin.ts | 19 +- models/all/src/migration.ts | 5 +- models/attachment/src/index.ts | 2 + models/attachment/src/plugin.ts | 3 +- models/chunter/package.json | 1 - models/chunter/src/index.ts | 460 ++----- models/chunter/src/migration.ts | 45 +- models/chunter/src/plugin.ts | 34 +- models/contact/src/index.ts | 6 + models/inventory/src/index.ts | 3 + models/lead/src/index.ts | 2 + models/notification/src/index.ts | 202 +-- models/notification/src/migration.ts | 172 ++- models/notification/src/plugin.ts | 24 +- models/recruit/src/index.ts | 4 + models/server-activity/package.json | 1 + models/server-activity/src/index.ts | 16 +- models/server-activity/src/migration.ts | 21 +- models/server-chunter/src/index.ts | 20 +- models/server-notification/src/index.ts | 40 +- models/tracker/src/index.ts | 23 + models/view/src/plugin.ts | 4 +- models/workbench/src/plugin.ts | 2 - packages/core/src/time.ts | 42 +- packages/model/src/migration.ts | 2 +- .../src/components/SpaceCreateCard.svelte | 1 + packages/ui/src/components/Component.svelte | 2 + plugins/activity-assets/assets/icons.svg | 3 + plugins/activity-assets/lang/en.json | 3 + plugins/activity-assets/lang/ru.json | 3 + plugins/activity-assets/src/index.ts | 3 +- plugins/activity-resources/package.json | 2 + .../src/activityMessagesUtils.ts | 64 +- .../src/components/ActivityExtension.svelte | 2 +- .../src/components/ActivityFilter.svelte | 31 +- .../components/ActivityScrolledView.svelte | 356 +++++ .../src/components/FilterPopup.svelte | 53 +- .../src/components/JumpToDateSelector.svelte | 18 +- .../src/components/Replies.svelte | 167 +++ .../src/components/SaveMessageAction.svelte | 51 + .../ActivityInfoMessagePresenter.svelte | 4 +- .../ActivityMessagePresenter.svelte | 19 +- .../ActivityMessageTemplate.svelte | 56 +- .../ActivityMessagesSeparator.svelte} | 11 +- .../DocUpdateMessagePresenter.svelte | 14 +- .../src/components/icons/Bookmark.svelte | 24 + .../reactions/AddReactionAction.svelte | 19 +- .../reactions/ReactionsPresenter.svelte | 7 +- plugins/activity-resources/src/index.ts | 9 +- plugins/activity/package.json | 5 +- plugins/activity/src/index.ts | 31 +- .../src/components/AttachmentDocList.svelte | 12 +- plugins/attachment-resources/src/plugin.ts | 1 - plugins/attachment/src/index.ts | 6 +- .../presenters/NotificationPresenter.svelte | 6 +- plugins/chunter-assets/assets/icons.svg | 3 - plugins/chunter-assets/lang/en.json | 12 +- plugins/chunter-assets/lang/ru.json | 12 +- plugins/chunter-assets/src/index.ts | 1 - .../src/components/Channel.svelte | 239 +--- .../src/components/ChannelHeader.svelte | 46 +- .../src/components/ChannelPreview.svelte | 17 +- .../src/components/ChannelView.svelte | 95 +- .../src/components/ChannelViewPanel.svelte | 34 +- .../src/components/DmIconPresenter.svelte | 39 + .../src/components/DmPresenter.svelte | 10 +- .../src/components/Header.svelte | 3 +- .../src/components/Message.svelte | 347 ----- .../src/components/MessagePresenter.svelte | 62 - .../src/components/MessagePreview.svelte | 119 -- .../src/components/MessagesBrowser.svelte | 117 -- .../src/components/PinnedMessages.svelte | 52 +- .../src/components/PinnedMessagesPopup.svelte | 117 +- .../src/components/Replies.svelte | 128 -- .../src/components/SavedMessages.svelte | 175 --- .../src/components/SpaceHeader.svelte | 42 - .../src/components/Thread.svelte | 230 ---- .../src/components/ThreadView.svelte | 249 ---- .../src/components/Threads.svelte | 67 - .../activity/TxCommentCreate.svelte | 116 -- .../activity/TxMessageCreate.svelte | 9 - .../chat-message/ChatMessageHeader.svelte | 15 +- .../chat-message/ChatMessageInput.svelte | 71 +- .../chat-message/ChatMessagePresenter.svelte | 20 +- .../src/components/chat/Chat.svelte | 146 ++ .../src/components/chat/DocChatPanel.svelte | 163 +++ .../{ => chat/create}/CreateChannel.svelte | 27 +- .../create}/CreateDirectMessage.svelte | 46 +- .../chat/navigator/ChatNavGroup.svelte | 95 ++ .../chat/navigator/ChatNavItem.svelte | 121 ++ .../chat/navigator/ChatNavigator.svelte | 75 ++ .../chat/specials/ChannelBrowser.svelte | 262 ++++ .../{ => chat/specials}/ChunterBrowser.svelte | 25 +- .../chat/specials/MessagesBrowser.svelte | 93 ++ .../chat/specials/SavedMessages.svelte | 164 +++ .../components/chat/specials/Threads.svelte | 55 + .../src/components/chat/types.ts | 33 + .../src/components/chat/utils.ts | 178 +++ .../src/components/icons/Bookmark.svelte | 10 - .../threads/ThreadMessagePresenter.svelte | 42 + .../ThreadParentPresenter.svelte | 8 +- .../src/components/threads/ThreadView.svelte | 126 ++ .../{ => threads}/ThreadViewPanel.svelte | 19 +- plugins/chunter-resources/src/index.ts | 249 ++-- plugins/chunter-resources/src/plugin.ts | 23 +- plugins/chunter-resources/src/utils.ts | 350 +++-- plugins/chunter/package.json | 1 - plugins/chunter/src/index.ts | 59 +- .../src/components/ChannelsDropdown.svelte | 56 +- .../src/components/ChannelsView.svelte | 49 +- plugins/contact-resources/src/utils.ts | 21 +- .../src/components/Chats.svelte | 9 +- .../src/components/Main.svelte | 6 +- .../src/components/NewMessage.svelte | 8 +- .../src/components/NewMessages.svelte | 6 +- plugins/notification-assets/lang/en.json | 4 +- plugins/notification-assets/lang/ru.json | 4 +- plugins/notification-resources/package.json | 1 + .../src/components/Activity.svelte | 11 +- .../DocNotifyContextPresenter.svelte | 55 + .../components/NotificationPresenter.svelte | 13 +- .../ActivityInboxNotificationPresenter.svelte | 145 ++ .../CommonInboxNotificationPresenter.svelte | 172 +++ .../src/components/inbox/Inbox.svelte | 95 +- .../src/components/inbox/InboxAside.svelte | 132 +- .../inbox/InboxNotificationPresenter.svelte | 32 + .../src/inboxNotificationsClient.ts | 145 +- plugins/notification-resources/src/index.ts | 47 +- plugins/notification-resources/src/types.ts | 1 + plugins/notification-resources/src/utils.ts | 390 +++--- plugins/notification/src/index.ts | 76 +- .../src/components/EditVacancy.svelte | 6 +- plugins/task/package.json | 7 +- plugins/task/src/index.ts | 2 +- .../src/components/Chat.svelte | 8 +- .../components/issues/edit/EditIssue.svelte | 6 +- .../templates/EditIssueTemplate.svelte | 6 +- plugins/tracker-resources/src/index.ts | 2 + plugins/tracker-resources/src/plugin.ts | 1 + plugins/tracker-resources/src/utils.ts | 4 + plugins/view-assets/lang/en.json | 5 +- plugins/view-assets/lang/ru.json | 5 +- .../src/components/BaseDocPresenter.svelte | 41 + .../src/components/DocAttributeBar.svelte | 13 +- .../src/components/EditDoc.svelte | 36 +- .../components/navigator/TreeElement.svelte | 18 +- .../src/components/navigator/TreeItem.svelte | 20 +- plugins/view-resources/src/index.ts | 1 + plugins/view-resources/src/plugin.ts | 2 - plugins/view-resources/src/utils.ts | 57 +- plugins/view/src/index.ts | 6 +- .../src/components/Applications.svelte | 25 +- .../src/components/Archive.svelte | 15 +- .../src/components/SpaceBrowser.svelte | 3 +- .../src/components/Workbench.svelte | 95 +- .../src/components/navigator/SpacesNav.svelte | 35 +- .../components/navigator/StarredNav.svelte | 28 +- plugins/workbench-resources/src/index.ts | 1 + plugins/workbench-resources/src/plugin.ts | 5 - plugins/workbench-resources/src/utils.ts | 7 +- plugins/workbench/package.json | 5 +- plugins/workbench/src/index.ts | 13 +- .../activity-resources/package.json | 6 +- .../activity-resources/src/index.ts | 161 ++- server-plugins/activity/src/index.ts | 3 +- server-plugins/chunter-resources/package.json | 1 + .../chunter-resources/src/backlinks.ts | 107 +- server-plugins/chunter-resources/src/index.ts | 394 +++--- server-plugins/chunter-resources/src/utils.ts | 95 ++ server-plugins/chunter/src/index.ts | 3 +- server-plugins/gmail-resources/src/index.ts | 41 +- .../notification-resources/src/index.ts | 1185 ++++++----------- .../notification-resources/src/types.ts | 24 + .../notification-resources/src/utils.ts | 299 ++++- server-plugins/notification/src/index.ts | 5 +- .../telegram-resources/src/index.ts | 41 +- server/tool/src/upgrade.ts | 4 +- tests/sanity/tests/channel.spec.ts | 2 +- .../tests/collaborative/applications.spec.ts | 23 +- .../sanity/tests/collaborative/issues.spec.ts | 22 +- tests/sanity/tests/model/notification-page.ts | 2 + 183 files changed, 6360 insertions(+), 5051 deletions(-) create mode 100644 plugins/activity-resources/src/components/ActivityScrolledView.svelte rename plugins/{chunter-resources => activity-resources}/src/components/JumpToDateSelector.svelte (71%) create mode 100644 plugins/activity-resources/src/components/Replies.svelte create mode 100644 plugins/activity-resources/src/components/SaveMessageAction.svelte rename plugins/{chunter-resources/src/components/ChannelSeparator.svelte => activity-resources/src/components/activity-message/ActivityMessagesSeparator.svelte} (84%) create mode 100644 plugins/activity-resources/src/components/icons/Bookmark.svelte create mode 100644 plugins/chunter-resources/src/components/DmIconPresenter.svelte delete mode 100644 plugins/chunter-resources/src/components/Message.svelte delete mode 100644 plugins/chunter-resources/src/components/MessagePresenter.svelte delete mode 100644 plugins/chunter-resources/src/components/MessagePreview.svelte delete mode 100644 plugins/chunter-resources/src/components/MessagesBrowser.svelte delete mode 100644 plugins/chunter-resources/src/components/Replies.svelte delete mode 100644 plugins/chunter-resources/src/components/SavedMessages.svelte delete mode 100644 plugins/chunter-resources/src/components/SpaceHeader.svelte delete mode 100644 plugins/chunter-resources/src/components/Thread.svelte delete mode 100644 plugins/chunter-resources/src/components/ThreadView.svelte delete mode 100644 plugins/chunter-resources/src/components/Threads.svelte delete mode 100644 plugins/chunter-resources/src/components/activity/TxCommentCreate.svelte delete mode 100644 plugins/chunter-resources/src/components/activity/TxMessageCreate.svelte create mode 100644 plugins/chunter-resources/src/components/chat/Chat.svelte create mode 100644 plugins/chunter-resources/src/components/chat/DocChatPanel.svelte rename plugins/chunter-resources/src/components/{ => chat/create}/CreateChannel.svelte (76%) rename plugins/chunter-resources/src/components/{ => chat/create}/CreateDirectMessage.svelte (59%) create mode 100644 plugins/chunter-resources/src/components/chat/navigator/ChatNavGroup.svelte create mode 100644 plugins/chunter-resources/src/components/chat/navigator/ChatNavItem.svelte create mode 100644 plugins/chunter-resources/src/components/chat/navigator/ChatNavigator.svelte create mode 100644 plugins/chunter-resources/src/components/chat/specials/ChannelBrowser.svelte rename plugins/chunter-resources/src/components/{ => chat/specials}/ChunterBrowser.svelte (82%) create mode 100644 plugins/chunter-resources/src/components/chat/specials/MessagesBrowser.svelte create mode 100644 plugins/chunter-resources/src/components/chat/specials/SavedMessages.svelte create mode 100644 plugins/chunter-resources/src/components/chat/specials/Threads.svelte create mode 100644 plugins/chunter-resources/src/components/chat/types.ts create mode 100644 plugins/chunter-resources/src/components/chat/utils.ts delete mode 100644 plugins/chunter-resources/src/components/icons/Bookmark.svelte create mode 100644 plugins/chunter-resources/src/components/threads/ThreadMessagePresenter.svelte rename plugins/chunter-resources/src/components/{ => threads}/ThreadParentPresenter.svelte (73%) create mode 100644 plugins/chunter-resources/src/components/threads/ThreadView.svelte rename plugins/chunter-resources/src/components/{ => threads}/ThreadViewPanel.svelte (61%) create mode 100644 plugins/notification-resources/src/components/DocNotifyContextPresenter.svelte create mode 100644 plugins/notification-resources/src/components/inbox/ActivityInboxNotificationPresenter.svelte create mode 100644 plugins/notification-resources/src/components/inbox/CommonInboxNotificationPresenter.svelte create mode 100644 plugins/notification-resources/src/components/inbox/InboxNotificationPresenter.svelte create mode 100644 plugins/notification-resources/src/types.ts create mode 100644 plugins/view-resources/src/components/BaseDocPresenter.svelte create mode 100644 server-plugins/chunter-resources/src/utils.ts diff --git a/models/activity/package.json b/models/activity/package.json index 4cb6b4a161..f7b331f92a 100644 --- a/models/activity/package.json +++ b/models/activity/package.json @@ -27,11 +27,15 @@ "dependencies": { "@hcengineering/activity": "^0.6.0", "@hcengineering/activity-resources": "^0.6.1", + "@hcengineering/contact": "^0.6.20", "@hcengineering/core": "^0.6.28", "@hcengineering/model": "^0.6.7", "@hcengineering/model-core": "^0.6.0", + "@hcengineering/model-preference": "^0.6.0", + "@hcengineering/model-view": "^0.6.0", + "@hcengineering/notification": "^0.6.16", "@hcengineering/platform": "^0.6.9", "@hcengineering/ui": "^0.6.11", - "@hcengineering/model-view": "^0.6.0" + "@hcengineering/view": "^0.6.9" } } diff --git a/models/activity/src/index.ts b/models/activity/src/index.ts index bb9aed4d91..a242723ec8 100644 --- a/models/activity/src/index.ts +++ b/models/activity/src/index.ts @@ -31,6 +31,7 @@ import { type Reaction, type TxViewlet, type ActivityMessageControl, + type SavedMessage, type IgnoreActivity } from '@hcengineering/activity' import core, { @@ -43,7 +44,8 @@ import core, { IndexKind, type TxCUD, type Domain, - type Account + type Account, + type Timestamp } from '@hcengineering/core' import { Model, @@ -55,11 +57,16 @@ import { Mixin, Collection, TypeBoolean, - TypeIntlString + TypeIntlString, + ArrOf, + TypeTimestamp } from '@hcengineering/model' import { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core' import type { Asset, IntlString, Resource } from '@hcengineering/platform' import { type AnyComponent } from '@hcengineering/ui/src/types' +import contact, { type Person } from '@hcengineering/contact' +import preference, { TPreference } from '@hcengineering/model-preference' +import notification from '@hcengineering/notification' import view from '@hcengineering/model-view' import activity from './plugin' @@ -102,8 +109,18 @@ export class TActivityMessage extends TAttachedDoc implements ActivityMessage { @Prop(TypeBoolean(), activity.string.Pinned) isPinned?: boolean + @Prop(ArrOf(TypeRef(contact.class.Person)), contact.string.Person) + repliedPersons?: Ref[] + + @Prop(TypeTimestamp(), activity.string.LastReply) + @Index(IndexKind.Indexed) + lastReply?: Timestamp + @Prop(Collection(activity.class.Reaction), activity.string.Reactions) reactions?: number + + @Prop(Collection(activity.class.ActivityMessage), activity.string.Replies) + replies?: number } @Model(activity.class.DocUpdateMessage, activity.class.ActivityMessage) @@ -186,11 +203,20 @@ export class TActivityExtension extends TDoc implements ActivityExtension { @Model(activity.class.ActivityMessagesFilter, core.class.Doc, DOMAIN_MODEL) export class TActivityMessagesFilter extends TDoc implements ActivityMessagesFilter { label!: IntlString + position!: number filter!: Resource<(message: ActivityMessage, _class?: Ref) => boolean> } @Model(activity.class.Reaction, core.class.AttachedDoc, DOMAIN_ACTIVITY) export class TReaction extends TAttachedDoc implements Reaction { + @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedTo) + @Index(IndexKind.Indexed) + declare attachedTo: Ref + + @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedToClass) + @Index(IndexKind.Indexed) + declare attachedToClass: Ref> + @Prop(TypeString(), activity.string.Emoji) emoji!: string @@ -198,6 +224,11 @@ export class TReaction extends TAttachedDoc implements Reaction { createBy!: Ref } +@Model(activity.class.SavedMessage, preference.class.Preference) +export class TSavedMessage extends TPreference implements SavedMessage { + @Prop(TypeRef(activity.class.ActivityMessage), view.string.Save) + declare attachedTo: Ref +} export function createModel (builder: Builder): void { builder.createModel( TTxViewlet, @@ -212,6 +243,7 @@ export function createModel (builder: Builder): void { TActivityAttributeUpdatesPresenter, TActivityInfoMessage, TActivityMessageControl, + TSavedMessage, TIgnoreActivity ) @@ -225,13 +257,30 @@ export function createModel (builder: Builder): void { presenter: activity.component.ActivityInfoMessagePresenter }) + builder.mixin(activity.class.DocUpdateMessage, core.class.Class, view.mixin.LinkProvider, { + encode: activity.function.GetFragment + }) + + builder.createDoc( + activity.class.ActivityMessagesFilter, + core.space.Model, + { + label: activity.string.All, + position: 10, + filter: activity.filter.AllFilter + }, + activity.ids.AllFilter + ) + builder.createDoc(activity.class.ActivityMessagesFilter, core.space.Model, { label: activity.string.Attributes, + position: 10, filter: activity.filter.AttributesFilter }) builder.createDoc(activity.class.ActivityMessagesFilter, core.space.Model, { label: activity.string.Pinned, + position: 20, filter: activity.filter.PinnedFilter }) @@ -259,6 +308,31 @@ export function createModel (builder: Builder): void { }, activity.ids.ReactionRemovedActivityViewlet ) + + builder.mixin(activity.class.ActivityMessage, core.class.Class, notification.mixin.ClassCollaborators, { + fields: ['createdBy', 'repliedPersons'] + }) + + builder.mixin(activity.class.DocUpdateMessage, core.class.Class, notification.mixin.ClassCollaborators, { + fields: ['createdBy', 'repliedPersons'] + }) + + builder.createDoc( + notification.class.NotificationType, + core.space.Model, + { + hidden: false, + generated: false, + label: activity.string.Reactions, + group: activity.ids.ActivityNotificationGroup, + txClasses: [core.class.TxCreateDoc], + objectClass: activity.class.Reaction, + providers: { + [notification.providers.PlatformNotification]: true + } + }, + activity.ids.AddReactionNotification + ) } export default activity diff --git a/models/activity/src/plugin.ts b/models/activity/src/plugin.ts index 912cede81e..d2566c8e9f 100644 --- a/models/activity/src/plugin.ts +++ b/models/activity/src/plugin.ts @@ -16,20 +16,33 @@ import { activityId, type ActivityMessage, type DocUpdateMessageViewlet } from ' import activity from '@hcengineering/activity-resources/src/plugin' import { type IntlString, mergeIds, type Resource } from '@hcengineering/platform' import { type Doc, type Ref } from '@hcengineering/core' +import type { Location } from '@hcengineering/ui' +import { type ActionCategory } from '@hcengineering/view' +import { type NotificationGroup, type NotificationType } from '@hcengineering/notification' export default mergeIds(activityId, activity, { string: { Attributes: '' as IntlString, Pinned: '' as IntlString, Emoji: '' as IntlString, - Reacted: '' as IntlString + Reacted: '' as IntlString, + Replies: '' as IntlString }, filter: { AttributesFilter: '' as Resource<(message: ActivityMessage, _class?: Ref) => boolean>, - PinnedFilter: '' as Resource<(message: ActivityMessage, _class?: Ref) => boolean> + PinnedFilter: '' as Resource<(message: ActivityMessage, _class?: Ref) => boolean>, + AllFilter: '' as Resource<(message: ActivityMessage, _class?: Ref) => boolean> }, ids: { ReactionAddedActivityViewlet: '' as Ref, - ReactionRemovedActivityViewlet: '' as Ref + ReactionRemovedActivityViewlet: '' as Ref, + ActivityNotificationGroup: '' as Ref, + AddReactionNotification: '' as Ref + }, + function: { + GetFragment: '' as Resource<(doc: Doc, props: Record) => Promise> + }, + category: { + Activity: '' as Ref } }) diff --git a/models/all/src/migration.ts b/models/all/src/migration.ts index 19daeb9b9a..201737373a 100644 --- a/models/all/src/migration.ts +++ b/models/all/src/migration.ts @@ -55,12 +55,13 @@ export const migrateOperations: [string, MigrateOperation][] = [ ['view', viewOperation], ['contact', contactOperation], ['tags', tagsOperation], - ['notification', notificationOperation], ['setting', settingOperation], ['tracker', trackerOperation], ['board', boardOperation], ['hr', hrOperation], ['bitrix', bitrixOperation], ['inventiry', inventoryOperation], - ['activityServer', activityServerOperation] + ['activityServer', activityServerOperation], + // We should call it after activityServer and chunter + ['notification', notificationOperation] ] diff --git a/models/attachment/src/index.ts b/models/attachment/src/index.ts index 49cc85af53..72a1f6e430 100644 --- a/models/attachment/src/index.ts +++ b/models/attachment/src/index.ts @@ -31,6 +31,7 @@ import { import core, { TAttachedDoc } from '@hcengineering/model-core' import preference, { TPreference } from '@hcengineering/model-preference' import view, { createAction } from '@hcengineering/model-view' + import attachment from './plugin' export { attachmentId } from '@hcengineering/attachment' @@ -150,6 +151,7 @@ export function createModel (builder: Builder): void { builder.createDoc(activity.class.ActivityMessagesFilter, core.space.Model, { label: attachment.string.FilterAttachments, + position: 50, filter: attachment.filter.AttachmentsFilter }) diff --git a/models/attachment/src/plugin.ts b/models/attachment/src/plugin.ts index 9bc47631bd..18da210538 100644 --- a/models/attachment/src/plugin.ts +++ b/models/attachment/src/plugin.ts @@ -24,8 +24,7 @@ import type { ActionCategory } from '@hcengineering/view' export default mergeIds(attachmentId, attachment, { component: { - AttachmentPresenter: '' as AnyComponent, - FileBrowser: '' as AnyComponent + AttachmentPresenter: '' as AnyComponent }, string: { AddAttachment: '' as IntlString, diff --git a/models/chunter/package.json b/models/chunter/package.json index 016006399f..8ee37f46d2 100644 --- a/models/chunter/package.json +++ b/models/chunter/package.json @@ -36,7 +36,6 @@ "@hcengineering/model-activity": "^0.6.0", "@hcengineering/model-core": "^0.6.0", "@hcengineering/model-notification": "^0.6.0", - "@hcengineering/model-preference": "^0.6.0", "@hcengineering/model-view": "^0.6.0", "@hcengineering/model-workbench": "^0.6.1", "@hcengineering/notification": "^0.6.16", diff --git a/models/chunter/src/index.ts b/models/chunter/src/index.ts index 14043816ba..3b14b8a2bc 100644 --- a/models/chunter/src/index.ts +++ b/models/chunter/src/index.ts @@ -13,22 +13,22 @@ // limitations under the License. // -import activity from '@hcengineering/activity' +import activity, { type ActivityMessage } from '@hcengineering/activity' import { type Backlink, type Channel, chunterId, type ChunterMessage, type ChunterMessageExtension, - type ChunterSpace, type Comment, type DirectMessage, type Message, - type SavedMessages, - type ThreadMessage, type DirectMessageInput, type ChatMessage, - type ChatMessageViewlet + type ChatMessageViewlet, + type ChunterSpace, + type ObjectChatPanel, + type ThreadMessage } from '@hcengineering/chunter' import contact, { type Person } from '@hcengineering/contact' import { @@ -61,14 +61,14 @@ import { import attachment from '@hcengineering/model-attachment' import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@hcengineering/model-core' import notification from '@hcengineering/model-notification' -import preference, { TPreference } from '@hcengineering/model-preference' import view, { createAction, actionTemplates as viewTemplates } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' import chunter from './plugin' import { type AnyComponent } from '@hcengineering/ui/src/types' import { TypeBoolean } from '@hcengineering/model' -import type { IntlString } from '@hcengineering/platform' +import type { IntlString, Resource } from '@hcengineering/platform' import { TActivityMessage } from '@hcengineering/model-activity' + export { chunterId } from '@hcengineering/chunter' export { chunterOperation } from './migration' @@ -119,14 +119,6 @@ export class TChunterMessage extends TAttachedDoc implements ChunterMessage { @Mixin(chunter.mixin.ChunterMessageExtension, chunter.class.ChunterMessage) export class TChunterMessageExtension extends TChunterMessage implements ChunterMessageExtension {} -@Model(chunter.class.ThreadMessage, chunter.class.ChunterMessage) -@UX(chunter.string.ThreadMessage, undefined, 'TMSG') -export class TThreadMessage extends TChunterMessage implements ThreadMessage { - declare attachedTo: Ref - - declare attachedToClass: Ref> -} - @Model(chunter.class.Message, chunter.class.ChunterMessage) @UX(chunter.string.Message, undefined, 'MSG') export class TMessage extends TChunterMessage implements Message { @@ -139,7 +131,7 @@ export class TMessage extends TChunterMessage implements Message { repliesCount?: number - @Prop(TypeTimestamp(), chunter.string.LastReply) + @Prop(TypeTimestamp(), activity.string.LastReply) lastReply?: Timestamp } @@ -167,12 +159,6 @@ export class TBacklink extends TComment implements Backlink { backlinkClass!: Ref> } -@Model(chunter.class.SavedMessages, preference.class.Preference) -export class TSavedMessages extends TPreference implements SavedMessages { - @Prop(TypeRef(chunter.class.ChunterMessage), chunter.string.SavedMessages) - declare attachedTo: Ref -} - @Mixin(chunter.mixin.DirectMessageInput, core.class.Class) export class TDirectMessageInput extends TClass implements DirectMessageInput { component!: AnyComponent @@ -184,13 +170,31 @@ export class TChatMessage extends TActivityMessage implements ChatMessage { @Index(IndexKind.FullText) message!: string + @Prop(TypeTimestamp(), chunter.string.Edit) + @Index(IndexKind.Indexed) + editedOn?: Timestamp + @Prop(PropCollection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) attachments?: number +} - @Prop(TypeBoolean(), core.string.Boolean) - isEdited?: boolean +@Model(chunter.class.ThreadMessage, chunter.class.ChatMessage) +export class TThreadMessage extends TChatMessage implements ThreadMessage { + @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedTo) + declare attachedTo: Ref + + @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedToClass) + declare attachedToClass: Ref> + + @Prop(TypeRef(core.class.Doc), core.string.Object) + @Index(IndexKind.Indexed) + objectId!: Ref + + @Prop(TypeRef(core.class.Class), core.string.Class) + @Index(IndexKind.Indexed) + objectClass!: Ref> } @Model(chunter.class.ChatMessageViewlet, core.class.Doc, DOMAIN_MODEL) @@ -199,31 +203,45 @@ export class TChatMessageViewlet extends TDoc implements ChatMessageViewlet { @Index(IndexKind.Indexed) objectClass!: Ref> + @Prop(TypeRef(core.class.Doc), core.string.Class) + @Index(IndexKind.Indexed) + messageClass!: Ref> + label?: IntlString onlyWithParent?: boolean } +@Mixin(chunter.mixin.ObjectChatPanel, core.class.Class) +export class TObjectChatPanel extends TClass implements ObjectChatPanel { + ignoreKeys!: string[] + titleProvider!: Resource<(object: Doc) => string> +} + export function createModel (builder: Builder, options = { addApplication: true }): void { builder.createModel( TChunterSpace, TChannel, TMessage, - TThreadMessage, TChunterMessage, TChunterMessageExtension, TComment, TBacklink, TDirectMessage, - TSavedMessages, TDirectMessageInput, TChatMessage, - TChatMessageViewlet + TThreadMessage, + TChatMessageViewlet, + TObjectChatPanel ) const spaceClasses = [chunter.class.Channel, chunter.class.DirectMessage] spaceClasses.forEach((spaceClass) => { builder.mixin(spaceClass, core.class.Class, activity.mixin.ActivityDoc, {}) + builder.mixin(spaceClass, core.class.Class, view.mixin.LinkProvider, { + encode: chunter.function.GetChunterSpaceLinkFragment + }) + builder.mixin(spaceClass, core.class.Class, workbench.mixin.SpaceView, { view: { class: chunter.class.Message @@ -239,14 +257,14 @@ export function createModel (builder: Builder, options = { addApplication: true }) }) - builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.SpaceName, { - getName: chunter.function.GetDmName - }) - 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.DirectMessageTitleProvider + }) + builder.mixin(chunter.class.DirectMessage, core.class.Class, notification.mixin.ClassCollaborators, { fields: ['members'] }) @@ -279,10 +297,6 @@ export function createModel (builder: Builder, options = { addApplication: true presenter: chunter.component.ThreadParentPresenter }) - builder.mixin(chunter.class.ThreadMessage, core.class.Class, view.mixin.ObjectPresenter, { - presenter: chunter.component.MessagePresenter - }) - builder.mixin(chunter.class.Message, core.class.Class, view.mixin.ObjectPanel, { component: chunter.component.ThreadViewPanel }) @@ -303,23 +317,6 @@ export function createModel (builder: Builder, options = { addApplication: true header: chunter.component.ChannelHeader }) - builder.createDoc( - view.class.ViewletDescriptor, - core.space.Model, - { - label: chunter.string.Chat, - icon: view.icon.Table, - component: chunter.component.ChannelView - }, - chunter.viewlet.Chat - ) - - builder.createDoc(view.class.Viewlet, core.space.Model, { - attachTo: chunter.class.Message, - descriptor: chunter.viewlet.Chat, - config: [] - }) - builder.createDoc( view.class.ActionCategory, core.space.Model, @@ -327,38 +324,6 @@ export function createModel (builder: Builder, options = { addApplication: true chunter.category.Chunter ) - createAction( - builder, - { - action: chunter.actionImpl.MarkUnread, - label: chunter.string.MarkUnread, - input: 'focus', - category: chunter.category.Chunter, - target: chunter.class.Message, - context: { - mode: 'context', - group: 'edit' - } - }, - chunter.action.MarkUnread - ) - - createAction( - builder, - { - label: chunter.string.MarkUnread, - action: chunter.actionImpl.MarkCommentUnread, - input: 'focus', - category: chunter.category.Chunter, - target: chunter.class.ThreadMessage, - context: { - mode: 'context', - group: 'edit' - } - }, - chunter.action.MarkCommentUnread - ) - createAction( builder, { @@ -423,164 +388,20 @@ export function createModel (builder: Builder, options = { addApplication: true { label: chunter.string.ApplicationLabelChunter, icon: chunter.icon.Chunter, - locationResolver: chunter.resolver.Location, alias: chunterId, hidden: false, - navigatorModel: { - specials: [ - { - id: 'spaceBrowser', - component: workbench.component.SpaceBrowser, - icon: chunter.icon.ChannelBrowser, - label: chunter.string.ChannelBrowser, - position: 'top', - spaceClass: chunter.class.Channel, - componentProps: { - _class: chunter.class.Channel, - label: chunter.string.ChannelBrowser, - createItemDialog: chunter.component.CreateChannel, - createItemLabel: chunter.string.CreateChannel - } - }, - { - id: 'archive', - component: workbench.component.Archive, - icon: view.icon.Archive, - label: workbench.string.Archive, - position: 'top', - visibleIf: workbench.function.HasArchiveSpaces, - spaceClass: chunter.class.Channel - }, - { - id: 'threads', - label: chunter.string.Threads, - icon: chunter.icon.Thread, - component: chunter.component.Threads, - position: 'top' - }, - { - id: 'savedItems', - label: chunter.string.SavedItems, - icon: chunter.icon.Bookmark, - component: chunter.component.SavedMessages - }, - { - id: 'fileBrowser', - label: attachment.string.FileBrowser, - icon: attachment.icon.FileBrowser, - component: attachment.component.FileBrowser, - componentProps: { - requestedSpaceClasses: [chunter.class.Channel, chunter.class.DirectMessage] - } - }, - { - id: 'chunterBrowser', - label: chunter.string.ChunterBrowser, - icon: workbench.icon.Search, - component: chunter.component.ChunterBrowser, - visibleIf: chunter.function.ChunterBrowserVisible - } - ], - spaces: [ - { - id: 'channels', - label: chunter.string.Channels, - spaceClass: chunter.class.Channel, - addSpaceLabel: chunter.string.CreateChannel, - createComponent: chunter.component.CreateChannel - }, - { - id: 'directMessages', - label: chunter.string.DirectMessages, - spaceClass: chunter.class.DirectMessage, - addSpaceLabel: chunter.string.NewDirectMessage, - createComponent: chunter.component.CreateDirectMessage - } - ], - aside: chunter.component.ThreadView - } + component: chunter.component.Chat, + aside: chunter.component.ThreadView, + shouldNotify: chunter.function.ShouldNotify }, chunter.app.Chunter ) } - builder.createDoc( - activity.class.TxViewlet, - core.space.Model, - { - objectClass: chunter.class.Comment, - icon: chunter.icon.Chunter, - txClass: core.class.TxCreateDoc, - component: chunter.activity.TxCommentCreate, - label: chunter.string.LeftComment, - display: 'content', - editable: true, - hideOnRemove: true - }, - chunter.ids.TxCommentCreate - ) - - builder.createDoc( - activity.class.TxViewlet, - core.space.Model, - { - objectClass: chunter.class.ChatMessage, - icon: chunter.icon.Chunter, - txClass: core.class.TxCreateDoc, - component: chunter.activity.TxCommentCreate, - label: chunter.string.LeftComment, - display: 'content', - editable: true, - hideOnRemove: true - }, - chunter.ids.TxChatMessageCreate - ) - - createAction( - builder, - { - action: view.actionImpl.CopyTextToClipboard, - actionProps: { - textProvider: chunter.function.GetLink - }, - label: chunter.string.CopyLink, - icon: chunter.icon.Thread, - keyBinding: [], - input: 'none', - category: chunter.category.Chunter, - target: chunter.class.Comment, - context: { - mode: ['context', 'browser'], - group: 'copy' - } - }, - chunter.action.CopyCommentLink - ) - - builder.mixin(chunter.class.Comment, core.class.Class, view.mixin.IgnoreActions, { - actions: [view.action.Open] - }) - - builder.mixin(chunter.class.Message, core.class.Class, view.mixin.IgnoreActions, { - actions: [view.action.Open] - }) - - builder.mixin(chunter.class.ThreadMessage, core.class.Class, view.mixin.IgnoreActions, { - actions: [view.action.Open] - }) - - builder.mixin(chunter.class.Comment, core.class.Class, view.mixin.LinkProvider, { - encode: chunter.function.GetFragment - }) - builder.mixin(chunter.class.ThreadMessage, core.class.Class, view.mixin.LinkProvider, { encode: chunter.function.GetFragment }) - builder.mixin(chunter.class.Message, core.class.Class, view.mixin.LinkProvider, { - encode: chunter.function.GetFragment - }) - createAction( builder, { @@ -593,114 +414,19 @@ export function createModel (builder: Builder, options = { addApplication: true keyBinding: [], input: 'none', category: chunter.category.Chunter, - target: chunter.class.Message, + target: activity.class.ActivityMessage, context: { mode: ['context', 'browser'], application: chunter.app.Chunter, group: 'copy' } }, - chunter.action.CopyMessageLink - ) - - createAction( - builder, - { - action: view.actionImpl.CopyTextToClipboard, - actionProps: { - textProvider: chunter.function.GetLink - }, - label: chunter.string.CopyLink, - icon: chunter.icon.Thread, - keyBinding: [], - input: 'none', - category: chunter.category.Chunter, - target: chunter.class.ThreadMessage, - context: { - mode: ['context', 'browser'], - application: chunter.app.Chunter, - group: 'copy' - } - }, - chunter.action.CopyThreadMessageLink - ) - - // We need to define this one, to hide default attached object removed case - builder.createDoc( - activity.class.TxViewlet, - core.space.Model, - { - objectClass: chunter.class.Comment, - icon: chunter.icon.Chunter, - txClass: core.class.TxRemoveDoc, - display: 'inline', - hideOnRemove: true - }, - chunter.ids.TxCommentRemove - ) - - builder.createDoc( - activity.class.TxViewlet, - core.space.Model, - { - objectClass: chunter.class.ChatMessage, - icon: chunter.icon.Chunter, - txClass: core.class.TxRemoveDoc, - display: 'inline', - hideOnRemove: true - }, - chunter.ids.TxChatMessageRemove - ) - - builder.createDoc( - activity.class.TxViewlet, - core.space.Model, - { - objectClass: chunter.class.Message, - icon: chunter.icon.Chunter, - txClass: core.class.TxCreateDoc, - component: chunter.activity.TxMessageCreate, - label: chunter.string.DMNotification, - display: 'content', - editable: true, - hideOnRemove: true - }, - chunter.ids.TxMessageCreate - ) - - builder.createDoc( - activity.class.TxViewlet, - core.space.Model, - { - objectClass: chunter.class.Backlink, - icon: chunter.icon.Chunter, - txClass: core.class.TxCreateDoc, - component: chunter.component.BacklinkContent, - label: chunter.string.MentionedIn, - labelComponent: chunter.component.BacklinkReference, - display: 'emphasized', - editable: false, - hideOnRemove: true - }, - chunter.ids.TxBacklinkCreate - ) - - // We need to define this one, to hide default attached object removed case - builder.createDoc( - activity.class.TxViewlet, - core.space.Model, - { - objectClass: chunter.class.Backlink, - icon: chunter.icon.Chunter, - txClass: core.class.TxRemoveDoc, - display: 'inline', - hideOnRemove: true - }, - chunter.ids.TxBacklinkRemove + chunter.action.CopyChatMessageLink ) builder.createDoc(activity.class.ActivityMessagesFilter, core.space.Model, { label: chunter.string.FilterBacklinks, + position: 60, filter: chunter.filter.BacklinksFilter }) @@ -753,7 +479,7 @@ export function createModel (builder: Builder, options = { addApplication: true generated: false, hidden: false, txClasses: [core.class.TxCreateDoc], - objectClass: chunter.class.Message, + objectClass: chunter.class.ChatMessage, providers: { [notification.providers.EmailNotification]: false, [notification.providers.PlatformNotification]: true @@ -776,7 +502,7 @@ export function createModel (builder: Builder, options = { addApplication: true generated: false, hidden: false, txClasses: [core.class.TxCreateDoc], - objectClass: chunter.class.Message, + objectClass: chunter.class.ChatMessage, providers: { [notification.providers.PlatformNotification]: true }, @@ -841,10 +567,16 @@ export function createModel (builder: Builder, options = { addApplication: true builder.createDoc(activity.class.ActivityMessagesFilter, core.space.Model, { label: chunter.string.Comments, + position: 60, filter: chunter.filter.ChatMessagesFilter }) + builder.mixin(chunter.class.ChatMessage, core.class.Class, activity.mixin.ActivityDoc, {}) + builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.ObjectIdentifier, { + provider: chunter.function.DmIdentifierProvider + }) + builder.mixin(chunter.class.ChatMessage, core.class.Class, view.mixin.CollectionPresenter, { presenter: chunter.component.ChatMessagesPresenter }) @@ -853,6 +585,21 @@ export function createModel (builder: Builder, options = { addApplication: true presenter: chunter.component.ChatMessagePresenter }) + builder.mixin(chunter.class.ThreadMessage, core.class.Class, view.mixin.ObjectPresenter, { + presenter: chunter.component.ThreadMessagePresenter + }) + + builder.createDoc( + chunter.class.ChatMessageViewlet, + core.space.Model, + { + messageClass: chunter.class.ThreadMessage, + objectClass: chunter.class.ChatMessage, + label: chunter.string.RepliedToThread + }, + chunter.ids.ThreadMessageViewlet + ) + createAction( builder, { @@ -863,10 +610,59 @@ export function createModel (builder: Builder, options = { addApplication: true keyBinding: ['Backspace'], category: chunter.category.Chunter, target: chunter.class.ChatMessage, - context: { mode: ['context', 'browser'], group: 'edit' } + visibilityTester: chunter.function.CanDeleteMessage, + context: { mode: ['context', 'browser'], group: 'remove' } }, chunter.action.DeleteChatMessage ) + + createAction( + builder, + { + action: chunter.actionImpl.ReplyToThread, + label: chunter.string.ReplyToThread, + icon: chunter.icon.Thread, + input: 'focus', + category: chunter.category.Chunter, + target: activity.class.ActivityMessage, + context: { mode: 'context', application: notification.app.Notification, group: 'edit' } + }, + chunter.action.ReplyToThread + ) + + createAction( + builder, + { + ...viewTemplates.open, + target: notification.class.DocNotifyContext, + context: { + mode: ['browser', 'context'], + group: 'create' + }, + action: chunter.actionImpl.OpenChannel + }, + chunter.action.OpenChannel + ) + + builder.createDoc(activity.class.ActivityExtension, core.space.Model, { + ofClass: chunter.class.Channel, + components: { input: chunter.component.ChatMessageInput } + }) + + builder.createDoc(activity.class.ActivityExtension, core.space.Model, { + ofClass: chunter.class.DirectMessage, + components: { input: chunter.component.ChatMessageInput } + }) + + builder.createDoc(activity.class.ActivityExtension, core.space.Model, { + ofClass: activity.class.DocUpdateMessage, + components: { input: chunter.component.ChatMessageInput } + }) + + builder.createDoc(activity.class.ActivityExtension, core.space.Model, { + ofClass: chunter.class.ChatMessage, + components: { input: chunter.component.ChatMessageInput } + }) } export default chunter diff --git a/models/chunter/src/migration.ts b/models/chunter/src/migration.ts index d91e8faccd..ee2a1593ad 100644 --- a/models/chunter/src/migration.ts +++ b/models/chunter/src/migration.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import core, { TxOperations } from '@hcengineering/core' +import core, { type Class, type Doc, type Ref, TxOperations } from '@hcengineering/core' import { type MigrateOperation, type MigrationClient, @@ -22,14 +22,44 @@ import { } from '@hcengineering/model' import { chunterId } from '@hcengineering/chunter' import { DOMAIN_ACTIVITY } from '@hcengineering/model-activity' +import notification from '@hcengineering/notification' import { DOMAIN_COMMENT } from './index' import chunter from './plugin' -export async function createGeneral (tx: TxOperations): Promise { +export async function createDocNotifyContexts ( + client: MigrationUpgradeClient, + tx: TxOperations, + attachedTo: Ref, + attachedToClass: Ref> +): Promise { + const users = await client.findAll(core.class.Account, {}) + for (const user of users) { + if (user._id === core.account.System) { + continue + } + const docNotifyContext = await client.findOne(notification.class.DocNotifyContext, { + user: user._id, + attachedTo, + attachedToClass + }) + + if (docNotifyContext === undefined) { + await tx.createDoc(notification.class.DocNotifyContext, core.space.Space, { + user: user._id, + attachedTo, + attachedToClass, + hidden: false + }) + } + } +} + +export async function createGeneral (client: MigrationUpgradeClient, tx: TxOperations): Promise { const createTx = await tx.findOne(core.class.TxCreateDoc, { objectId: chunter.space.General }) + if (createTx === undefined) { await tx.createDoc( chunter.class.Channel, @@ -45,12 +75,15 @@ export async function createGeneral (tx: TxOperations): Promise { chunter.space.General ) } + + await createDocNotifyContexts(client, tx, chunter.space.General, chunter.class.Channel) } -export async function createRandom (tx: TxOperations): Promise { +export async function createRandom (client: MigrationUpgradeClient, tx: TxOperations): Promise { const createTx = await tx.findOne(core.class.TxCreateDoc, { objectId: chunter.space.Random }) + if (createTx === undefined) { await tx.createDoc( chunter.class.Channel, @@ -66,6 +99,8 @@ export async function createRandom (tx: TxOperations): Promise { chunter.space.Random ) } + + await createDocNotifyContexts(client, tx, chunter.space.Random, chunter.class.Channel) } async function createBacklink (tx: TxOperations): Promise { @@ -104,8 +139,8 @@ export const chunterOperation: MigrateOperation = { }, async upgrade (client: MigrationUpgradeClient): Promise { const tx = new TxOperations(client, core.account.System) - await createGeneral(tx) - await createRandom(tx) + await createGeneral(client, tx) + await createRandom(client, tx) await createBacklink(tx) } } diff --git a/models/chunter/src/plugin.ts b/models/chunter/src/plugin.ts index 99418b9442..a7e5cfa6f1 100644 --- a/models/chunter/src/plugin.ts +++ b/models/chunter/src/plugin.ts @@ -16,8 +16,8 @@ import type { ActivityMessage, DocUpdateMessageViewlet, TxViewlet } from '@hcengineering/activity' import { chunterId, type Channel } from '@hcengineering/chunter' import chunter from '@hcengineering/chunter-resources/src/plugin' -import type { Doc, Ref, Space } from '@hcengineering/core' -import { type NotificationGroup } from '@hcengineering/notification' +import { type Client, type Doc, type Ref } from '@hcengineering/core' +import { type DocNotifyContext, type NotificationGroup } from '@hcengineering/notification' import type { IntlString, Resource } from '@hcengineering/platform' import { mergeIds } from '@hcengineering/platform' import type { AnyComponent, Location } from '@hcengineering/ui/src/types' @@ -29,11 +29,10 @@ export default mergeIds(chunterId, chunter, { DirectMessagePresenter: '' as AnyComponent, MessagePresenter: '' as AnyComponent, DmPresenter: '' as AnyComponent, - Threads: '' as AnyComponent, - SavedMessages: '' as AnyComponent, - ChunterBrowser: '' as AnyComponent, BacklinkContent: '' as AnyComponent, - BacklinkReference: '' as AnyComponent + BacklinkReference: '' as AnyComponent, + ChannelsPanel: '' as AnyComponent, + Chat: '' as AnyComponent }, action: { MarkCommentUnread: '' as Ref, @@ -41,17 +40,15 @@ export default mergeIds(chunterId, chunter, { ArchiveChannel: '' as Ref, UnarchiveChannel: '' as Ref, ConvertToPrivate: '' as Ref, - CopyCommentLink: '' as Ref>, - CopyThreadMessageLink: '' as Ref>, - CopyMessageLink: '' as Ref> + CopyChatMessageLink: '' as Ref>, + OpenChannel: '' as Ref }, actionImpl: { - MarkUnread: '' as ViewAction, - MarkCommentUnread: '' as ViewAction, ArchiveChannel: '' as ViewAction, UnarchiveChannel: '' as ViewAction, ConvertDmToPrivateChannel: '' as ViewAction, - DeleteChatMessage: '' as ViewAction + DeleteChatMessage: '' as ViewAction, + ReplyToThread: '' as ViewAction }, category: { Chunter: '' as Ref @@ -62,7 +59,6 @@ export default mergeIds(chunterId, chunter, { Content: '' as IntlString, Comment: '' as IntlString, Reference: '' as IntlString, - Chat: '' as IntlString, CreateBy: '' as IntlString, Create: '' as IntlString, Edit: '' as IntlString, @@ -71,14 +67,15 @@ export default mergeIds(chunterId, chunter, { MentionNotification: '' as IntlString, PinnedMessages: '' as IntlString, SavedMessages: '' as IntlString, - ThreadMessage: '' as IntlString, Emoji: '' as IntlString, FilterBacklinks: '' as IntlString, DM: '' as IntlString, DMNotification: '' as IntlString, ConfigLabel: '' as IntlString, ConfigDescription: '' as IntlString, - Reacted: '' as IntlString + Reacted: '' as IntlString, + Saved: '' as IntlString, + RepliedToThread: '' as IntlString }, viewlet: { Chat: '' as Ref @@ -105,9 +102,12 @@ export default mergeIds(chunterId, chunter, { Random: '' as Ref }, function: { - ChunterBrowserVisible: '' as Resource<(spaces: Space[]) => Promise>, GetLink: '' as Resource<(doc: Doc, props: Record) => Promise>, - GetFragment: '' as Resource<(doc: Doc, props: Record) => Promise> + GetFragment: '' as Resource<(doc: Doc, props: Record) => Promise>, + ShouldNotify: '' as Resource<(docNotifyContexts: DocNotifyContext[]) => Promise>, + DmIdentifierProvider: '' as Resource<(client: Client, ref: Ref, doc?: T) => Promise>, + CanDeleteMessage: '' as Resource<(doc?: Doc | Doc[]) => Promise>, + GetChunterSpaceLinkFragment: '' as Resource<(doc: Doc, props: Record) => Promise> }, filter: { BacklinksFilter: '' as Resource<(message: ActivityMessage, _class?: Ref) => boolean>, diff --git a/models/contact/src/index.ts b/models/contact/src/index.ts index 4d4bbf57fa..2b55024d43 100644 --- a/models/contact/src/index.ts +++ b/models/contact/src/index.ts @@ -243,6 +243,8 @@ export function createModel (builder: Builder): void { ] }) + builder.mixin(contact.class.Channel, core.class.Class, activity.mixin.ActivityDoc, {}) + builder.createDoc(activity.class.ActivityExtension, core.space.Model, { ofClass: contact.class.Contact, components: { input: chunter.component.ChatMessageInput } @@ -280,6 +282,7 @@ export function createModel (builder: Builder): void { hidden: false, // component: contact.component.ContactsTabs, locationResolver: contact.resolver.Location, + aside: chunter.component.ThreadView, navigatorModel: { spaces: [], specials: [ @@ -988,6 +991,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: contact.class.Person, label: chunter.string.LeftComment }, @@ -998,6 +1002,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: contact.mixin.Employee, label: chunter.string.LeftComment }, @@ -1008,6 +1013,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: contact.class.Organization, label: chunter.string.LeftComment }, diff --git a/models/inventory/src/index.ts b/models/inventory/src/index.ts index 23c34e068e..ec63238fef 100644 --- a/models/inventory/src/index.ts +++ b/models/inventory/src/index.ts @@ -148,6 +148,7 @@ export function createModel (builder: Builder): void { icon: inventory.icon.InventoryApplication, alias: inventoryId, hidden: false, + aside: chunter.component.ThreadView, navigatorModel: { specials: [ { @@ -187,6 +188,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: inventory.class.Product, label: chunter.string.LeftComment }, @@ -197,6 +199,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: inventory.class.Category, label: chunter.string.LeftComment }, diff --git a/models/lead/src/index.ts b/models/lead/src/index.ts index f96d080aca..b6f4b46dc7 100644 --- a/models/lead/src/index.ts +++ b/models/lead/src/index.ts @@ -155,6 +155,7 @@ export function createModel (builder: Builder): void { icon: lead.icon.LeadApplication, alias: leadId, hidden: false, + aside: chunter.component.ThreadView, navigatorModel: { specials: [ { @@ -542,6 +543,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: lead.class.Lead, label: chunter.string.LeftComment }, diff --git a/models/notification/src/index.ts b/models/notification/src/index.ts index 6a31b9b912..b092b10ef0 100644 --- a/models/notification/src/index.ts +++ b/models/notification/src/index.ts @@ -43,7 +43,8 @@ import { TypeString, UX, TypeBoolean, - TypeDate + TypeDate, + TypeIntlString } from '@hcengineering/model' import core, { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core' import preference, { TPreference } from '@hcengineering/model-preference' @@ -65,7 +66,9 @@ import { type NotificationTemplate, type NotificationType, notificationId, - type NotificationObjectPresenter + type NotificationObjectPresenter, + type ActivityInboxNotification, + type CommonInboxNotification } from '@hcengineering/notification' import { type Asset, type IntlString } from '@hcengineering/platform' import setting from '@hcengineering/setting' @@ -197,18 +200,13 @@ export class TDocNotifyContext extends TDoc implements DocNotifyContext { @Prop(TypeDate(), core.string.Date) @Index(IndexKind.Indexed) lastUpdateTimestamp?: Timestamp + + @Prop(TypeBoolean(), notification.string.Pinned) + isPinned?: boolean } @Model(notification.class.InboxNotification, core.class.Doc, DOMAIN_NOTIFICATION) export class TInboxNotification extends TDoc implements InboxNotification { - @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedTo) - @Index(IndexKind.Indexed) - attachedTo!: Ref - - @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedToClass) - @Index(IndexKind.Indexed) - attachedToClass!: Ref> - @Prop(TypeRef(notification.class.DocNotifyContext), core.string.AttachedTo) @Index(IndexKind.Indexed) docNotifyContext!: Ref @@ -220,6 +218,32 @@ export class TInboxNotification extends TDoc implements InboxNotification { @Prop(TypeBoolean(), core.string.Boolean) @Index(IndexKind.Indexed) isViewed!: boolean + + title?: IntlString + body?: IntlString + intlParams?: Record + intlParamsNotLocalized?: Record +} + +@Model(notification.class.ActivityInboxNotification, notification.class.InboxNotification) +export class TActivityInboxNotification extends TInboxNotification implements ActivityInboxNotification { + @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedTo) + @Index(IndexKind.Indexed) + attachedTo!: Ref + + @Prop(TypeRef(activity.class.ActivityMessage), core.string.AttachedToClass) + @Index(IndexKind.Indexed) + attachedToClass!: Ref> +} + +@Model(notification.class.CommonInboxNotification, notification.class.InboxNotification) +export class TCommonInboxNotification extends TInboxNotification implements CommonInboxNotification { + @Prop(TypeIntlString(), notification.string.Message) + message!: IntlString + + props!: Record + icon!: Asset + iconProps!: Record } export function createModel (builder: Builder): void { @@ -236,7 +260,9 @@ export function createModel (builder: Builder): void { TNotificationObjectPresenter, TNotificationPreview, TDocNotifyContext, - TInboxNotification + TInboxNotification, + TActivityInboxNotification, + TCommonInboxNotification ) // Temporarily disabled, we should think about it @@ -283,20 +309,6 @@ export function createModel (builder: Builder): void { notification.ids.NotificationSettings ) - builder.createDoc( - workbench.class.Application, - core.space.Model, - { - label: notification.string.Inbox, - icon: notification.icon.Notifications, - alias: notificationId, - hidden: true, - component: notification.component.Inbox, - aside: chunter.component.ThreadView - }, - notification.app.Notification - ) - builder.createDoc( workbench.class.Application, core.space.Model, @@ -312,7 +324,7 @@ export function createModel (builder: Builder): void { specials: [ { id: 'all', - component: notification.component.NewInbox, + component: notification.component.Inbox, icon: activity.icon.Activity, label: activity.string.AllActivity, componentProps: { @@ -322,7 +334,7 @@ export function createModel (builder: Builder): void { }, { id: 'reactions', - component: notification.component.NewInbox, + component: notification.component.Inbox, icon: activity.icon.Emoji, label: activity.string.Reactions, componentProps: { @@ -336,57 +348,6 @@ export function createModel (builder: Builder): void { notification.app.Inbox ) - createAction( - builder, - { - action: notification.actionImpl.MarkAsUnread, - actionProps: {}, - label: notification.string.MarkAsUnread, - icon: notification.icon.Track, - input: 'focus', - visibilityTester: notification.function.HasntNotifications, - category: notification.category.Notification, - target: notification.class.DocUpdates, - context: { mode: 'context', application: notification.app.Notification, group: 'edit' } - }, - notification.action.MarkAsUnread - ) - - createAction( - builder, - { - action: notification.actionImpl.Hide, - actionProps: {}, - label: notification.string.Archive, - icon: view.icon.Archive, - input: 'focus', - keyBinding: ['Backspace'], - category: notification.category.Notification, - target: notification.class.DocUpdates, - context: { mode: ['context', 'browser'], group: 'edit' } - }, - notification.action.Hide - ) - - createAction( - builder, - { - action: notification.actionImpl.Unsubscribe, - actionProps: {}, - label: notification.string.DontTrack, - icon: notification.icon.Hide, - input: 'focus', - category: notification.category.Notification, - target: notification.class.DocUpdates, - context: { mode: 'context', application: notification.app.Notification, group: 'edit' } - }, - notification.action.Unsubscribe - ) - - builder.mixin(notification.class.DocUpdates, core.class.Class, view.mixin.IgnoreActions, { - actions: [view.action.Delete, view.action.Open] - }) - createAction(builder, { action: workbench.actionImpl.Navigate, actionProps: { @@ -486,7 +447,7 @@ export function createModel (builder: Builder): void { input: 'focus', visibilityTester: notification.function.HasMarkAsReadAction, category: notification.category.Notification, - target: activity.class.ActivityMessage, + target: notification.class.InboxNotification, context: { mode: 'context', application: notification.app.Notification, group: 'edit' } }, notification.action.MarkAsReadInboxNotification @@ -501,7 +462,7 @@ export function createModel (builder: Builder): void { input: 'focus', visibilityTester: notification.function.HasMarkAsUnreadAction, category: notification.category.Notification, - target: activity.class.ActivityMessage, + target: notification.class.InboxNotification, context: { mode: 'context', application: notification.app.Notification, group: 'edit' } }, notification.action.MarkAsUnreadInboxNotification @@ -516,12 +477,89 @@ export function createModel (builder: Builder): void { input: 'focus', keyBinding: ['Backspace'], category: notification.category.Notification, - visibilityTester: notification.function.HasDeleteNotificationAction, - target: activity.class.ActivityMessage, + target: notification.class.InboxNotification, context: { mode: ['context', 'browser'], group: 'edit' } }, notification.action.DeleteInboxNotification ) + + createAction( + builder, + { + action: notification.actionImpl.HideDocNotifyContext, + label: view.string.Archive, + icon: view.icon.Archive, + input: 'focus', + category: view.category.General, + target: notification.class.DocNotifyContext, + context: { + mode: ['browser', 'context'], + group: 'remove' + }, + visibilityTester: notification.function.IsDocNotifyContextVisible + }, + notification.action.HideDocNotifyContext + ) + + createAction( + builder, + { + action: notification.actionImpl.UnHideDocNotifyContext, + label: view.string.UnArchive, + icon: view.icon.Archive, + input: 'focus', + category: view.category.General, + target: notification.class.DocNotifyContext, + context: { + mode: ['browser', 'context'], + group: 'remove' + }, + visibilityTester: notification.function.IsDocNotifyContextHidden + }, + notification.action.UnHideDocNotifyContext + ) + + createAction( + builder, + { + action: notification.actionImpl.PinDocNotifyContext, + label: view.string.Pin, + icon: notification.icon.Track, + input: 'focus', + category: notification.category.Notification, + target: notification.class.DocNotifyContext, + visibilityTester: notification.function.HasDocNotifyContextPinAction, + context: { mode: ['context', 'browser'], group: 'edit' } + }, + notification.action.PinDocNotifyContext + ) + + createAction( + builder, + { + action: notification.actionImpl.UnpinDocNotifyContext, + label: view.string.Unpin, + icon: notification.icon.Track, + input: 'focus', + category: notification.category.Notification, + target: notification.class.DocNotifyContext, + visibilityTester: notification.function.HasDocNotifyContextUnpinAction, + context: { mode: ['context', 'browser'], group: 'edit' } + }, + notification.action.UnpinDocNotifyContext + ) + + builder.mixin(notification.class.DocNotifyContext, core.class.Class, view.mixin.ObjectPresenter, { + presenter: notification.component.DocNotifyContextPresenter + }) + + builder.mixin(notification.class.ActivityInboxNotification, core.class.Class, view.mixin.ObjectPresenter, { + presenter: notification.component.ActivityInboxNotificationPresenter + }) + + builder.mixin(notification.class.CommonInboxNotification, core.class.Class, view.mixin.ObjectPresenter, { + presenter: notification.component.CommonInboxNotificationPresenter + }) } export function generateClassNotificationTypes ( diff --git a/models/notification/src/migration.ts b/models/notification/src/migration.ts index feaea55f93..fe838f407f 100644 --- a/models/notification/src/migration.ts +++ b/models/notification/src/migration.ts @@ -13,9 +13,41 @@ // limitations under the License. // -import core, { TxOperations } from '@hcengineering/core' -import { type MigrateOperation, type MigrationClient, type MigrationUpgradeClient } from '@hcengineering/model' -import notification from '@hcengineering/notification' +import core, { + type AttachedDoc, + type Doc, + type Domain, + DOMAIN_TX, + generateId, + type Ref, + type TxCollectionCUD, + type TxCUD, + TxOperations, + TxProcessor +} from '@hcengineering/core' +import { + type MigrateOperation, + type MigrationClient, + type MigrationUpgradeClient, + tryMigrate +} from '@hcengineering/model' +import notification, { + type DocNotifyContext, + type DocUpdates, + type DocUpdateTx, + type InboxNotification, + notificationId +} from '@hcengineering/notification' +import activity, { type ActivityMessage, type DocUpdateMessage } from '@hcengineering/activity' + +import { DOMAIN_NOTIFICATION } from './index' + +interface InboxData { + context: DocNotifyContext + notifications: InboxNotification[] +} + +const DOMAIN_ACTIVITY = 'activity' as Domain async function createSpace (client: MigrationUpgradeClient): Promise { const txop = new TxOperations(client, core.account.System) @@ -38,8 +70,140 @@ async function createSpace (client: MigrationUpgradeClient): Promise { } } +async function getActivityMessages ( + client: MigrationClient, + tx: DocUpdateTx, + context: DocNotifyContext +): Promise { + const docUpdateMessages = await client.find(DOMAIN_ACTIVITY, { + _class: activity.class.DocUpdateMessage, + txId: tx._id, + attachedTo: context.attachedTo + }) + + if (docUpdateMessages.length > 0) { + return docUpdateMessages + } + + const originTx = (await client.find>(DOMAIN_TX, { _id: tx._id }))[0] + + if (originTx === undefined) { + return [] + } + + const innerTx = TxProcessor.extractTx(originTx as TxCollectionCUD) as TxCUD + + return ( + await client.find(DOMAIN_ACTIVITY, { + _id: innerTx.objectId as Ref, + attachedTo: context.attachedTo + }) + ).filter(({ _class }) => client.hierarchy.isDerived(_class, activity.class.ActivityMessage)) +} + +async function getInboxNotifications ( + client: MigrationClient, + tx: DocUpdateTx, + context: DocNotifyContext +): Promise { + const messages = await getActivityMessages(client, tx, context) + + if (messages.length === 0) { + return [] + } + + return messages.map((message) => ({ + _id: generateId(), + _class: notification.class.InboxNotification, + space: context.space, + user: context.user, + isViewed: !tx.isNew, + attachedTo: message._id, + attachedToClass: message._class, + docNotifyContext: 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 + })) +} + +async function getInboxData (client: MigrationClient, docUpdate: DocUpdates): Promise { + if (!client.hierarchy.hasClass(docUpdate.attachedToClass)) { + console.log('cannot find class: ', docUpdate.attachedToClass) + return + } + + const { txes } = docUpdate + const newTxIndex = 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 ? txes[newTxIndex - 1]?.modifiedOn : docUpdate.lastTxTime, + lastUpdateTimestamp: docUpdate.lastTxTime, + modifiedBy: docUpdate.modifiedBy, + modifiedOn: docUpdate.modifiedOn, + createdBy: docUpdate.createdBy, + createdOn: docUpdate.createdOn + } + + const notifications = (await Promise.all(txes.map((tx) => getInboxNotifications(client, tx, context)))).flat() + + return { + context, + notifications + } +} + +async function migrateInboxNotifications (client: MigrationClient): Promise { + while (true) { + const docUpdates = await client.find( + DOMAIN_NOTIFICATION, + { + _class: notification.class.DocUpdates + }, + { limit: 500 } + ) + + if (docUpdates.length === 0) { + return + } + + const data: InboxData[] = ( + await Promise.all(docUpdates.map((docUpdate) => getInboxData(client, docUpdate))) + ).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 const notificationOperation: MigrateOperation = { - async migrate (client: MigrationClient): Promise {}, + async migrate (client: MigrationClient): Promise { + await tryMigrate(client, notificationId, [ + { + state: 'inbox-notifications', + func: migrateInboxNotifications + } + ]) + }, async upgrade (client: MigrationUpgradeClient): Promise { await createSpace(client) } diff --git a/models/notification/src/plugin.ts b/models/notification/src/plugin.ts index ccee946854..0a63725891 100644 --- a/models/notification/src/plugin.ts +++ b/models/notification/src/plugin.ts @@ -31,7 +31,8 @@ export default mergeIds(notificationId, notification, { Archive: '' as IntlString, MarkAsUnread: '' as IntlString, MarkAsRead: '' as IntlString, - ChangeCollaborators: '' as IntlString + ChangeCollaborators: '' as IntlString, + Message: '' as IntlString }, app: { Notification: '' as Ref, @@ -48,31 +49,30 @@ export default mergeIds(notificationId, notification, { component: { NotificationSettings: '' as AnyComponent, InboxAside: '' as AnyComponent, - ChatMessagePresenter: '' as AnyComponent, - DocUpdateMessagePresenter: '' as AnyComponent, - PinMessageAction: '' as AnyComponent + ActivityInboxNotificationPresenter: '' as AnyComponent, + CommonInboxNotificationPresenter: '' as AnyComponent }, function: { - HasntNotifications: '' as Resource<(doc?: Doc | Doc[]) => Promise>, HasMarkAsUnreadAction: '' as Resource<(doc?: Doc | Doc[]) => Promise>, HasMarkAsReadAction: '' as Resource<(doc?: Doc | Doc[]) => Promise>, - HasDeleteNotificationAction: '' as Resource<(doc?: Doc | Doc[]) => Promise> + HasDocNotifyContextPinAction: '' as Resource<(doc?: Doc | Doc[]) => Promise>, + HasDocNotifyContextUnpinAction: '' as Resource<(doc?: Doc | Doc[]) => Promise> }, category: { Notification: '' as Ref }, groups: {}, action: { - Unsubscribe: '' as Ref, - Hide: '' as Ref, - MarkAsUnread: '' as Ref + Unsubscribe: '' as Ref }, actionImpl: { Unsubscribe: '' as ViewAction, - Hide: '' as ViewAction, - MarkAsUnread: '' as ViewAction, MarkAsUnreadInboxNotification: '' as ViewAction, MarkAsReadInboxNotification: '' as ViewAction, - DeleteInboxNotification: '' as ViewAction + DeleteInboxNotification: '' as ViewAction, + UnpinDocNotifyContext: '' as ViewAction, + PinDocNotifyContext: '' as ViewAction, + HideDocNotifyContext: '' as ViewAction, + UnHideDocNotifyContext: '' as ViewAction } }) diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index b489d663ff..3718dae45a 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -298,6 +298,7 @@ export function createModel (builder: Builder): void { locationResolver: recruit.resolver.Location, alias: recruitId, hidden: false, + aside: chunter.component.ThreadView, navigatorModel: { spaces: [], specials: [ @@ -1619,6 +1620,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: recruit.class.Vacancy, label: chunter.string.LeftComment }, @@ -1629,6 +1631,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: recruit.class.Applicant, label: chunter.string.LeftComment }, @@ -1639,6 +1642,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: recruit.class.Review, label: chunter.string.LeftComment }, diff --git a/models/server-activity/package.json b/models/server-activity/package.json index b113d47b49..bce375443d 100644 --- a/models/server-activity/package.json +++ b/models/server-activity/package.json @@ -28,6 +28,7 @@ "@hcengineering/activity": "^0.6.0", "@hcengineering/core": "^0.6.28", "@hcengineering/model": "^0.6.7", + "@hcengineering/model-activity": "^0.6.0", "@hcengineering/platform": "^0.6.9", "@hcengineering/server-activity": "^0.6.0", "@hcengineering/server-activity-resources": "^0.6.0", diff --git a/models/server-activity/src/index.ts b/models/server-activity/src/index.ts index db61a72077..e88986c1dd 100644 --- a/models/server-activity/src/index.ts +++ b/models/server-activity/src/index.ts @@ -22,15 +22,13 @@ export { activityServerOperation } from './migration' export { serverActivityId } from '@hcengineering/server-activity' export function createModel (builder: Builder): void { - // NOTE: temporarily disabled - // builder.createDoc(serverCore.class.Trigger, core.space.Model, { - // trigger: serverNotification.trigger.OnReactionChanged, - // txMatch: { - // collection: 'reactions', - // objectClass: activity.class.ActivityMessage, - // _class: core.class.TxCollectionCUD - // } - // }) + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverActivity.trigger.OnReactionChanged, + txMatch: { + collection: 'reactions', + _class: core.class.TxCollectionCUD + } + }) builder.createDoc(serverCore.class.Trigger, core.space.Model, { trigger: serverActivity.trigger.ActivityMessagesHandler diff --git a/models/server-activity/src/migration.ts b/models/server-activity/src/migration.ts index 563a5ed276..f89dad5a00 100644 --- a/models/server-activity/src/migration.ts +++ b/models/server-activity/src/migration.ts @@ -36,6 +36,7 @@ import { serverActivityId } from '@hcengineering/server-activity' import { generateDocUpdateMessages } from '@hcengineering/server-activity-resources' +import { DOMAIN_ACTIVITY } from '@hcengineering/model-activity' function getActivityControl (client: MigrationClient): ActivityControl { const txFactory = new TxFactory(core.account.System, false) @@ -55,6 +56,16 @@ async function generateDocUpdateMessageByTx ( client: MigrationClient, objectCache?: DocObjectCache ): Promise { + const existsMessages = await client.find( + DOMAIN_ACTIVITY, + { _class: activity.class.DocUpdateMessage, txId: tx._id }, + { projection: { _id: 1 } } + ) + + if (existsMessages.length > 0) { + return + } + const createCollectionCUDTxes = await generateDocUpdateMessages(tx, control, undefined, undefined, objectCache) for (const collectionTx of createCollectionCUDTxes) { @@ -206,8 +217,14 @@ export const activityServerOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await tryMigrate(client, serverActivityId, [ { - state: 'activity-messages', - func: createDocUpdateMessages + state: 'doc-update-messages', + func: async (client) => { + // Recreate activity to avoid duplicates + await client.deleteMany(DOMAIN_ACTIVITY, { + _class: activity.class.DocUpdateMessage + }) + await createDocUpdateMessages(client) + } } ]) }, diff --git a/models/server-chunter/src/index.ts b/models/server-chunter/src/index.ts index 299a747d70..f929ed86d1 100644 --- a/models/server-chunter/src/index.ts +++ b/models/server-chunter/src/index.ts @@ -21,6 +21,7 @@ import serverNotification from '@hcengineering/server-notification' import serverCore, { type ObjectDDParticipant } from '@hcengineering/server-core' import serverChunter from '@hcengineering/server-chunter' import notification from '@hcengineering/notification' + export { serverChunterId } from '@hcengineering/server-chunter' export function createModel (builder: Builder): void { @@ -45,7 +46,7 @@ export function createModel (builder: Builder): void { presenter: serverChunter.function.ChunterNotificationContentProvider }) - builder.mixin(chunter.class.Message, core.class.Class, serverNotification.mixin.NotificationPresenter, { + builder.mixin(chunter.class.ChatMessage, core.class.Class, serverNotification.mixin.NotificationPresenter, { presenter: serverChunter.function.ChunterNotificationContentProvider }) @@ -58,11 +59,22 @@ export function createModel (builder: Builder): void { }) builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverChunter.trigger.OnMessageSent, + trigger: serverChunter.trigger.OnDirectMessageSent, txMatch: { - objectClass: chunter.class.DirectMessage, _class: core.class.TxCollectionCUD, - collection: 'messages' + objectClass: chunter.class.DirectMessage, + collection: 'messages', + 'tx._class': core.class.TxCreateDoc, + 'tx.objectClass': chunter.class.ChatMessage + } + }) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverChunter.trigger.OnChatMessageRemoved, + txMatch: { + _class: core.class.TxCollectionCUD, + 'tx._class': core.class.TxRemoveDoc, + 'tx.objectClass': chunter.class.ChatMessage } }) diff --git a/models/server-notification/src/index.ts b/models/server-notification/src/index.ts index 8390902b69..2da67e62a9 100644 --- a/models/server-notification/src/index.ts +++ b/models/server-notification/src/index.ts @@ -31,6 +31,7 @@ import serverNotification, { type NotificationContentProvider } from '@hcengineering/server-notification' import chunter from '@hcengineering/model-chunter' +import activity from '@hcengineering/activity' export { serverNotificationId } from '@hcengineering/server-notification' @@ -60,17 +61,36 @@ export function createModel (builder: Builder): void { builder.createModel(THTMLPresenter, TTextPresenter, TTypeMatch, TNotificationPresenter) builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverNotification.trigger.OnBacklinkCreate - }) - - builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverNotification.trigger.NotificationMessagesHandler - }) - - builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverNotification.trigger.OnChatMessageSent, + trigger: serverNotification.trigger.ActivityNotificationsHandler, txMatch: { - objectClass: chunter.class.ChatMessage + _class: core.class.TxCollectionCUD, + 'tx._class': core.class.TxCreateDoc, + 'tx.objectClass': activity.class.DocUpdateMessage + } + }) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverNotification.trigger.OnActivityNotificationViewed, + txMatch: { + _class: core.class.TxUpdateDoc, + objectClass: notification.class.ActivityInboxNotification + } + }) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverNotification.trigger.OnChatMessageCreate, + txMatch: { + _class: core.class.TxCollectionCUD, + 'tx._class': core.class.TxCreateDoc, + 'tx.objectClass': chunter.class.ChatMessage + } + }) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverNotification.trigger.OnBacklinkCreate, + txMatch: { + _class: core.class.TxCollectionCUD, + 'tx._class': core.class.TxCreateDoc } }) diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 775e34c695..59dbabd771 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -161,6 +161,24 @@ function defineFilters (builder: Builder): void { getVisibleFilters: tracker.function.GetVisibleFilters }) + builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ObjectIdentifier, { + provider: tracker.function.IssueTitleProvider + }) + + builder.mixin(tracker.class.Issue, core.class.Class, chunter.mixin.ObjectChatPanel, { + ignoreKeys: [ + 'number', + 'createdBy', + 'attachedTo', + 'title', + 'collaborators', + 'description', + 'remainingTime', + 'reportedTime' + ], + titleProvider: tracker.function.IssueChatTitleProvider + }) + // // Issue Status // @@ -256,6 +274,7 @@ function defineApplication ( alias: trackerId, hidden: false, locationResolver: tracker.resolver.Location, + aside: chunter.component.ThreadView, navigatorModel: { specials: [ { @@ -607,6 +626,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: tracker.class.Issue, label: chunter.string.LeftComment }, @@ -617,6 +637,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: tracker.class.IssueTemplate, label: chunter.string.LeftComment }, @@ -627,6 +648,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: tracker.class.Component, label: chunter.string.LeftComment }, @@ -637,6 +659,7 @@ export function createModel (builder: Builder): void { chunter.class.ChatMessageViewlet, core.space.Model, { + messageClass: chunter.class.ChatMessage, objectClass: tracker.class.Milestone, label: chunter.string.LeftComment }, diff --git a/models/view/src/plugin.ts b/models/view/src/plugin.ts index cf2b9d6011..5c5979bdec 100644 --- a/models/view/src/plugin.ts +++ b/models/view/src/plugin.ts @@ -99,7 +99,9 @@ export default mergeIds(viewId, view, { General: '' as IntlString, Navigation: '' as IntlString, Editor: '' as IntlString, - MarkdownFormatting: '' as IntlString + MarkdownFormatting: '' as IntlString, + Pin: '' as IntlString, + Unpin: '' as IntlString }, function: { FilterArrayAllResult: '' as FilterFunction, diff --git a/models/workbench/src/plugin.ts b/models/workbench/src/plugin.ts index fd44116871..7918c3ba51 100644 --- a/models/workbench/src/plugin.ts +++ b/models/workbench/src/plugin.ts @@ -22,8 +22,6 @@ import workbench from '@hcengineering/workbench-resources/src/plugin' export default mergeIds(workbenchId, workbench, { component: { ApplicationPresenter: '' as AnyComponent, - Archive: '' as AnyComponent, - SpaceBrowser: '' as AnyComponent, SpecialView: '' as AnyComponent, ServerManager: '' as AnyComponent }, diff --git a/packages/core/src/time.ts b/packages/core/src/time.ts index 912c3932dc..fadb0dc6dd 100644 --- a/packages/core/src/time.ts +++ b/packages/core/src/time.ts @@ -1,11 +1,23 @@ -function isToday (time: number): boolean { - const current = new Date() - const target = new Date(time) - return ( - current.getDate() === target.getDate() && - current.getMonth() === target.getMonth() && - current.getFullYear() === target.getFullYear() - ) +// +// 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. +// +import { Timestamp } from './classes' + +export function getDay (time: Timestamp): Timestamp { + const date: Date = new Date(time) + date.setHours(0, 0, 0, 0) + return date.getTime() } export function getDisplayTime (time: number): string { @@ -20,3 +32,17 @@ export function getDisplayTime (time: number): string { return new Date(time).toLocaleString('default', options) } + +export function isOtherDay (time1: Timestamp, time2: Timestamp): boolean { + return getDay(time1) !== getDay(time2) +} + +function isToday (time: number): boolean { + const current = new Date() + const target = new Date(time) + return ( + current.getDate() === target.getDate() && + current.getMonth() === target.getMonth() && + current.getFullYear() === target.getFullYear() + ) +} diff --git a/packages/model/src/migration.ts b/packages/model/src/migration.ts index 93733772fd..37e58aa9b3 100644 --- a/packages/model/src/migration.ts +++ b/packages/model/src/migration.ts @@ -94,7 +94,7 @@ export interface MigrationClient { create: (domain: Domain, doc: T | T[]) => Promise delete: (domain: Domain, _id: Ref) => Promise - deleteMany: (domain: Domain, ids: Ref[]) => Promise + deleteMany: (domain: Domain, query: DocumentQuery) => Promise hierarchy: Hierarchy model: ModelDb diff --git a/packages/presentation/src/components/SpaceCreateCard.svelte b/packages/presentation/src/components/SpaceCreateCard.svelte index 459b1a0541..2161a1405e 100644 --- a/packages/presentation/src/components/SpaceCreateCard.svelte +++ b/packages/presentation/src/components/SpaceCreateCard.svelte @@ -48,6 +48,7 @@ dispatch('close') }} /> +
- {:else} -
- {#if message.attachments} -
- -
- {/if} - {#each links as link} - - {/each} - {/if} - {#if reactions?.length || (!thread && hasReplies)} - - {/if} - - -
- {#each extensions as mixinClass} - - {/each} - {#if !readOnly} - -
- -
- {#if !thread} - - {/if} - - { - showMenu(e) - }} - /> - {/if} -
- - - diff --git a/plugins/chunter-resources/src/components/MessagePresenter.svelte b/plugins/chunter-resources/src/components/MessagePresenter.svelte deleted file mode 100644 index 2db794b2ac..0000000000 --- a/plugins/chunter-resources/src/components/MessagePresenter.svelte +++ /dev/null @@ -1,62 +0,0 @@ - - - -{#if inline || embedded} - {#if presenter && doc} -
- {#if isThreadMessage} -
- -
- - -   - {/if} - -
- {/if} -{:else} -
-{/if} diff --git a/plugins/chunter-resources/src/components/MessagePreview.svelte b/plugins/chunter-resources/src/components/MessagePreview.svelte deleted file mode 100644 index df10ee0fa2..0000000000 --- a/plugins/chunter-resources/src/components/MessagePreview.svelte +++ /dev/null @@ -1,119 +0,0 @@ - - - -
-
-
- {#if employee && account} - {#if account._id !== me} - - {:else} -
-
- {#if value.attachments} -
- -
- {/if} - {#each links as link} - - {/each} -
-
- - diff --git a/plugins/chunter-resources/src/components/MessagesBrowser.svelte b/plugins/chunter-resources/src/components/MessagesBrowser.svelte deleted file mode 100644 index 74a6a5909c..0000000000 --- a/plugins/chunter-resources/src/components/MessagesBrowser.svelte +++ /dev/null @@ -1,117 +0,0 @@ - - -{#if withHeader} -
-
- -
- { - updateSearchQuery(search) - updateMessages(resultQuery) - }} - /> -
-{/if} - (resultQuery = e.detail)} /> -{#if messages.length > 0} - - {#each messages as message} - - -
{ - openMessageFromSpecial(message) - }} - > - -
- {/each} -
-{:else} -
-
-{/if} diff --git a/plugins/chunter-resources/src/components/PinnedMessages.svelte b/plugins/chunter-resources/src/components/PinnedMessages.svelte index 2f70e07938..fe11d6af66 100644 --- a/plugins/chunter-resources/src/components/PinnedMessages.svelte +++ b/plugins/chunter-resources/src/components/PinnedMessages.svelte @@ -1,29 +1,55 @@ + -{#if pinnedIds.length > 0} +{#if pinnedMessagesCount > 0}
-
{ - showMessages(ev) - }} - > +
{/if} diff --git a/plugins/chunter-resources/src/components/PinnedMessagesPopup.svelte b/plugins/chunter-resources/src/components/PinnedMessagesPopup.svelte index f4f5538da3..46ec8eab05 100644 --- a/plugins/chunter-resources/src/components/PinnedMessagesPopup.svelte +++ b/plugins/chunter-resources/src/components/PinnedMessagesPopup.svelte @@ -1,76 +1,37 @@ + @@ -80,34 +41,4 @@ max-height: 20rem; color: var(--caption-color); } - - .message { - padding: 0.75rem 1rem 0.75rem; - margin-bottom: 1rem; - box-shadow: inherit; - border-radius: inherit; - } - - .header { - display: flex; - flex-direction: row; - align-items: center; - - .name { - font-weight: 500; - margin-left: 1rem; - flex-grow: 2; - } - - .cross { - opacity: 0.4; - cursor: pointer; - &:hover { - opacity: 1; - } - } - } - .time { - font-size: 0.75rem; - } diff --git a/plugins/chunter-resources/src/components/Replies.svelte b/plugins/chunter-resources/src/components/Replies.svelte deleted file mode 100644 index 16c08d89cf..0000000000 --- a/plugins/chunter-resources/src/components/Replies.svelte +++ /dev/null @@ -1,128 +0,0 @@ - - - - - -
-
- {#each showReplies as reply} -
- {/each} - {#if employees.size > shown} -
+{employees.size - shown}
- {/if} -
-
-
- {#if hasNew} -
- {/if} - {#if (message.replies?.length ?? 0) > 1} -
-
- {/if} - -
- - diff --git a/plugins/chunter-resources/src/components/SavedMessages.svelte b/plugins/chunter-resources/src/components/SavedMessages.svelte deleted file mode 100644 index 7b98d456bf..0000000000 --- a/plugins/chunter-resources/src/components/SavedMessages.svelte +++ /dev/null @@ -1,175 +0,0 @@ - - -
-
- -
-
- - {#if savedMessages.length > 0 || savedAttachments.length > 0} - {#each savedMessages as message} - - -
{ - openMessageFromSpecial(message) - }} - > - -
- {/each} - {#each savedAttachments as att} - - -
openAttachment(att)}> - -
-
-
- {/each} - {:else} -
- -
-
- - -
- {/if} -
- - diff --git a/plugins/chunter-resources/src/components/SpaceHeader.svelte b/plugins/chunter-resources/src/components/SpaceHeader.svelte deleted file mode 100644 index e141bb6e70..0000000000 --- a/plugins/chunter-resources/src/components/SpaceHeader.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - -{#if channel} - {#if hierarchy.isDerived(channel._class, chunter.class.DirectMessage)} - - {:else} - - {/if} -{/if} diff --git a/plugins/chunter-resources/src/components/Thread.svelte b/plugins/chunter-resources/src/components/Thread.svelte deleted file mode 100644 index 3b8dd64d9a..0000000000 --- a/plugins/chunter-resources/src/components/Thread.svelte +++ /dev/null @@ -1,230 +0,0 @@ - - - -{#if showHeader && parent} -
- {#await getChannel(parent.space) then channel} - {#if channel?._class === plugin.class.Channel} - - {:else if channel} - - {/if} - {/await} -
- {#await getParticipants(comments, parent, $personByIdStore) then participants} - {participants.join(', ')} -
-
-{/if} -
- {#if parent} - - {#if total > comments.length} - - -
{ - showAll = true - }} - > -
- {/if} - {#each comments as comment (comment._id)} - - {/each} - {#if !readOnly} -
-
- -
-
- { - showSend = true - showActions = true - }} - bind:loading - /> -
-
- {/if} - {/if} -
- - diff --git a/plugins/chunter-resources/src/components/ThreadView.svelte b/plugins/chunter-resources/src/components/ThreadView.svelte deleted file mode 100644 index 77dfd9d8da..0000000000 --- a/plugins/chunter-resources/src/components/ThreadView.svelte +++ /dev/null @@ -1,249 +0,0 @@ - - - -
-
- - -
{ - dispatch('close') - }} - > - -
-
-
- {#if message} - - {#each comments as comment} - - {/each} - {/if} -
-
- -
- - diff --git a/plugins/chunter-resources/src/components/Threads.svelte b/plugins/chunter-resources/src/components/Threads.svelte deleted file mode 100644 index d25331b3a8..0000000000 --- a/plugins/chunter-resources/src/components/Threads.svelte +++ /dev/null @@ -1,67 +0,0 @@ - - - -
-
- -
-
- - - {#each threads as thread, i (thread)} -
- -
- {#if i < threads.length - 1} -
- {/if} - {/each} - diff --git a/plugins/chunter-resources/src/components/activity/TxCommentCreate.svelte b/plugins/chunter-resources/src/components/activity/TxCommentCreate.svelte deleted file mode 100644 index 4a507905ea..0000000000 --- a/plugins/chunter-resources/src/components/activity/TxCommentCreate.svelte +++ /dev/null @@ -1,116 +0,0 @@ - - - -
- {#if edit} - -
-
- {:else} - - - {#each links as link} - - {/each} - {/if} -
- - diff --git a/plugins/chunter-resources/src/components/activity/TxMessageCreate.svelte b/plugins/chunter-resources/src/components/activity/TxMessageCreate.svelte deleted file mode 100644 index d9776fcf76..0000000000 --- a/plugins/chunter-resources/src/components/activity/TxMessageCreate.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/plugins/chunter-resources/src/components/chat-message/ChatMessageHeader.svelte b/plugins/chunter-resources/src/components/chat-message/ChatMessageHeader.svelte index 4ed08c0e70..5d8c549dff 100644 --- a/plugins/chunter-resources/src/components/chat-message/ChatMessageHeader.svelte +++ b/plugins/chunter-resources/src/components/chat-message/ChatMessageHeader.svelte @@ -21,11 +21,14 @@ import { getLinkData, LinkData } from '@hcengineering/activity-resources' import notification from '@hcengineering/notification' + import chunter from '../../plugin' + export let message: ChatMessage export let person: Person | undefined export let viewlet: ChatMessageViewlet | undefined export let object: Doc | undefined export let parentObject: Doc | undefined + export let skipLabel = false let linkData: LinkData | undefined = undefined @@ -34,21 +37,21 @@ }) -{#if viewlet?.label} - +{#if !skipLabel} + {#if linkData} - + {linkData.title} - {#if message.isEdited} - - {/if} {/if} {/if} +{#if message.editedOn} + +{/if} diff --git a/plugins/chunter-resources/src/components/CreateChannel.svelte b/plugins/chunter-resources/src/components/chat/create/CreateChannel.svelte similarity index 76% rename from plugins/chunter-resources/src/components/CreateChannel.svelte rename to plugins/chunter-resources/src/components/chat/create/CreateChannel.svelte index 299052126e..1a0038c408 100644 --- a/plugins/chunter-resources/src/components/CreateChannel.svelte +++ b/plugins/chunter-resources/src/components/chat/create/CreateChannel.svelte @@ -1,5 +1,5 @@ + + +{#if !model.hideEmpty || notifyContexts.length > 0} + getGroupActions()}> + {#each notifyContexts as docNotifyContext} + {@const doc = docByNotifyContext.get(docNotifyContext._id)} + {#if doc} + {#key docNotifyContext._id} + + {/key} + {/if} + {/each} + +{/if} diff --git a/plugins/chunter-resources/src/components/chat/navigator/ChatNavItem.svelte b/plugins/chunter-resources/src/components/chat/navigator/ChatNavItem.svelte new file mode 100644 index 0000000000..adbb242156 --- /dev/null +++ b/plugins/chunter-resources/src/components/chat/navigator/ChatNavItem.svelte @@ -0,0 +1,121 @@ + + + + + {#await getChannelName(doc) then name} + await getItemActions(notifyContext)} + indent + /> + {/await} + diff --git a/plugins/chunter-resources/src/components/chat/navigator/ChatNavigator.svelte b/plugins/chunter-resources/src/components/chat/navigator/ChatNavigator.svelte new file mode 100644 index 0000000000..b161ca9cc3 --- /dev/null +++ b/plugins/chunter-resources/src/components/chat/navigator/ChatNavigator.svelte @@ -0,0 +1,75 @@ + + + + + + {#each chatSpecials as special, row} + {#if row > 0 && chatSpecials[row].position !== chatSpecials[row - 1].position} + + {/if} + {#await isSpecialVisible(special, notifyContexts) then isVisible} + {#if isVisible} + + + + {/if} + {/await} + {/each} + + {#each chatNavGroups as model} + + {/each} +
+ diff --git a/plugins/chunter-resources/src/components/chat/specials/ChannelBrowser.svelte b/plugins/chunter-resources/src/components/chat/specials/ChannelBrowser.svelte new file mode 100644 index 0000000000..a7ff040d40 --- /dev/null +++ b/plugins/chunter-resources/src/components/chat/specials/ChannelBrowser.svelte @@ -0,0 +1,262 @@ + + + +{#if withHeader} +
+
+ +
+ {#if createItemDialog} +
+
+ {/if} +
+
+
+ { + updateSearchQuery(search) + update(sort, resultQuery) + }} + /> + +
+ {#if withFilterButton} + + {/if} +
+
+{:else if withFilterButton} +
+
+ +
+
+{/if} + (resultQuery = e.detail)} /> + +
+ {#each spaces as space (space._id)} + {@const icon = getChannelIcon(space)} + {@const joined = space.members.includes(me)} + +
+
+
+ {#if icon} +
+ {/if} + +
+
+ {#if joined} +
+
+
+ {#if joined} +
+
+ {/each} +
+ {#if createItemDialog} +
+
+ {/if} +
+ + diff --git a/plugins/chunter-resources/src/components/ChunterBrowser.svelte b/plugins/chunter-resources/src/components/chat/specials/ChunterBrowser.svelte similarity index 82% rename from plugins/chunter-resources/src/components/ChunterBrowser.svelte rename to plugins/chunter-resources/src/components/chat/specials/ChunterBrowser.svelte index 009acfd5df..5c510b82b9 100644 --- a/plugins/chunter-resources/src/components/ChunterBrowser.svelte +++ b/plugins/chunter-resources/src/components/chat/specials/ChunterBrowser.svelte @@ -1,19 +1,34 @@ + + +{#if withHeader} +
+
+ +
+ { + updateSearchQuery(search) + updateMessages(resultQuery) + }} + /> +
+{/if} + (resultQuery = e.detail)} +/> +{#if messages.length > 0} + + {#each messages as message} + { + openMessageFromSpecial(message) + }} + /> + {/each} + +{:else} +
+
+{/if} diff --git a/plugins/chunter-resources/src/components/chat/specials/SavedMessages.svelte b/plugins/chunter-resources/src/components/chat/specials/SavedMessages.svelte new file mode 100644 index 0000000000..452c1be793 --- /dev/null +++ b/plugins/chunter-resources/src/components/chat/specials/SavedMessages.svelte @@ -0,0 +1,164 @@ + + + +
+
+ +
+
+ + {#if savedMessages.length > 0 || savedAttachments.length > 0} + {#each savedMessages as message} + {#if message.$lookup?.attachedTo} + + + + { + openMessageFromSpecial(message.$lookup?.attachedTo) + }} + /> + {/if} + {/each} + {#each savedAttachments as attach} + {#if attach.$lookup?.attachedTo} + + +
openAttachment(attach.$lookup?.attachedTo)} + > + +
+
+
+ {/if} + {/each} + {:else} +
+ +
+
+ + +
+ {/if} +
+ + diff --git a/plugins/chunter-resources/src/components/chat/specials/Threads.svelte b/plugins/chunter-resources/src/components/chat/specials/Threads.svelte new file mode 100644 index 0000000000..2ae4828e57 --- /dev/null +++ b/plugins/chunter-resources/src/components/chat/specials/Threads.svelte @@ -0,0 +1,55 @@ + + + +
+
+ +
+
+ + + {#each threads as thread} +
+ +
+ {/each} +
diff --git a/plugins/chunter-resources/src/components/chat/types.ts b/plugins/chunter-resources/src/components/chat/types.ts new file mode 100644 index 0000000000..4e89449862 --- /dev/null +++ b/plugins/chunter-resources/src/components/chat/types.ts @@ -0,0 +1,33 @@ +// +// 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. +// +import { type Asset, type IntlString } from '@hcengineering/platform' +import { type DocumentQuery } from '@hcengineering/core' +import { type DocNotifyContext } from '@hcengineering/notification' +import { type ViewAction } from '@hcengineering/view' +import { type AnyComponent } from '@hcengineering/ui' + +export interface ChatNavGroupModel { + id: string + label?: IntlString + addLabel?: IntlString + addComponent?: AnyComponent + query?: DocumentQuery + hideEmpty?: boolean + actions?: Array<{ + icon: Asset + label: IntlString + action: ViewAction + }> +} diff --git a/plugins/chunter-resources/src/components/chat/utils.ts b/plugins/chunter-resources/src/components/chat/utils.ts new file mode 100644 index 0000000000..49de383a2e --- /dev/null +++ b/plugins/chunter-resources/src/components/chat/utils.ts @@ -0,0 +1,178 @@ +// +// 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. +// +import notification, { type DocNotifyContext } from '@hcengineering/notification' +import { type Doc, type Ref } from '@hcengineering/core' +import { getClient } from '@hcengineering/presentation' + +import view from '@hcengineering/view' +import workbench, { type SpecialNavModel } from '@hcengineering/workbench' +import attachment from '@hcengineering/attachment' +import activity from '@hcengineering/activity' + +import { type ChatNavGroupModel } from './types' +import chunter from '../../plugin' + +export const chatSpecials: SpecialNavModel[] = [ + { + id: 'channelBrowser', + component: workbench.component.SpaceBrowser, + icon: chunter.icon.ChannelBrowser, + label: chunter.string.ChannelBrowser, + position: 'top', + componentProps: { + _class: chunter.class.Channel, + label: chunter.string.ChannelBrowser, + createItemDialog: chunter.component.CreateChannel, + createItemLabel: chunter.string.CreateChannel + } + }, + { + id: 'fileBrowser', + label: attachment.string.FileBrowser, + icon: attachment.icon.FileBrowser, + component: attachment.component.FileBrowser, + position: 'top', + componentProps: { + requestedSpaceClasses: [chunter.class.Channel, chunter.class.DirectMessage] + } + }, + { + id: 'threads', + label: chunter.string.Threads, + icon: chunter.icon.Thread, + component: chunter.component.Threads, + position: 'top' + }, + { + id: 'saved', + label: chunter.string.SavedItems, + icon: activity.icon.Bookmark, + position: 'bottom', + component: chunter.component.SavedMessages + }, + { + id: 'archive', + component: workbench.component.Archive, + icon: view.icon.Archive, + label: workbench.string.Archive, + position: 'bottom', + componentProps: { + _class: notification.class.DocNotifyContext, + config: [ + { key: '', label: chunter.string.ChannelName }, + { key: 'attachedToClass', label: view.string.Type }, + 'modifiedOn' + ], + baseMenuClass: notification.class.DocNotifyContext, + query: { + _class: notification.class.DocNotifyContext, + hidden: true + } + }, + visibleIf: notification.function.HasHiddenDocNotifyContext + }, + { + id: 'chunterBrowser', + label: chunter.string.ChunterBrowser, + icon: workbench.icon.Search, + component: chunter.component.ChunterBrowser, + visibleIf: chunter.function.ChunterBrowserVisible + } +] + +export const chatNavGroups: ChatNavGroupModel[] = [ + { + id: 'pinned', + label: notification.string.Pinned, + hideEmpty: true, + query: { + isPinned: true + }, + actions: [ + { + icon: view.icon.Delete, + label: view.string.Delete, + action: chunter.actionImpl.UnpinAllChannels + } + ] + }, + { + id: 'documents', + label: chunter.string.Docs, + query: { + attachedToClass: { + $nin: [ + chunter.class.DirectMessage, + chunter.class.Channel, + activity.class.DocUpdateMessage, + chunter.class.ChatMessage, + chunter.class.ThreadMessage, + chunter.class.Backlink + ] + }, + isPinned: { $ne: true } + } + }, + { + id: 'channels', + label: chunter.string.Channels, + query: { + attachedToClass: { $in: [chunter.class.Channel] }, + isPinned: { $ne: true } + }, + addLabel: chunter.string.CreateChannel, + addComponent: chunter.component.CreateChannel + }, + { + id: 'direct', + label: chunter.string.DirectMessages, + query: { + attachedToClass: { $in: [chunter.class.DirectMessage] }, + isPinned: { $ne: true } + }, + addLabel: chunter.string.NewDirectMessage, + addComponent: chunter.component.CreateDirectMessage + } +] + +export async function getDocByNotifyContext ( + docNotifyContexts: DocNotifyContext[] +): Promise, Doc>> { + const client = getClient() + + const docs = await Promise.all( + docNotifyContexts.map( + async ({ attachedTo, attachedToClass }) => await client.findOne(attachedToClass, { _id: attachedTo }) + ) + ) + + const result: Map, Doc> = new Map, Doc>() + + for (const doc of docs) { + if (doc === undefined) { + continue + } + + const context = docNotifyContexts.find(({ attachedTo }) => attachedTo === doc._id) + + if (context === undefined) { + continue + } + + result.set(context._id, doc) + } + + return result +} diff --git a/plugins/chunter-resources/src/components/icons/Bookmark.svelte b/plugins/chunter-resources/src/components/icons/Bookmark.svelte deleted file mode 100644 index bac0fd3ae4..0000000000 --- a/plugins/chunter-resources/src/components/icons/Bookmark.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/plugins/chunter-resources/src/components/threads/ThreadMessagePresenter.svelte b/plugins/chunter-resources/src/components/threads/ThreadMessagePresenter.svelte new file mode 100644 index 0000000000..b0df37548a --- /dev/null +++ b/plugins/chunter-resources/src/components/threads/ThreadMessagePresenter.svelte @@ -0,0 +1,42 @@ + + + + diff --git a/plugins/chunter-resources/src/components/ThreadParentPresenter.svelte b/plugins/chunter-resources/src/components/threads/ThreadParentPresenter.svelte similarity index 73% rename from plugins/chunter-resources/src/components/ThreadParentPresenter.svelte rename to plugins/chunter-resources/src/components/threads/ThreadParentPresenter.svelte index aa65d4f049..ec865b9456 100644 --- a/plugins/chunter-resources/src/components/ThreadParentPresenter.svelte +++ b/plugins/chunter-resources/src/components/threads/ThreadParentPresenter.svelte @@ -13,8 +13,10 @@ // limitations under the License. --> -