From 509952de4e8a4735223f6f054b128eab9849f96d Mon Sep 17 00:00:00 2001 From: Kristina Date: Tue, 8 Oct 2024 19:20:18 +0400 Subject: [PATCH] Use reverse scroll in chat (#6736) Signed-off-by: Kristina Fefelova --- packages/presentation/src/utils.ts | 2 + packages/ui/src/components/Scroller.svelte | 22 +- plugins/activity-resources/src/activity.ts | 14 + .../src/components/Activity.svelte | 2 + .../ActivityMessagePresenter.svelte | 2 +- .../src/components/reactions/Reactions.svelte | 1 - .../reactions/ReactionsPresenter.svelte | 2 +- .../src/channelDataProvider.ts | 5 +- .../src/components/BaseChatScroller.svelte | 56 ++ .../src/components/Channel.svelte | 14 +- .../src/components/ChannelHeader.svelte | 2 +- .../src/components/ChannelInput.svelte | 4 +- .../ChannelMessagesSeparator.svelte | 2 +- .../src/components/ChannelScrollView.svelte | 7 +- .../src/components/ChannelSidebarView.svelte | 6 +- .../src/components/JumpToDateSelector.svelte | 52 +- .../ReverseChannelScrollView.svelte | 669 ++++++++++++++++++ .../components/threads/ThreadContent.svelte | 29 +- plugins/chunter-resources/src/navigation.ts | 2 +- plugins/chunter-resources/src/scroll.ts | 171 +++++ tests/sanity/tests/chat/chat.spec.ts | 2 +- .../tests/model/recruiting/talents-page.ts | 2 +- 22 files changed, 1000 insertions(+), 68 deletions(-) create mode 100644 plugins/chunter-resources/src/components/BaseChatScroller.svelte create mode 100644 plugins/chunter-resources/src/components/ReverseChannelScrollView.svelte create mode 100644 plugins/chunter-resources/src/scroll.ts diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index 39ee4b1eee..2ee4072fd0 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -54,9 +54,11 @@ import view, { type AttributeCategory, type AttributeEditor } from '@hcengineeri import { deepEqual } from 'fast-equals' import { onDestroy } from 'svelte' import { get, writable, type Writable } from 'svelte/store' + import { type KeyedAttribute } from '..' import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline' import plugin from './plugin' + export { reduceCalls } from '@hcengineering/core' let liveQuery: LQ diff --git a/packages/ui/src/components/Scroller.svelte b/packages/ui/src/components/Scroller.svelte index 05372851e9..a19a6cee4a 100644 --- a/packages/ui/src/components/Scroller.svelte +++ b/packages/ui/src/components/Scroller.svelte @@ -31,6 +31,7 @@ export let fade: FadeOptions = defaultSP export let noFade: boolean = true export let invertScroll: boolean = false + export let scrollDirection: 'vertical' | 'vertical-reverse' = 'vertical' export let contentDirection: 'vertical' | 'vertical-reverse' | 'horizontal' = 'vertical' export let horizontal: boolean = contentDirection === 'horizontal' export let align: 'start' | 'center' | 'end' | 'stretch' = 'stretch' @@ -39,7 +40,7 @@ export let buttons: 'normal' | 'union' | false = false export let shrink: boolean = false export let divScroll: HTMLElement | undefined | null = undefined - export let divBox: HTMLElement | undefined = undefined + export let divBox: HTMLElement | undefined | null = undefined export let checkForHeaders: boolean = false export let stickedScrollBars: boolean = false export let thinScrollBars: boolean = false @@ -50,7 +51,7 @@ export function scroll (top: number, left?: number, behavior: 'auto' | 'smooth' = 'auto') { if (divScroll) { if (top !== 0) divScroll.scroll({ top, left: 0, behavior }) - if (left !== 0 || left !== undefined) divScroll.scroll({ top: 0, left, behavior }) + if (left !== 0 && left !== undefined) divScroll.scroll({ top: 0, left, behavior }) } } export function scrollBy (top: number, left?: number, behavior: 'auto' | 'smooth' = 'auto') { @@ -102,11 +103,19 @@ const scrollH = divScroll.scrollHeight const proc = scrollH / trackH - const newHeight = (divScroll.clientHeight - 4) / proc + 'px' + const newHeight = (divScroll.clientHeight - 4) / proc + const newHeightPx = newHeight + 'px' if (divBar.style.height !== 'newHeight') { - divBar.style.height = newHeight + divBar.style.height = newHeightPx + } + + let newTop = '0px' + + if (scrollDirection === 'vertical-reverse') { + newTop = divScroll.clientHeight + divScroll.scrollTop / proc - newHeight - shiftTop - 2 + 'px' + } else { + newTop = divScroll.scrollTop / proc + shiftTop + 2 + 'px' } - const newTop = divScroll.scrollTop / proc + shiftTop + 2 + 'px' if (divBar.style.top !== newTop) { divBar.style.top = newTop } @@ -542,7 +551,8 @@ divHeight = element.clientHeight onResize?.() }} - class="scroll relative flex-shrink" + class="scroll relative flex-shrink flex-col" + style:flex-direction={scrollDirection === 'vertical-reverse' ? 'column-reverse' : 'column'} class:disableOverscroll style:overflow-x={horizontal ? 'auto' : 'hidden'} on:scroll={() => { diff --git a/plugins/activity-resources/src/activity.ts b/plugins/activity-resources/src/activity.ts index 0d748dafd0..89125e90b7 100644 --- a/plugins/activity-resources/src/activity.ts +++ b/plugins/activity-resources/src/activity.ts @@ -1,3 +1,17 @@ +// +// Copyright © 2024 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 activity, { type ActivityMessage, type SavedMessage } from '@hcengineering/activity' import core, { type Ref, SortingOrder, type WithLookup } from '@hcengineering/core' import { writable } from 'svelte/store' diff --git a/plugins/activity-resources/src/components/Activity.svelte b/plugins/activity-resources/src/components/Activity.svelte index 5a2cc7db4f..1444af2fa8 100644 --- a/plugins/activity-resources/src/components/Activity.svelte +++ b/plugins/activity-resources/src/components/Activity.svelte @@ -279,6 +279,7 @@ hideLink={true} type={canGroup ? 'short' : 'default'} isHighlighted={selectedMessageId === message._id} + withShowMore /> {:else} @@ -288,6 +289,7 @@ hideLink={true} type={canGroup ? 'short' : 'default'} isHighlighted={selectedMessageId === message._id} + withShowMore /> {/if} diff --git a/plugins/activity-resources/src/components/activity-message/ActivityMessagePresenter.svelte b/plugins/activity-resources/src/components/activity-message/ActivityMessagePresenter.svelte index a81e8ac830..3069850236 100644 --- a/plugins/activity-resources/src/components/activity-message/ActivityMessagePresenter.svelte +++ b/plugins/activity-resources/src/components/activity-message/ActivityMessagePresenter.svelte @@ -33,7 +33,7 @@ export let actions: Action[] = [] export let hoverable = true export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover' - export let withShowMore: boolean = true + export let withShowMore: boolean = false export let attachmentImageSize: 'x-large' | undefined = undefined export let type: ActivityMessageViewType = 'default' export let videoPreload = true diff --git a/plugins/activity-resources/src/components/reactions/Reactions.svelte b/plugins/activity-resources/src/components/reactions/Reactions.svelte index 440cb291ba..d7ecf51871 100644 --- a/plugins/activity-resources/src/components/reactions/Reactions.svelte +++ b/plugins/activity-resources/src/components/reactions/Reactions.svelte @@ -27,7 +27,6 @@ export let readonly: boolean = false const dispatch = createEventDispatcher() - const client = getClient() const me = getCurrentAccount() let reactionsAccounts = new Map[]>() diff --git a/plugins/activity-resources/src/components/reactions/ReactionsPresenter.svelte b/plugins/activity-resources/src/components/reactions/ReactionsPresenter.svelte index 86eb49e3a1..0a2c4cabbf 100644 --- a/plugins/activity-resources/src/components/reactions/ReactionsPresenter.svelte +++ b/plugins/activity-resources/src/components/reactions/ReactionsPresenter.svelte @@ -55,7 +55,7 @@ } -{#if object && reactions.length > 0} +{#if object && (reactions.length > 0 || (object?.reactions ?? 0) > 0)} diff --git a/plugins/chunter-resources/src/channelDataProvider.ts b/plugins/chunter-resources/src/channelDataProvider.ts index c095d0e855..ed79fa65d5 100644 --- a/plugins/chunter-resources/src/channelDataProvider.ts +++ b/plugins/chunter-resources/src/channelDataProvider.ts @@ -330,12 +330,11 @@ export class ChannelDataProvider implements IChannelDataProvider { const client = getClient() const skipIds = this.getChunkSkipIds(loadAfter) - const messages = await client.findAll( + let messages: ActivityMessage[] = await client.findAll( this.msgClass, { attachedTo: this.chatId, space: this.space, - _id: { $nin: skipIds }, createdOn: equal ? isBackward ? { $lte: loadAfter } @@ -351,6 +350,8 @@ export class ChannelDataProvider implements IChannelDataProvider { } ) + messages = messages.filter(({ _id }) => !skipIds.includes(_id)) + if (messages.length === 0) { return } diff --git a/plugins/chunter-resources/src/components/BaseChatScroller.svelte b/plugins/chunter-resources/src/components/BaseChatScroller.svelte new file mode 100644 index 0000000000..36426376a5 --- /dev/null +++ b/plugins/chunter-resources/src/components/BaseChatScroller.svelte @@ -0,0 +1,56 @@ + + + +{#if loadingOverlay} +
+ +
+{/if} + + + + + diff --git a/plugins/chunter-resources/src/components/Channel.svelte b/plugins/chunter-resources/src/components/Channel.svelte index ec2ff01215..3273498461 100644 --- a/plugins/chunter-resources/src/components/Channel.svelte +++ b/plugins/chunter-resources/src/components/Channel.svelte @@ -19,11 +19,11 @@ import { getClient, isSpace } from '@hcengineering/presentation' import { getMessageFromLoc, messageInFocus } from '@hcengineering/activity-resources' import { location as locationStore } from '@hcengineering/ui' + import { onDestroy } from 'svelte' import chunter from '../plugin' - import ChannelScrollView from './ChannelScrollView.svelte' import { ChannelDataProvider } from '../channelDataProvider' - import { onDestroy } from 'svelte' + import ReverseChannelScrollView from './ReverseChannelScrollView.svelte' export let object: Doc export let context: DocNotifyContext | undefined @@ -101,17 +101,13 @@ {#if dataProvider} - {/if} diff --git a/plugins/chunter-resources/src/components/ChannelHeader.svelte b/plugins/chunter-resources/src/components/ChannelHeader.svelte index 284153a4eb..d59c9cc794 100644 --- a/plugins/chunter-resources/src/components/ChannelHeader.svelte +++ b/plugins/chunter-resources/src/components/ChannelHeader.svelte @@ -74,7 +74,7 @@ intlLabel={chunter.string.Channel} {description} titleKind={isPerson ? 'default' : 'breadcrumbs'} - withFilters={!hierarchy.isDerived(_class, chunter.class.ChunterSpace)} + withFilters={false} {allowClose} {canOpen} {withAside} diff --git a/plugins/chunter-resources/src/components/ChannelInput.svelte b/plugins/chunter-resources/src/components/ChannelInput.svelte index 218d13237d..e4fbfa8503 100644 --- a/plugins/chunter-resources/src/components/ChannelInput.svelte +++ b/plugins/chunter-resources/src/components/ChannelInput.svelte @@ -92,7 +92,7 @@ diff --git a/plugins/chunter-resources/src/components/threads/ThreadContent.svelte b/plugins/chunter-resources/src/components/threads/ThreadContent.svelte index 827da56d96..5f10008a09 100644 --- a/plugins/chunter-resources/src/components/threads/ThreadContent.svelte +++ b/plugins/chunter-resources/src/components/threads/ThreadContent.svelte @@ -1,20 +1,24 @@