mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-21 11:52:23 +02:00
Fixed scroll to new messages on chat open (#5369)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
@@ -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<void>>
|
||||
}
|
||||
|
||||
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, {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -99,7 +99,8 @@ export default mergeIds(chunterId, chunter, {
|
||||
CanDeleteMessage: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||
CanCopyMessageLink: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||
GetChunterSpaceLinkFragment: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
|
||||
GetThreadLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>
|
||||
GetThreadLink: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
|
||||
ReplyToThread: '' as Resource<(doc: ActivityMessage) => Promise<void>>
|
||||
},
|
||||
filter: {
|
||||
ChatMessagesFilter: '' as Resource<(message: ActivityMessage, _class?: Ref<Doc>) => boolean>
|
||||
|
||||
+12
-7
@@ -13,10 +13,11 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { personByIdStore, Avatar } from '@hcengineering/contact-resources'
|
||||
import { Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { Label, TimeSince } from '@hcengineering/ui'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import notification, {
|
||||
ActivityInboxNotification,
|
||||
DocNotifyContext,
|
||||
@@ -24,14 +25,13 @@
|
||||
InboxNotificationsClient
|
||||
} from '@hcengineering/notification'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { Label, TimeSince, getLocation, navigate } from '@hcengineering/ui'
|
||||
|
||||
import { buildThreadLink } from '../navigation'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
|
||||
export let object: ActivityMessage
|
||||
export let embedded = false
|
||||
export let onReply: (() => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const maxDisplayPersons = 5
|
||||
|
||||
$: lastReply = object.lastReply ?? new Date().getTime()
|
||||
@@ -77,7 +77,9 @@
|
||||
.slice(0, maxDisplayPersons - 1)
|
||||
}
|
||||
|
||||
function handleReply (e: MouseEvent): void {
|
||||
const replyProvider = client.getModel().findAllSync(activity.class.ReplyProvider, {})[0]
|
||||
|
||||
async function handleReply (e: MouseEvent) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
@@ -86,7 +88,10 @@
|
||||
return
|
||||
}
|
||||
|
||||
navigate(buildThreadLink(getLocation(), object.attachedTo, object.attachedToClass, object._id))
|
||||
if (replyProvider) {
|
||||
const fn = await getResource(replyProvider.function)
|
||||
fn(object)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
+3
-11
@@ -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 @@
|
||||
<slot name="content" />
|
||||
|
||||
{#if !hideFooter}
|
||||
<ActivityMessageExtensionComponent
|
||||
kind="footer"
|
||||
{extensions}
|
||||
props={{ object: message, embedded, onReply }}
|
||||
/>
|
||||
<Replies {embedded} object={message} {onReply} />
|
||||
{/if}
|
||||
<ReactionsPresenter object={message} {readonly} />
|
||||
{#if parentMessage && showEmbedded}
|
||||
|
||||
@@ -308,6 +308,10 @@ export interface SavedMessage extends Preference {
|
||||
attachedTo: Ref<ActivityMessage>
|
||||
}
|
||||
|
||||
export interface ReplyProvider extends Doc {
|
||||
function: Resource<(message: ActivityMessage) => Promise<void>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -334,7 +338,8 @@ export default plugin(activityId, {
|
||||
ActivityExtension: '' as Ref<Class<ActivityExtension>>,
|
||||
Reaction: '' as Ref<Class<Reaction>>,
|
||||
SavedMessage: '' as Ref<Class<SavedMessage>>,
|
||||
ActivityReference: '' as Ref<Class<ActivityReference>>
|
||||
ActivityReference: '' as Ref<Class<ActivityReference>>,
|
||||
ReplyProvider: '' as Ref<Class<ReplyProvider>>
|
||||
},
|
||||
icon: {
|
||||
Activity: '' as Asset,
|
||||
|
||||
@@ -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<ActivityMessagesFilter>,
|
||||
(message: ActivityMessage, _class?: Ref<Doc>) => 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
|
||||
|
||||
@@ -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<Space> = {
|
||||
name: SortingOrder.Ascending
|
||||
}
|
||||
|
||||
@@ -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<Resources> => ({
|
||||
ChatMessageNotificationLabel,
|
||||
ThreadNotificationPresenter,
|
||||
ChatAside,
|
||||
Replies,
|
||||
ReplyToThreadAction,
|
||||
ThreadMessagePreview,
|
||||
ChatMessagePreview
|
||||
@@ -192,7 +190,8 @@ export default async (): Promise<Resources> => ({
|
||||
CanCopyMessageLink: canCopyMessageLink,
|
||||
GetChunterSpaceLinkFragment: chunterSpaceLinkFragmentProvider,
|
||||
GetUnreadThreadsCount: getUnreadThreadsCount,
|
||||
GetThreadLink: getThreadLink
|
||||
GetThreadLink: getThreadLink,
|
||||
ReplyToThread: replyToThread
|
||||
},
|
||||
actionImpl: {
|
||||
ArchiveChannel,
|
||||
|
||||
@@ -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<Ref<ActivityMessagesFilter>, (message: ActivityMessage, _class?: Ref<Doc>) => boolean>,
|
||||
objectClass: Ref<Class<Doc>>,
|
||||
selectedIds: Array<Ref<ActivityMessagesFilter>>
|
||||
): Promise<DisplayActivityMessage[]> {
|
||||
): 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<Doc>) => 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)))
|
||||
|
||||
@@ -432,6 +432,16 @@ async function OnChannelMembersChanged (tx: TxUpdateDoc<Channel>, 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<Channel>, 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
|
||||
|
||||
Reference in New Issue
Block a user