diff --git a/models/activity/src/index.ts b/models/activity/src/index.ts index a9db7be15a..8b7c096295 100644 --- a/models/activity/src/index.ts +++ b/models/activity/src/index.ts @@ -34,7 +34,8 @@ import { type IgnoreActivity, type Reaction, type SavedMessage, - type TxViewlet + type TxViewlet, + type ReplyProvider } from '@hcengineering/activity' import contact, { type Person } from '@hcengineering/contact' import core, { @@ -264,6 +265,11 @@ export class TActivityMessagePreview extends TClass implements ActivityMessagePr presenter!: AnyComponent } +@Model(activity.class.ReplyProvider, core.class.Doc, DOMAIN_MODEL) +export class TReplyProvider extends TDoc implements ReplyProvider { + function!: Resource<(message: ActivityMessage) => Promise> +} + export function createModel (builder: Builder): void { builder.createModel( TTxViewlet, @@ -281,7 +287,8 @@ export function createModel (builder: Builder): void { TSavedMessage, TIgnoreActivity, TActivityReference, - TActivityMessagePreview + TActivityMessagePreview, + TReplyProvider ) builder.mixin(activity.class.DocUpdateMessage, core.class.Class, view.mixin.ObjectPresenter, { diff --git a/models/chunter/src/index.ts b/models/chunter/src/index.ts index 1e03d46582..d61aaf1032 100644 --- a/models/chunter/src/index.ts +++ b/models/chunter/src/index.ts @@ -733,6 +733,10 @@ export function createModel (builder: Builder, options = { addApplication: true domain: DOMAIN_CHUNTER, disabled: [{ _class: 1 }, { space: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }] }) + + builder.createDoc(activity.class.ReplyProvider, core.space.Model, { + function: chunter.function.ReplyToThread + }) } export default chunter diff --git a/models/chunter/src/plugin.ts b/models/chunter/src/plugin.ts index a3744fd9c5..0b5550b1d2 100644 --- a/models/chunter/src/plugin.ts +++ b/models/chunter/src/plugin.ts @@ -99,7 +99,8 @@ export default mergeIds(chunterId, chunter, { CanDeleteMessage: '' as Resource<(doc?: Doc | Doc[]) => Promise>, CanCopyMessageLink: '' as Resource<(doc?: Doc | Doc[]) => Promise>, GetChunterSpaceLinkFragment: '' as Resource<(doc: Doc, props: Record) => Promise>, - GetThreadLink: '' as Resource<(doc: Doc, props: Record) => Promise> + GetThreadLink: '' as Resource<(doc: Doc, props: Record) => Promise>, + ReplyToThread: '' as Resource<(doc: ActivityMessage) => Promise> }, filter: { ChatMessagesFilter: '' as Resource<(message: ActivityMessage, _class?: Ref) => boolean> diff --git a/plugins/chunter-resources/src/components/Replies.svelte b/plugins/activity-resources/src/components/Replies.svelte similarity index 91% rename from plugins/chunter-resources/src/components/Replies.svelte rename to plugins/activity-resources/src/components/Replies.svelte index 4e8057d280..5598bd6ebb 100644 --- a/plugins/chunter-resources/src/components/Replies.svelte +++ b/plugins/activity-resources/src/components/Replies.svelte @@ -13,10 +13,11 @@ // limitations under the License. --> diff --git a/plugins/activity-resources/src/components/activity-message/ActivityMessageTemplate.svelte b/plugins/activity-resources/src/components/activity-message/ActivityMessageTemplate.svelte index bc8ade35f1..3f46d11ae4 100644 --- a/plugins/activity-resources/src/components/activity-message/ActivityMessageTemplate.svelte +++ b/plugins/activity-resources/src/components/activity-message/ActivityMessageTemplate.svelte @@ -26,13 +26,13 @@ import { getActions, restrictionStore } from '@hcengineering/view-resources' import ReactionsPresenter from '../reactions/ReactionsPresenter.svelte' - import ActivityMessageExtensionComponent from './ActivityMessageExtension.svelte' import ActivityMessagePresenter from './ActivityMessagePresenter.svelte' import ActivityMessageActions from '../ActivityMessageActions.svelte' import { isReactionMessage } from '../../activityMessagesUtils' import Bookmark from '../icons/Bookmark.svelte' import { savedMessagesStore } from '../../activity' import MessageTimestamp from '../MessageTimestamp.svelte' + import Replies from '../Replies.svelte' export let message: DisplayActivityMessage export let parentMessage: DisplayActivityMessage | undefined = undefined @@ -85,11 +85,7 @@ setTimeout(scrollToMessage, 100) } - void client - .findAll(activity.class.ActivityMessageExtension, { ofMessage: message._class }) - .then((res: ActivityMessageExtension[]) => { - extensions = res - }) + $: extensions = client.getModel().findAllSync(activity.class.ActivityMessageExtension, { ofMessage: message._class }) function handleActionsOpened (): void { isActionsOpened = true @@ -175,11 +171,7 @@ {#if !hideFooter} - + {/if} {#if parentMessage && showEmbedded} diff --git a/plugins/activity/src/index.ts b/plugins/activity/src/index.ts index d7b1b6184c..94c3302f7e 100644 --- a/plugins/activity/src/index.ts +++ b/plugins/activity/src/index.ts @@ -308,6 +308,10 @@ export interface SavedMessage extends Preference { attachedTo: Ref } +export interface ReplyProvider extends Doc { + function: Resource<(message: ActivityMessage) => Promise> +} + /** * @public */ @@ -334,7 +338,8 @@ export default plugin(activityId, { ActivityExtension: '' as Ref>, Reaction: '' as Ref>, SavedMessage: '' as Ref>, - ActivityReference: '' as Ref> + ActivityReference: '' as Ref>, + ReplyProvider: '' as Ref> }, icon: { Activity: '' as Asset, diff --git a/plugins/chunter-resources/src/components/ChannelScrollView.svelte b/plugins/chunter-resources/src/components/ChannelScrollView.svelte index d02ac3be88..ef7fa0d181 100644 --- a/plugins/chunter-resources/src/components/ChannelScrollView.svelte +++ b/plugins/chunter-resources/src/components/ChannelScrollView.svelte @@ -29,6 +29,7 @@ import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources' import { get } from 'svelte/store' import { tick, beforeUpdate, afterUpdate } from 'svelte' + import { getResource } from '@hcengineering/platform' import ActivityMessagesSeparator from './ChannelMessagesSeparator.svelte' import { filterChatMessages, getClosestDate, readChannelMessages } from '../utils' @@ -58,7 +59,12 @@ const client = getClient() const inboxClient = InboxNotificationsClientImpl.getClient() const contextByDocStore = inboxClient.contextByDoc - const filters = client.getModel().findAllSync(activity.class.ActivityMessagesFilter, {}) + + let filters: ActivityMessagesFilter[] = [] + const filterResources = new Map< + Ref, + (message: ActivityMessage, _class?: Ref) => boolean + >() const messagesStore = provider.messagesStore const isLoadingStore = provider.isLoadingStore @@ -93,15 +99,23 @@ $: notifyContext = $contextByDocStore.get(objectId) - $: void filterChatMessages(messages, filters, objectClass, selectedFilters).then((filteredMessages) => { - displayMessages = filteredMessages - }) + void client + .getModel() + .findAll(activity.class.ActivityMessagesFilter, {}) + .then(async (res) => { + filters = res + for (const filter of filters) { + filterResources.set(filter._id, await getResource(filter.filter)) + } + }) + + $: displayMessages = filterChatMessages(messages, filters, filterResources, objectClass, selectedFilters) inboxClient.inboxNotificationsByContext.subscribe(() => { readViewportMessages() }) - function scrollToBottom (afterScrollFn?: () => void) { + function scrollToBottom (afterScrollFn?: () => void): void { if (scroller !== undefined && scrollElement !== undefined) { scroller.scrollBy(scrollElement.scrollHeight) updateSelectedDate() @@ -109,9 +123,19 @@ } } - function scrollToSeparator () { - if (separatorElement) { + function scrollToSeparator (): void { + if (separatorElement && scrollElement) { + const messagesElements = scrollContentBox?.getElementsByClassName('activityMessage') + const messagesHeight = displayMessages + .slice(separatorIndex) + .reduce((res, msg) => res + (messagesElements?.[msg._id as any]?.clientHeight ?? 0), 0) + separatorElement.scrollIntoView() + + if (messagesHeight >= scrollElement.clientHeight) { + scroller?.scrollBy(-50) + } + updateShouldScrollToNew() readViewportMessages() } @@ -373,7 +397,7 @@ newTimestamp !== undefined ? displayMessages.findIndex((message) => (message.createdOn ?? 0) >= (newTimestamp ?? 0)) : -1 - $: void initializeScroll($isLoadingStore, separatorElement, separatorIndex) + $: void initializeScroll(isLoading, separatorElement, separatorIndex) let isInitialScrolling = true async function initializeScroll (isLoading: boolean, separatorElement?: HTMLDivElement, separatorIndex?: number) { @@ -389,6 +413,7 @@ scrollToMessage() isInitialScrolling = false } else if (separatorIndex === -1) { + await wait() isScrollInitialized = true shouldWaitAndRead = true autoscroll = true diff --git a/plugins/chunter-resources/src/components/chat/specials/ChannelBrowser.svelte b/plugins/chunter-resources/src/components/chat/specials/ChannelBrowser.svelte index 543d337bbb..e64a082cc9 100644 --- a/plugins/chunter-resources/src/components/chat/specials/ChannelBrowser.svelte +++ b/plugins/chunter-resources/src/components/chat/specials/ChannelBrowser.svelte @@ -24,13 +24,14 @@ Space } from '@hcengineering/core' import { IntlString } from '@hcengineering/platform' - import presentation, { createQuery } from '@hcengineering/presentation' + import presentation, { createQuery, getClient } from '@hcengineering/presentation' import { AnyComponent, Button, Icon, Label, Scroller, SearchEdit, showPopup } from '@hcengineering/ui' import { FilterBar, FilterButton, SpacePresenter } from '@hcengineering/view-resources' import workbench from '@hcengineering/workbench' import { Channel } from '@hcengineering/chunter' - import { openChannel } from '../../../navigation' + import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources' + import { openChannel } from '../../../navigation' import { getObjectIcon, joinChannel, leaveChannel } from '../../../utils' import chunter from './../../../plugin' @@ -45,6 +46,9 @@ const me = getCurrentAccount()._id const channelsQuery = createQuery() + const client = getClient() + const notificationsClient = InboxNotificationsClientImpl.getClient() + const sort: SortingQuery = { name: SortingOrder.Ascending } diff --git a/plugins/chunter-resources/src/index.ts b/plugins/chunter-resources/src/index.ts index e57553dfb3..4d8236ddf2 100644 --- a/plugins/chunter-resources/src/index.ts +++ b/plugins/chunter-resources/src/index.ts @@ -48,7 +48,6 @@ import ChannelIcon from './components/ChannelIcon.svelte' import ThreadNotificationPresenter from './components/notification/ThreadNotificationPresenter.svelte' import ChatMessageNotificationLabel from './components/notification/ChatMessageNotificationLabel.svelte' import ChatAside from './components/chat/ChatAside.svelte' -import Replies from './components/Replies.svelte' import ReplyToThreadAction from './components/ReplyToThreadAction.svelte' import ThreadMessagePreview from './components/threads/ThreadMessagePreview.svelte' import ChatMessagePreview from './components/chat-message/ChatMessagePreview.svelte' @@ -65,7 +64,7 @@ import { leaveChannelAction, removeChannelAction } from './utils' -import { chunterSpaceLinkFragmentProvider, getThreadLink, getMessageLink } from './navigation' +import { chunterSpaceLinkFragmentProvider, getThreadLink, getMessageLink, replyToThread } from './navigation' export { default as ChatMessagesPresenter } from './components/chat-message/ChatMessagesPresenter.svelte' export { default as ChatMessagePopup } from './components/chat-message/ChatMessagePopup.svelte' @@ -175,7 +174,6 @@ export default async (): Promise => ({ ChatMessageNotificationLabel, ThreadNotificationPresenter, ChatAside, - Replies, ReplyToThreadAction, ThreadMessagePreview, ChatMessagePreview @@ -192,7 +190,8 @@ export default async (): Promise => ({ CanCopyMessageLink: canCopyMessageLink, GetChunterSpaceLinkFragment: chunterSpaceLinkFragmentProvider, GetUnreadThreadsCount: getUnreadThreadsCount, - GetThreadLink: getThreadLink + GetThreadLink: getThreadLink, + ReplyToThread: replyToThread }, actionImpl: { ArchiveChannel, diff --git a/plugins/chunter-resources/src/utils.ts b/plugins/chunter-resources/src/utils.ts index 9e0b5cd4ed..0af57f74cc 100644 --- a/plugins/chunter-resources/src/utils.ts +++ b/plugins/chunter-resources/src/utils.ts @@ -29,7 +29,7 @@ import { } from '@hcengineering/core' import { getClient } from '@hcengineering/presentation' import { type AnySvelteComponent } from '@hcengineering/ui' -import { type Asset, getResource, translate } from '@hcengineering/platform' +import { type Asset, translate } from '@hcengineering/platform' import { classIcon, getDocLinkTitle, getDocTitle } from '@hcengineering/view-resources' import activity, { type ActivityMessage, @@ -273,12 +273,13 @@ export function getClosestDate (selectedDate: Timestamp, dates: Timestamp[]): Ti return closestDate } -export async function filterChatMessages ( +export function filterChatMessages ( messages: DisplayActivityMessage[], filters: ActivityMessagesFilter[], + filterResources: Map, (message: ActivityMessage, _class?: Ref) => boolean>, objectClass: Ref>, selectedIds: Array> -): Promise { +): DisplayActivityMessage[] { if (selectedIds.length === 0 || selectedIds.includes(activity.ids.AllFilter)) { return messages } @@ -291,8 +292,10 @@ export async function filterChatMessages ( const filtersFns: Array<(message: ActivityMessage, _class?: Ref) => boolean> = [] for (const filter of selectedFilters) { - const filterFn = await getResource(filter.filter) - filtersFns.push(filterFn) + const filterFn = filterResources.get(filter._id) + if (filterFn !== undefined) { + filtersFns.push(filterFn) + } } return messages.filter((message) => filtersFns.some((filterFn) => filterFn(message, objectClass))) diff --git a/server-plugins/chunter-resources/src/index.ts b/server-plugins/chunter-resources/src/index.ts index 3f9eed5674..5a99058d97 100644 --- a/server-plugins/chunter-resources/src/index.ts +++ b/server-plugins/chunter-resources/src/index.ts @@ -432,6 +432,16 @@ async function OnChannelMembersChanged (tx: TxUpdateDoc, control: Trigg ) } + if (added.length > 0) { + res.push( + control.txFactory.createTxMixin(tx.objectId, tx.objectClass, tx.objectSpace, notification.mixin.Collaborators, { + $push: { + collaborators: { $each: added, $position: 0 } + } + }) + ) + } + for (const addedMember of added) { const context = allContexts.find(({ user }) => user === addedMember) @@ -457,16 +467,8 @@ async function OnChannelMembersChanged (tx: TxUpdateDoc, control: Trigg const contextsToRemove = allContexts.filter(({ user }) => removed.includes(user)) - if (contextsToRemove.length === 0) { - return res - } - - const channel = (await control.findAll(chunter.class.Channel, { _id: tx.objectId }))[0] - - if (channel !== undefined) { - for (const context of contextsToRemove) { - res.push(control.txFactory.createTxRemoveDoc(context._class, context.space, context._id)) - } + for (const context of contextsToRemove) { + res.push(control.txFactory.createTxRemoveDoc(context._class, context.space, context._id)) } return res