diff --git a/models/contact/src/index.ts b/models/contact/src/index.ts index 37c7446e54..177465955e 100644 --- a/models/contact/src/index.ts +++ b/models/contact/src/index.ts @@ -292,6 +292,18 @@ export function createModel (builder: Builder): void { provider: contact.function.PersonTooltipProvider }) + builder.mixin(contact.class.Channel, core.class.Class, view.mixin.ObjectIdentifier, { + provider: contact.function.ChannelIdentifierProvider + }) + + builder.mixin(contact.class.Channel, core.class.Class, view.mixin.ObjectTitle, { + titleProvider: contact.function.ChannelTitleProvider + }) + + builder.mixin(contact.class.Channel, core.class.Class, view.mixin.ObjectIcon, { + component: contact.component.ChannelIcon + }) + builder.createDoc( workbench.class.Application, core.space.Model, diff --git a/models/contact/src/plugin.ts b/models/contact/src/plugin.ts index b04a4195ea..3b2f2ec9e8 100644 --- a/models/contact/src/plugin.ts +++ b/models/contact/src/plugin.ts @@ -60,7 +60,8 @@ export default mergeIds(contactId, contact, { ActivityChannelPresenter: '' as AnyComponent, EmployeeFilter: '' as AnyComponent, EmployeeFilterValuePresenter: '' as AnyComponent, - PersonAccountFilterValuePresenter: '' as AnyComponent + PersonAccountFilterValuePresenter: '' as AnyComponent, + ChannelIcon: '' as AnyComponent }, string: { Persons: '' as IntlString, @@ -137,6 +138,8 @@ export default mergeIds(contactId, contact, { GetContactName: '' as Resource, GetContactFirstName: '' as Resource, GetContactLastName: '' as Resource, - ContactTitleProvider: '' as Resource<(client: Client, ref: Ref, doc?: Doc) => Promise> + ContactTitleProvider: '' as Resource<(client: Client, ref: Ref, doc?: Doc) => Promise>, + ChannelTitleProvider: '' as Resource<(client: Client, ref: Ref, doc?: Doc) => Promise>, + ChannelIdentifierProvider: '' as Resource<(client: Client, ref: Ref, doc?: Doc) => Promise> } }) diff --git a/models/gmail/src/index.ts b/models/gmail/src/index.ts index 0fe1358ecb..b802f0a44f 100644 --- a/models/gmail/src/index.ts +++ b/models/gmail/src/index.ts @@ -176,6 +176,19 @@ export function createModel (builder: Builder): void { gmail.ids.TxSharedCreate ) + builder.createDoc( + activity.class.DocUpdateMessageViewlet, + core.space.Model, + { + objectClass: gmail.class.Message, + icon: contact.icon.Email, + action: 'create', + component: gmail.activity.GmailWriteMessage, + label: gmail.string.HaveWrittenEmail + }, + gmail.ids.GmailWriteMessageActivityViewlet + ) + builder.createDoc( activity.class.TxViewlet, core.space.Model, @@ -192,6 +205,20 @@ export function createModel (builder: Builder): void { gmail.ids.TxSharedCreate ) + builder.createDoc( + activity.class.DocUpdateMessageViewlet, + core.space.Model, + { + objectClass: gmail.class.SharedMessages, + icon: contact.icon.Email, + action: 'create', + component: gmail.activity.GmailSharedMessage, + label: gmail.string.SharedMessages, + hideIfRemoved: true + }, + gmail.ids.GmailSharedMessageActivityViewlet + ) + createAction( builder, { diff --git a/models/gmail/src/plugin.ts b/models/gmail/src/plugin.ts index 6add4452c8..f8721c76d8 100644 --- a/models/gmail/src/plugin.ts +++ b/models/gmail/src/plugin.ts @@ -19,7 +19,7 @@ import { type IntlString, mergeIds, type Resource } from '@hcengineering/platfor import { gmailId } from '@hcengineering/gmail' import gmail from '@hcengineering/gmail-resources/src/plugin' import type { AnyComponent } from '@hcengineering/ui/src/types' -import type { TxViewlet } from '@hcengineering/activity' +import type { DocUpdateMessageViewlet, TxViewlet } from '@hcengineering/activity' import { type Action } from '@hcengineering/view' import { type NotificationGroup } from '@hcengineering/notification' @@ -45,11 +45,15 @@ export default mergeIds(gmailId, gmail, { ids: { TxSharedCreate: '' as Ref, NewMessageNotification: '' as Ref, - EmailNotificationGroup: '' as Ref + EmailNotificationGroup: '' as Ref, + GmailSharedMessageActivityViewlet: '' as Ref, + GmailWriteMessageActivityViewlet: '' as Ref }, activity: { TxSharedCreate: '' as AnyComponent, - TxWriteMessage: '' as AnyComponent + TxWriteMessage: '' as AnyComponent, + GmailSharedMessage: '' as AnyComponent, + GmailWriteMessage: '' as AnyComponent }, function: { HasEmail: '' as Resource<(doc?: Doc | Doc[] | undefined) => Promise> diff --git a/models/telegram/src/index.ts b/models/telegram/src/index.ts index 291c0613ff..90c1237e3d 100644 --- a/models/telegram/src/index.ts +++ b/models/telegram/src/index.ts @@ -144,7 +144,7 @@ export function createModel (builder: Builder): void { action: 'create', icon: contact.icon.Telegram, component: telegram.activity.TelegramMessageCreated, - label: telegram.string.SharedMessages + label: telegram.string.SharedMessage }, telegram.ids.TelegramMessageCreatedActivityViewlet ) diff --git a/plugins/activity-resources/src/activityMessagesUtils.ts b/plugins/activity-resources/src/activityMessagesUtils.ts index 7f2a96d580..f5e4d9fe3f 100644 --- a/plugins/activity-resources/src/activityMessagesUtils.ts +++ b/plugins/activity-resources/src/activityMessagesUtils.ts @@ -34,7 +34,7 @@ import { getDocLinkTitle, hasAttributePresenter } from '@hcengineering/view-resources' -import { type Person } from '@hcengineering/contact' +import contact, { type Person } from '@hcengineering/contact' import { type IntlString } from '@hcengineering/platform' import { type AnyComponent } from '@hcengineering/ui' import { get } from 'svelte/store' @@ -220,20 +220,42 @@ function combineByCreateThreshold (docUpdateMessages: DocUpdateMessage[]): DocUp }) } +function wrapMessages ( + hierarchy: Hierarchy, + messages: ActivityMessage[] +): { toCombine: DocUpdateMessage[], uncombined: ActivityMessage[] } { + const toCombine: DocUpdateMessage[] = [] + const uncombined: ActivityMessage[] = [] + + for (const message of messages) { + if (isDocUpdateMessage(message)) { + if (hierarchy.isDerived(message.attachedToClass, contact.class.Channel)) { + uncombined.push(message) + } else { + toCombine.push(message) + } + } else { + uncombined.push(message) + } + } + + return { toCombine, uncombined } +} + export async function combineActivityMessages ( messages: ActivityMessage[], sortingOrder: SortingOrder = SortingOrder.Ascending ): Promise { const client = getClient() - const uncombinedMessages = messages.filter((message) => message._class !== activity.class.DocUpdateMessage) - const docUpdateMessages = combineByCreateThreshold(messages.filter(isDocUpdateMessage)) + const { uncombined, toCombine } = wrapMessages(client.getHierarchy(), messages) + const docUpdateMessages = combineByCreateThreshold(toCombine) if (docUpdateMessages.length === 0) { - return sortActivityMessages(uncombinedMessages, sortingOrder) + return sortActivityMessages(uncombined, sortingOrder) } - const result: Array = [...uncombinedMessages] + const result: Array = [...uncombined] const groupedByType: Map = groupByArray(docUpdateMessages, getDocUpdateMessageKey) diff --git a/plugins/activity-resources/src/components/doc-update-message/DocUpdateMessagePresenter.svelte b/plugins/activity-resources/src/components/doc-update-message/DocUpdateMessagePresenter.svelte index cb50393a97..3855116ad4 100644 --- a/plugins/activity-resources/src/components/doc-update-message/DocUpdateMessagePresenter.svelte +++ b/plugins/activity-resources/src/components/doc-update-message/DocUpdateMessagePresenter.svelte @@ -185,11 +185,14 @@
{#each value?.previousMessages ?? [] as msg} - + {/each}
diff --git a/plugins/chunter-resources/src/components/chat/utils.ts b/plugins/chunter-resources/src/components/chat/utils.ts index f09d4f32ea..d61bcfdfab 100644 --- a/plugins/chunter-resources/src/components/chat/utils.ts +++ b/plugins/chunter-resources/src/components/chat/utils.ts @@ -22,6 +22,7 @@ import attachment, { type SavedAttachments } from '@hcengineering/attachment' import activity from '@hcengineering/activity' import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources' import { type Action, showPopup } from '@hcengineering/ui' +import contact from '@hcengineering/contact' import { type ChatNavGroupModel, type ChatNavItemModel } from './types' import chunter from '../../plugin' @@ -115,7 +116,8 @@ export const chatNavGroupModels: ChatNavGroupModel[] = [ query: { isPinned: { $ne: true }, attachedToClass: { - $nin: [chunter.class.DirectMessage, chunter.class.Channel] + // Ignore external channels until support is provided for them + $nin: [chunter.class.DirectMessage, chunter.class.Channel, contact.class.Channel] } } } diff --git a/plugins/contact-resources/src/components/ChannelIcon.svelte b/plugins/contact-resources/src/components/ChannelIcon.svelte new file mode 100644 index 0000000000..1387ac0e4e --- /dev/null +++ b/plugins/contact-resources/src/components/ChannelIcon.svelte @@ -0,0 +1,40 @@ + + + +{#if icon} + +{/if} diff --git a/plugins/contact-resources/src/components/ChannelPanel.svelte b/plugins/contact-resources/src/components/ChannelPanel.svelte index 06a1b4c275..708283bf24 100644 --- a/plugins/contact-resources/src/components/ChannelPanel.svelte +++ b/plugins/contact-resources/src/components/ChannelPanel.svelte @@ -18,10 +18,12 @@ import { createQuery, getClient } from '@hcengineering/presentation' import { AnyComponent, Component } from '@hcengineering/ui' import { channelProviders } from '../utils' + import { DocUpdateMessage } from '@hcengineering/activity' export let _id: Ref export let _class: Ref> export let embedded: boolean = false + export let activityMessage: DocUpdateMessage | undefined = undefined const client = getClient() @@ -45,7 +47,14 @@ {#if presenter} {/if} {/await} diff --git a/plugins/contact-resources/src/index.ts b/plugins/contact-resources/src/index.ts index 6ea8b8682e..39ed17cd5b 100644 --- a/plugins/contact-resources/src/index.ts +++ b/plugins/contact-resources/src/index.ts @@ -104,9 +104,12 @@ import SelectUsersPopup from './components/SelectUsersPopup.svelte' import IconAddMember from './components/icons/AddMember.svelte' import UserDetails from './components/UserDetails.svelte' import EditOrganizationPanel from './components/EditOrganizationPanel.svelte' +import ChannelIcon from './components/ChannelIcon.svelte' import contact from './plugin' import { + channelIdentifierProvider, + channelTitleProvider, contactTitleProvider, employeeSort, filterChannelHasMessagesResult, @@ -334,7 +337,8 @@ export default async (): Promise => ({ DeleteConfirmationPopup, PersonAccountRefPresenter, PersonIcon, - EditOrganizationPanel + EditOrganizationPanel, + ChannelIcon }, completion: { EmployeeQuery: async ( @@ -377,7 +381,9 @@ export default async (): Promise => ({ GetContactLastName: getContactLastName, GetContactLink: getContactLink, ContactTitleProvider: contactTitleProvider, - PersonTooltipProvider: getPersonTooltip + PersonTooltipProvider: getPersonTooltip, + ChannelTitleProvider: channelTitleProvider, + ChannelIdentifierProvider: channelIdentifierProvider }, resolver: { Location: resolveLocation diff --git a/plugins/contact-resources/src/utils.ts b/plugins/contact-resources/src/utils.ts index ccdcbc931f..0f3f39f815 100644 --- a/plugins/contact-resources/src/utils.ts +++ b/plugins/contact-resources/src/utils.ts @@ -26,7 +26,8 @@ import { formatName, getFirstName, getLastName, - getName + getName, + type Channel } from '@hcengineering/contact' import { type Client, @@ -41,7 +42,7 @@ import { type Class } from '@hcengineering/core' import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification' -import { getEmbeddedLabel, getResource } from '@hcengineering/platform' +import { getEmbeddedLabel, getResource, translate } from '@hcengineering/platform' import { createQuery, getClient } from '@hcengineering/presentation' import { type TemplateDataProvider } from '@hcengineering/templates' import { @@ -402,3 +403,22 @@ export function getPersonTooltip (client: Client, value: Person | null | undefin label: getEmbeddedLabel(getName(hierarchy, value)) } } + +export async function channelIdentifierProvider (client: Client, ref: Ref, doc?: Channel): Promise { + const channel = doc ?? (await client.findOne(contact.class.Channel, { _id: ref })) + if (channel === undefined) return '' + + const provider = await client.findOne(contact.class.ChannelProvider, { _id: channel.provider }) + + if (provider === undefined) return channel.value + + return await translate(provider.label, {}) +} + +export async function channelTitleProvider (client: Client, ref: Ref, doc?: Channel): Promise { + const channel = doc ?? (await client.findOne(contact.class.Channel, { _id: ref })) + + if (channel === undefined) return '' + + return channel.value +} diff --git a/plugins/gmail-resources/src/components/Main.svelte b/plugins/gmail-resources/src/components/Main.svelte index 734c0aa408..77e120c576 100644 --- a/plugins/gmail-resources/src/components/Main.svelte +++ b/plugins/gmail-resources/src/components/Main.svelte @@ -16,7 +16,7 @@ {#if channel && object} diff --git a/plugins/gmail-resources/src/components/activity/GmailSharedMessage.svelte b/plugins/gmail-resources/src/components/activity/GmailSharedMessage.svelte new file mode 100644 index 0000000000..9558b5b39d --- /dev/null +++ b/plugins/gmail-resources/src/components/activity/GmailSharedMessage.svelte @@ -0,0 +1,46 @@ + + + +{#if doc} + +{/if} diff --git a/plugins/gmail-resources/src/components/activity/GmailWriteMessage.svelte b/plugins/gmail-resources/src/components/activity/GmailWriteMessage.svelte new file mode 100644 index 0000000000..c930546a06 --- /dev/null +++ b/plugins/gmail-resources/src/components/activity/GmailWriteMessage.svelte @@ -0,0 +1,69 @@ + + + +{#if doc} + + + {doc.subject} +{/if} diff --git a/plugins/gmail-resources/src/index.ts b/plugins/gmail-resources/src/index.ts index 0f553d0e24..d3f5397443 100644 --- a/plugins/gmail-resources/src/index.ts +++ b/plugins/gmail-resources/src/index.ts @@ -19,6 +19,8 @@ import { getMetadata, type Resources } from '@hcengineering/platform' import presentation from '@hcengineering/presentation' import TxSharedCreate from './components/activity/TxSharedCreate.svelte' import TxWriteMessage from './components/activity/TxWriteMessage.svelte' +import GmailWriteMessage from './components/activity/GmailWriteMessage.svelte' +import GmailSharedMessage from './components/activity/GmailSharedMessage.svelte' import Configure from './components/Configure.svelte' import Connect from './components/Connect.svelte' import IconGmail from './components/icons/GmailColor.svelte' @@ -37,7 +39,9 @@ export default async (): Promise => ({ }, activity: { TxSharedCreate, - TxWriteMessage + TxWriteMessage, + GmailWriteMessage, + GmailSharedMessage }, function: { HasEmail: checkHasEmail diff --git a/plugins/notification-resources/src/components/inbox/Inbox.svelte b/plugins/notification-resources/src/components/inbox/Inbox.svelte index bc15c6b2f2..b0ac0d3485 100644 --- a/plugins/notification-resources/src/components/inbox/Inbox.svelte +++ b/plugins/notification-resources/src/components/inbox/Inbox.svelte @@ -22,24 +22,25 @@ import view, { Viewlet } from '@hcengineering/view' import { AnyComponent, + ButtonWithDropdown, Component, defineSeparators, + IconDropdown, Label, Loading, location as locationStore, + Location, Scroller, Separator, TabItem, - TabList, - Location, - IconDropdown, - ButtonWithDropdown + TabList } from '@hcengineering/ui' import chunter, { ThreadMessage } from '@hcengineering/chunter' import { Ref, WithLookup } from '@hcengineering/core' import { ViewletSelector } from '@hcengineering/view-resources' import activity, { ActivityMessage } from '@hcengineering/activity' import { isReactionMessage } from '@hcengineering/activity-resources' + import { get } from 'svelte/store' import { inboxMessagesStore, InboxNotificationsClientImpl } from '../../inboxNotificationsClient' import Filter from '../Filter.svelte' @@ -99,6 +100,8 @@ let viewlet: WithLookup | undefined let loading = true + let selectedMessage: ActivityMessage | undefined = undefined + void client.findAll(notification.class.ActivityNotificationViewlet, {}).then((res) => { viewlets = res }) @@ -136,6 +139,17 @@ if (selectedContextId !== selectedContext?._id) { selectedContext = undefined } + + const selectedMessageId = loc?.loc.query?.message as Ref | undefined + + if (selectedMessageId !== undefined) { + selectedMessage = get(inboxClient.activityInboxNotifications).find( + ({ attachedTo }) => attachedTo === selectedMessageId + )?.$lookup?.attachedTo + if (selectedMessage === undefined) { + selectedMessage = await client.findOne(activity.class.ActivityMessage, { _id: selectedMessageId }) + } + } } $: selectedContext = selectedContextId @@ -368,6 +382,7 @@ _class: selectedContext.attachedToClass, embedded: true, context: selectedContext, + activityMessage: selectedMessage, props: { context: selectedContext } }} on:close={() => selectContext(undefined)} diff --git a/plugins/telegram-resources/src/components/MessagePresenter.svelte b/plugins/telegram-resources/src/components/MessagePresenter.svelte new file mode 100644 index 0000000000..1c6882da1b --- /dev/null +++ b/plugins/telegram-resources/src/components/MessagePresenter.svelte @@ -0,0 +1,51 @@ + + + +{#if doc} +
+ +
+{/if} + + diff --git a/plugins/telegram-resources/src/index.ts b/plugins/telegram-resources/src/index.ts index 234b3ca59c..073a43673f 100644 --- a/plugins/telegram-resources/src/index.ts +++ b/plugins/telegram-resources/src/index.ts @@ -25,6 +25,7 @@ import TxMessage from './components/activity/TxMessage.svelte' import IconTelegram from './components/icons/TelegramColor.svelte' import TxSharedCreate from './components/activity/TxSharedCreate.svelte' import TelegramMessageCreated from './components/activity/TelegramMessageCreated.svelte' +import MessagePresenter from './components/MessagePresenter.svelte' import telegram from './plugin' import { getCurrentEmployeeTG, getIntegrationOwnerTG } from './utils' @@ -36,7 +37,8 @@ export default async (): Promise => ({ Connect, Reconnect, IconTelegram, - SharedMessages + SharedMessages, + MessagePresenter }, activity: { TxSharedCreate, diff --git a/plugins/view-resources/src/utils.ts b/plugins/view-resources/src/utils.ts index 81c3da74d3..7620365046 100644 --- a/plugins/view-resources/src/utils.ts +++ b/plugins/view-resources/src/utils.ts @@ -618,7 +618,7 @@ export function makeViewletKey (loc?: Location): string { loc.query = undefined // TODO: make better fix. Just temporary fix for correct inbox viewlets. - if (loc.path[2] === 'inbox') { + if (loc.path[2] === 'notification') { loc.path = loc.path.slice(0, 3) }