mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-25 19:58:30 +03:00
UBERF-7638: Add scroll to latest message button (#6119)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
c443cc60bd
commit
4962367182
@ -40,6 +40,7 @@
|
||||
export let inheritFont: boolean = false
|
||||
export let tooltip: LabelAndProps | undefined = undefined
|
||||
export let element: HTMLButtonElement | undefined = undefined
|
||||
export let shape: 'rectangle' | 'round' = 'rectangle'
|
||||
export let id: string | undefined = undefined
|
||||
|
||||
let actualIconSize: IconSize = 'small'
|
||||
@ -89,7 +90,7 @@
|
||||
<button
|
||||
{id}
|
||||
bind:this={element}
|
||||
class="font-medium-14 {kind} {size} {type}"
|
||||
class="font-medium-14 {kind} {size} {type} {shape}"
|
||||
class:loading
|
||||
class:pressed
|
||||
class:inheritColor
|
||||
@ -155,6 +156,10 @@
|
||||
height: var(--global-large-Size);
|
||||
border-radius: var(--medium-BorderRadius);
|
||||
|
||||
&.round {
|
||||
border-radius: var(--large-BorderRadius);
|
||||
}
|
||||
|
||||
&.type-button:not(.iconOnly) {
|
||||
padding: 0 var(--spacing-2);
|
||||
}
|
||||
@ -167,6 +172,9 @@
|
||||
height: var(--global-medium-Size);
|
||||
border-radius: var(--medium-BorderRadius);
|
||||
|
||||
&.round {
|
||||
border-radius: var(--large-BorderRadius);
|
||||
}
|
||||
&.type-button:not(.iconOnly) {
|
||||
padding: 0 var(--spacing-2);
|
||||
}
|
||||
@ -180,6 +188,9 @@
|
||||
gap: var(--spacing-0_5);
|
||||
border-radius: var(--small-BorderRadius);
|
||||
|
||||
&.round {
|
||||
border-radius: var(--large-BorderRadius);
|
||||
}
|
||||
&.type-button:not(.iconOnly) {
|
||||
padding: 0 var(--spacing-1);
|
||||
}
|
||||
@ -192,6 +203,9 @@
|
||||
height: var(--global-extra-small-Size);
|
||||
border-radius: var(--extra-small-BorderRadius);
|
||||
|
||||
&.round {
|
||||
border-radius: var(--large-BorderRadius);
|
||||
}
|
||||
&.type-button:not(.iconOnly) {
|
||||
padding: 0 var(--spacing-1);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
export let labelParams: Record<string, any> = {}
|
||||
export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' = 'secondary'
|
||||
export let size: ButtonBaseSize = 'large'
|
||||
export let shape: 'rectangle' | 'round' = 'rectangle'
|
||||
export let icon: Asset | AnySvelteComponent | ComponentType | undefined = undefined
|
||||
export let iconSize: IconSize | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
@ -29,6 +30,7 @@
|
||||
<ButtonBase
|
||||
type={'type-button'}
|
||||
{title}
|
||||
{shape}
|
||||
{label}
|
||||
{labelParams}
|
||||
{kind}
|
||||
|
@ -110,6 +110,7 @@
|
||||
"JoinChannelHeader": "Click \"Join\" to get started.",
|
||||
"JoinChannelText": "Once you've joined, you'll be able to read all messages and contribute to the discussion.",
|
||||
"NoMessagesInChannel": "Currently there are no messages",
|
||||
"SendMessagesInChannel": "Send the first message to start the conversation"
|
||||
"SendMessagesInChannel": "Send the first message to start the conversation",
|
||||
"LatestMessages": "↓ Latest messages"
|
||||
}
|
||||
}
|
@ -110,6 +110,7 @@
|
||||
"JoinChannelHeader": "Haga clic en \"Unirse\" para comenzar.",
|
||||
"JoinChannelText": "Una vez que se haya unido, podrá leer todos los mensajes y contribuir a la discusión.",
|
||||
"NoMessagesInChannel": "No hay mensajes en este canal todavía.",
|
||||
"SendMessagesInChannel": "Envíe mensajes en este canal para comenzar la conversación."
|
||||
"SendMessagesInChannel": "Envíe mensajes en este canal para comenzar la conversación.",
|
||||
"LatestMessages": "↓ Últimos mensajes"
|
||||
}
|
||||
}
|
@ -110,6 +110,7 @@
|
||||
"JoinChannelHeader": "Cliquez sur \"Rejoindre\" pour commencer.",
|
||||
"JoinChannelText": "Une fois que vous avez rejoint, vous pourrez lire tous les messages et participer à la discussion.",
|
||||
"NoMessagesInChannel": "Il n'y a pas encore de messages dans ce canal.",
|
||||
"SendMessagesInChannel": "Envoyez des messages pour commencer la conversation."
|
||||
"SendMessagesInChannel": "Envoyez des messages pour commencer la conversation.",
|
||||
"LatestMessages": "↓ Derniers messages"
|
||||
}
|
||||
}
|
@ -110,6 +110,7 @@
|
||||
"JoinChannelHeader": "Clique em \"Participar\" para começar.",
|
||||
"JoinChannelText": "Depois de entrar, você poderá ler todas as mensagens e contribuir na discussão.",
|
||||
"NoMessagesInChannel": "Ainda não existem mensagens neste canal.",
|
||||
"SendMessagesInChannel": "Envie a sua primeira mensagem!"
|
||||
"SendMessagesInChannel": "Envie a sua primeira mensagem!",
|
||||
"LatestMessages": "↓ Últimas mensagens"
|
||||
}
|
||||
}
|
@ -110,6 +110,7 @@
|
||||
"JoinChannelHeader": "Нажмите \"Присоединиться\", чтобы начать.",
|
||||
"JoinChannelText": "Присоединившись, вы сможете читать все сообщения и участвовать в обсуждении.",
|
||||
"NoMessagesInChannel": "В этом канале пока нет сообщений",
|
||||
"SendMessagesInChannel": "Отправьте первое сообщение, чтобы начать общение"
|
||||
"SendMessagesInChannel": "Отправьте первое сообщение, чтобы начать общение",
|
||||
"LatestMessages": "↓ Последние сообщения"
|
||||
}
|
||||
}
|
@ -110,6 +110,7 @@
|
||||
"JoinChannelHeader": "点击“加入”开始。",
|
||||
"JoinChannelText": "加入后,你将能够阅读所有消息并参与讨论。",
|
||||
"NoMessagesInChannel": "此频道中没有消息。",
|
||||
"SendMessagesInChannel": "在此频道中发送消息。"
|
||||
"SendMessagesInChannel": "在此频道中发送消息。",
|
||||
"LatestMessages": "↓ 最新消息"
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,10 @@ import { derived, get, type Readable, writable } from 'svelte/store'
|
||||
import { type ActivityMessage } from '@hcengineering/activity'
|
||||
import attachment from '@hcengineering/attachment'
|
||||
import { combineActivityMessages } from '@hcengineering/activity-resources'
|
||||
import { type ChatMessage } from '@hcengineering/chunter'
|
||||
import notification, { type DocNotifyContext } from '@hcengineering/notification'
|
||||
|
||||
import chunter from './plugin'
|
||||
import { type ChatMessage } from '@hcengineering/chunter'
|
||||
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
|
||||
|
||||
export type LoadMode = 'forward' | 'backward'
|
||||
|
||||
@ -175,7 +175,11 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
)
|
||||
}
|
||||
|
||||
private async loadInitialMessages (selectedMsg?: Ref<ActivityMessage>, loadAll = false): Promise<void> {
|
||||
private async loadInitialMessages (
|
||||
selectedMsg?: Ref<ActivityMessage>,
|
||||
loadAll = false,
|
||||
ignoreNew = false
|
||||
): Promise<void> {
|
||||
const isLoading = get(this.isInitialLoadingStore)
|
||||
const isLoaded = get(this.isInitialLoadedStore)
|
||||
|
||||
@ -183,28 +187,15 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
return
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
this.isInitialLoadingStore.set(true)
|
||||
const firstNotification =
|
||||
this.context !== undefined
|
||||
? await client.findOne(
|
||||
notification.class.InboxNotification,
|
||||
{
|
||||
_class: {
|
||||
$in: [notification.class.MentionInboxNotification, notification.class.ActivityInboxNotification]
|
||||
},
|
||||
docNotifyContext: this.context._id,
|
||||
isViewed: false
|
||||
},
|
||||
{ sort: { createdOn: SortingOrder.Ascending } }
|
||||
)
|
||||
: undefined
|
||||
|
||||
const metadata = get(this.metadataStore)
|
||||
const firstNewMsgIndex = this.getFirstNewMsgIndex(firstNotification)
|
||||
const firstNewMsgIndex = ignoreNew ? undefined : await this.getFirstNewMsgIndex()
|
||||
|
||||
if (get(this.newTimestampStore) === undefined) {
|
||||
this.newTimestampStore.set(firstNewMsgIndex !== undefined ? metadata[firstNewMsgIndex]?.createdOn : undefined)
|
||||
} else if (ignoreNew) {
|
||||
this.newTimestampStore.set(undefined)
|
||||
}
|
||||
|
||||
const startPosition = this.getStartPosition(selectedMsg ?? this.selectedMsgId, firstNewMsgIndex)
|
||||
@ -351,7 +342,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
return firsNewMsgIndex
|
||||
}
|
||||
|
||||
private getFirstNewMsgIndex (firstNotification: InboxNotification | undefined): number | undefined {
|
||||
private async getFirstNewMsgIndex (): Promise<number | undefined> {
|
||||
const metadata = get(this.metadataStore)
|
||||
|
||||
if (metadata.length === 0) {
|
||||
@ -363,6 +354,18 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
}
|
||||
|
||||
const lastViewedTimestamp = this.context.lastViewedTimestamp
|
||||
const client = getClient()
|
||||
const firstNotification = await client.findOne(
|
||||
notification.class.InboxNotification,
|
||||
{
|
||||
_class: {
|
||||
$in: [notification.class.MentionInboxNotification, notification.class.ActivityInboxNotification]
|
||||
},
|
||||
docNotifyContext: this.context._id,
|
||||
isViewed: false
|
||||
},
|
||||
{ sort: { createdOn: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
if (lastViewedTimestamp === undefined && firstNotification === undefined) {
|
||||
return -1
|
||||
@ -436,7 +439,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
await this.loadInitialMessages(msg._id)
|
||||
}
|
||||
|
||||
public jumpToMessage (message: ActivityMessage): boolean {
|
||||
public jumpToMessage (message: MessageMetadata): boolean {
|
||||
const metadata = get(this.metadataStore).find(({ _id }) => _id === message._id)
|
||||
|
||||
if (metadata === undefined) {
|
||||
@ -455,7 +458,7 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
return true
|
||||
}
|
||||
|
||||
public jumpToEnd (): boolean {
|
||||
public jumpToEnd (ignoreNew = false): boolean {
|
||||
const last = get(this.metadataStore)[get(this.metadataStore).length - 1]
|
||||
|
||||
if (last === undefined) {
|
||||
@ -468,8 +471,9 @@ export class ChannelDataProvider implements IChannelDataProvider {
|
||||
return false
|
||||
}
|
||||
|
||||
this.selectedMsgId = undefined
|
||||
this.clearMessages()
|
||||
void this.loadInitialMessages()
|
||||
void this.loadInitialMessages(this.selectedMsgId, false, ignoreNew)
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -22,13 +22,14 @@
|
||||
import {
|
||||
ActivityExtension as ActivityExtensionComponent,
|
||||
ActivityMessagePresenter,
|
||||
canGroupMessages
|
||||
canGroupMessages,
|
||||
messageInFocus
|
||||
} from '@hcengineering/activity-resources'
|
||||
import { Class, Doc, getDay, Ref, Timestamp } from '@hcengineering/core'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Loading, Scroller, ScrollParams } from '@hcengineering/ui'
|
||||
import { Loading, ModernButton, Scroller, ScrollParams } from '@hcengineering/ui'
|
||||
import { afterUpdate, beforeUpdate, onDestroy, onMount, tick } from 'svelte'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
@ -101,6 +102,9 @@
|
||||
let selectedDate: Timestamp | undefined = undefined
|
||||
let dateToJump: Timestamp | undefined = undefined
|
||||
|
||||
let prevScrollHeight = 0
|
||||
let isScrollAtBottom = false
|
||||
|
||||
let messagesCount = 0
|
||||
|
||||
let wasAsideOpened = isAsideOpened
|
||||
@ -277,6 +281,7 @@
|
||||
|
||||
function handleScroll ({ autoScrolling }: ScrollParams): void {
|
||||
saveScrollPosition()
|
||||
updateDownButtonVisibility($metadataStore, displayMessages, scrollElement)
|
||||
if (autoScrolling) {
|
||||
return
|
||||
}
|
||||
@ -450,6 +455,8 @@
|
||||
isScrollInitialized = true
|
||||
isInitialScrolling = false
|
||||
}
|
||||
|
||||
updateDownButtonVisibility($metadataStore, displayMessages, scrollElement)
|
||||
}
|
||||
|
||||
function reinitializeScroll (): void {
|
||||
@ -557,9 +564,6 @@
|
||||
loadMore()
|
||||
}
|
||||
|
||||
let prevScrollHeight = 0
|
||||
let isScrollAtBottom = false
|
||||
|
||||
function saveScrollPosition (): void {
|
||||
if (!scrollElement) {
|
||||
return
|
||||
@ -588,7 +592,7 @@
|
||||
}
|
||||
})
|
||||
|
||||
async function compensateAside (isOpened: boolean) {
|
||||
async function compensateAside (isOpened: boolean): Promise<void> {
|
||||
if (!isInitialScrolling && isScrollAtBottom && !wasAsideOpened && isOpened) {
|
||||
await wait()
|
||||
scrollToBottom()
|
||||
@ -599,7 +603,7 @@
|
||||
|
||||
$: void compensateAside(isAsideOpened)
|
||||
|
||||
function canGroupChatMessages (message: ActivityMessage, prevMessage?: ActivityMessage) {
|
||||
function canGroupChatMessages (message: ActivityMessage, prevMessage?: ActivityMessage): boolean {
|
||||
let prevMetadata: MessageMetadata | undefined = undefined
|
||||
|
||||
if (prevMessage === undefined) {
|
||||
@ -617,6 +621,53 @@
|
||||
onDestroy(() => {
|
||||
unsubscribe()
|
||||
})
|
||||
|
||||
let showScrollDownButton = false
|
||||
|
||||
$: updateDownButtonVisibility($metadataStore, displayMessages, scrollElement)
|
||||
|
||||
function updateDownButtonVisibility (
|
||||
metadata: MessageMetadata[],
|
||||
displayMessages: DisplayActivityMessage[],
|
||||
element?: HTMLDivElement
|
||||
): void {
|
||||
if (metadata.length === 0 || displayMessages.length === 0) {
|
||||
showScrollDownButton = false
|
||||
return
|
||||
}
|
||||
|
||||
const lastMetadata = metadata[metadata.length - 1]
|
||||
const lastMessage = displayMessages[displayMessages.length - 1]
|
||||
|
||||
if (lastMetadata._id !== lastMessage._id) {
|
||||
showScrollDownButton = true
|
||||
} else if (element != null) {
|
||||
const { scrollHeight, scrollTop, offsetHeight } = element
|
||||
|
||||
showScrollDownButton = scrollHeight > offsetHeight + scrollTop + 300
|
||||
} else {
|
||||
showScrollDownButton = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleScrollDown (): void {
|
||||
selectedMessageId = undefined
|
||||
messageInFocus.set(undefined)
|
||||
|
||||
const metadata = $metadataStore
|
||||
const lastMetadata = metadata[metadata.length - 1]
|
||||
const lastMessage = displayMessages[displayMessages.length - 1]
|
||||
|
||||
void inboxClient.readDoc(client, objectId)
|
||||
|
||||
if (lastMetadata._id !== lastMessage._id) {
|
||||
separatorIndex = -1
|
||||
provider.jumpToEnd(true)
|
||||
reinitializeScroll()
|
||||
} else {
|
||||
scrollToBottom()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if isLoading}
|
||||
@ -689,6 +740,18 @@
|
||||
<HistoryLoading isLoading={$isLoadingMoreStore} />
|
||||
{/if}
|
||||
</Scroller>
|
||||
|
||||
{#if showScrollDownButton}
|
||||
<div class="down-button absolute">
|
||||
<ModernButton
|
||||
label={chunter.string.LatestMessages}
|
||||
shape="round"
|
||||
size="small"
|
||||
kind="primary"
|
||||
on:click={handleScrollDown}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if object}
|
||||
<div class="ref-input">
|
||||
@ -728,4 +791,11 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.down-button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
bottom: -0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -108,6 +108,7 @@ export default mergeIds(chunterId, chunter, {
|
||||
ArchiveActivityConfirmationTitle: '' as IntlString,
|
||||
ArchiveActivityConfirmationMessage: '' as IntlString,
|
||||
JoinChannelHeader: '' as IntlString,
|
||||
JoinChannelText: '' as IntlString
|
||||
JoinChannelText: '' as IntlString,
|
||||
LatestMessages: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user