feat: Theme studio v2
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
<div class="chat-layout relative h-full">
|
||||
<div
|
||||
appThemeNode="chatSurface"
|
||||
class="chat-layout relative h-full"
|
||||
>
|
||||
<app-chat-message-list
|
||||
[allMessages]="allMessages()"
|
||||
[channelMessages]="channelMessages()"
|
||||
@@ -19,7 +22,10 @@
|
||||
(embedRemoved)="handleEmbedRemoved($event)"
|
||||
/>
|
||||
|
||||
<div class="chat-bottom-bar absolute bottom-0 left-0 right-0 z-10">
|
||||
<div
|
||||
appThemeNode="chatComposerBar"
|
||||
class="chat-bottom-bar absolute bottom-0 left-0 right-0 z-10"
|
||||
>
|
||||
<app-chat-message-composer
|
||||
[replyTo]="replyTo()"
|
||||
[showKlipyGifPicker]="showKlipyGifPicker()"
|
||||
@@ -47,6 +53,7 @@
|
||||
|
||||
<div class="pointer-events-none fixed inset-0 z-[90]">
|
||||
<div
|
||||
appThemeNode="chatGifPickerSurface"
|
||||
class="pointer-events-auto absolute w-[calc(100vw-2rem)] max-w-5xl sm:w-[34rem] md:w-[42rem] xl:w-[52rem]"
|
||||
[style.bottom.px]="composerBottomPadding() + 8"
|
||||
[style.right.px]="klipyGifPickerAnchorRight()"
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { selectCurrentUser, selectIsCurrentUserAdmin } from '../../../../store/users/users.selectors';
|
||||
import { selectActiveChannelId, selectCurrentRoom } from '../../../../store/rooms/rooms.selectors';
|
||||
import { Message } from '../../../../shared-kernel';
|
||||
import { ThemeNodeDirective } from '../../../theme';
|
||||
import { ChatMessageComposerComponent } from './components/message-composer/chat-message-composer.component';
|
||||
import { KlipyGifPickerComponent } from '../klipy-gif-picker/klipy-gif-picker.component';
|
||||
import { ChatMessageListComponent } from './components/message-list/chat-message-list.component';
|
||||
@@ -43,7 +44,8 @@ import {
|
||||
ChatMessageComposerComponent,
|
||||
KlipyGifPickerComponent,
|
||||
ChatMessageListComponent,
|
||||
ChatMessageOverlaysComponent
|
||||
ChatMessageOverlaysComponent,
|
||||
ThemeNodeDirective
|
||||
],
|
||||
templateUrl: './chat-messages.component.html',
|
||||
styleUrl: './chat-messages.component.scss'
|
||||
@@ -70,16 +72,10 @@ export class ChatMessagesComponent {
|
||||
const channelId = this.activeChannelId();
|
||||
const roomId = this.currentRoom()?.id;
|
||||
|
||||
return this.allMessages().filter(
|
||||
(message) =>
|
||||
message.roomId === roomId &&
|
||||
(message.channelId || 'general') === channelId
|
||||
);
|
||||
return this.allMessages().filter((message) => message.roomId === roomId && (message.channelId || 'general') === channelId);
|
||||
});
|
||||
|
||||
readonly conversationKey = computed(
|
||||
() => `${this.currentRoom()?.id ?? 'no-room'}:${this.activeChannelId() ?? 'general'}`
|
||||
);
|
||||
readonly conversationKey = computed(() => `${this.currentRoom()?.id ?? 'no-room'}:${this.activeChannelId() ?? 'general'}`);
|
||||
readonly klipyEnabled = computed(() => this.klipy.isEnabled(this.currentRoom()));
|
||||
readonly composerBottomPadding = signal(140);
|
||||
readonly klipyGifPickerAnchorRight = signal(16);
|
||||
@@ -176,9 +172,7 @@ export class ChatMessagesComponent {
|
||||
if (!message || !currentUserId)
|
||||
return;
|
||||
|
||||
const hasReacted = message.reactions.some(
|
||||
(reaction) => reaction.emoji === event.emoji && reaction.userId === currentUserId
|
||||
);
|
||||
const hasReacted = message.reactions.some((reaction) => reaction.emoji === event.emoji && reaction.userId === currentUserId);
|
||||
|
||||
if (hasReacted) {
|
||||
this.store.dispatch(
|
||||
@@ -243,9 +237,7 @@ export class ChatMessagesComponent {
|
||||
const minRight = 16;
|
||||
const maxRight = Math.max(minRight, viewportWidth - popupWidth - 16);
|
||||
|
||||
this.klipyGifPickerAnchorRight.set(
|
||||
Math.min(Math.max(Math.round(preferredRight), minRight), maxRight)
|
||||
);
|
||||
this.klipyGifPickerAnchorRight.set(Math.min(Math.max(Math.round(preferredRight), minRight), maxRight));
|
||||
}
|
||||
|
||||
private getKlipyGifPickerWidth(viewportWidth: number): number {
|
||||
@@ -290,10 +282,7 @@ export class ChatMessagesComponent {
|
||||
|
||||
if (blob) {
|
||||
try {
|
||||
const result = await electronApi.saveFileAs(
|
||||
attachment.filename,
|
||||
await this.blobToBase64(blob)
|
||||
);
|
||||
const result = await electronApi.saveFileAs(attachment.filename, await this.blobToBase64(blob));
|
||||
|
||||
if (result.saved || result.cancelled)
|
||||
return;
|
||||
@@ -416,12 +405,7 @@ export class ChatMessagesComponent {
|
||||
|
||||
const message = [...this.channelMessages()]
|
||||
.reverse()
|
||||
.find(
|
||||
(entry) =>
|
||||
entry.senderId === currentUserId &&
|
||||
entry.content === content &&
|
||||
!entry.isDeleted
|
||||
);
|
||||
.find((entry) => entry.senderId === currentUserId && entry.content === content && !entry.isDeleted);
|
||||
|
||||
if (!message) {
|
||||
setTimeout(() => this.attachFilesToLastOwnMessage(content, pendingFiles), 150);
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<!-- eslint-disable @angular-eslint/template/button-has-type -->
|
||||
<div #composerRoot>
|
||||
<div
|
||||
#composerRoot
|
||||
appThemeNode="chatComposerBar"
|
||||
>
|
||||
@if (replyTo()) {
|
||||
<div class="pointer-events-auto flex items-center gap-2 bg-secondary/50 px-4 py-2">
|
||||
<div
|
||||
appThemeNode="chatComposerReplyBar"
|
||||
class="pointer-events-auto flex items-center gap-2 bg-secondary/50 px-4 py-2"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideReply"
|
||||
class="h-4 w-4 text-muted-foreground"
|
||||
@@ -31,6 +37,7 @@
|
||||
(mouseleave)="onToolbarMouseLeave()"
|
||||
>
|
||||
<div
|
||||
appThemeNode="chatComposerToolbar"
|
||||
class="mx-4 -mb-2 flex flex-wrap items-center justify-start gap-2 rounded-lg border border-border bg-card/70 px-2 py-1 shadow-sm backdrop-blur"
|
||||
>
|
||||
<button
|
||||
@@ -124,6 +131,7 @@
|
||||
|
||||
<div class="border-border p-4">
|
||||
<div
|
||||
appThemeNode="chatComposerInput"
|
||||
class="chat-input-wrapper relative"
|
||||
(mouseenter)="inputHovered.set(true)"
|
||||
(mouseleave)="inputHovered.set(false)"
|
||||
@@ -156,6 +164,7 @@
|
||||
}
|
||||
|
||||
<button
|
||||
appThemeNode="chatComposerSendButton"
|
||||
type="button"
|
||||
(click)="sendMessage()"
|
||||
[disabled]="!messageContent.trim() && pendingFiles.length === 0 && !pendingKlipyGif()"
|
||||
|
||||
@@ -23,6 +23,7 @@ import type { ClipboardFilePayload } from '../../../../../../core/platform/elect
|
||||
import { ElectronBridgeService } from '../../../../../../core/platform/electron/electron-bridge.service';
|
||||
import { KlipyGif, KlipyService } from '../../../../application/services/klipy.service';
|
||||
import { Message } from '../../../../../../shared-kernel';
|
||||
import { ThemeNodeDirective } from '../../../../../theme';
|
||||
import type { RoomSignalSourceInput } from '../../../../../server-directory';
|
||||
import { ChatImageProxyFallbackDirective } from '../../../chat-image-proxy-fallback.directive';
|
||||
import { TypingIndicatorComponent } from '../../../typing-indicator/typing-indicator.component';
|
||||
@@ -43,7 +44,8 @@ const DEFAULT_TEXTAREA_HEIGHT = 62;
|
||||
FormsModule,
|
||||
NgIcon,
|
||||
ChatImageProxyFallbackDirective,
|
||||
TypingIndicatorComponent
|
||||
TypingIndicatorComponent,
|
||||
ThemeNodeDirective
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
@@ -415,11 +417,7 @@ export class ChatMessageComposerComponent implements AfterViewInit, OnDestroy {
|
||||
requestAnimationFrame(() => this.messageInputRef?.nativeElement.focus());
|
||||
}
|
||||
|
||||
private hasPotentialFilePayload(
|
||||
dataTransfer: DataTransfer | null,
|
||||
treatMissingTypesAsPotentialFile = true
|
||||
): boolean {
|
||||
|
||||
private hasPotentialFilePayload(dataTransfer: DataTransfer | null, treatMissingTypesAsPotentialFile = true): boolean {
|
||||
if (!dataTransfer)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
@let msg = message();
|
||||
@let attachmentsList = attachmentViewModels();
|
||||
<div
|
||||
appThemeNode="chatMessageBubble"
|
||||
[attr.data-message-id]="msg.id"
|
||||
class="group relative flex gap-3 rounded-lg p-2 transition-colors hover:bg-secondary/30"
|
||||
[class.opacity-50]="msg.isDeleted"
|
||||
>
|
||||
<div
|
||||
appThemeNode="chatMessageAvatar"
|
||||
class="flex-shrink-0 cursor-pointer"
|
||||
(click)="openSenderProfileCard($event); $event.stopPropagation()"
|
||||
>
|
||||
@@ -17,7 +19,10 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div
|
||||
appThemeNode="chatMessageContent"
|
||||
class="min-w-0 flex-1"
|
||||
>
|
||||
@if (msg.replyToId) {
|
||||
@let reply = repliedMessage();
|
||||
<div
|
||||
@@ -150,7 +155,10 @@
|
||||
</div>
|
||||
</div>
|
||||
} @else if ((att.receivedBytes || 0) > 0) {
|
||||
<div class="max-w-xs rounded-md border border-border bg-secondary/40 p-3">
|
||||
<div
|
||||
appThemeNode="chatAttachmentCard"
|
||||
class="max-w-xs rounded-md border border-border bg-secondary/40 p-3"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md bg-primary/10">
|
||||
<ng-icon
|
||||
@@ -172,7 +180,10 @@
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="max-w-xs rounded-md border border-dashed border-border bg-secondary/20 p-4">
|
||||
<div
|
||||
appThemeNode="chatAttachmentCard"
|
||||
class="max-w-xs rounded-md border border-dashed border-border bg-secondary/20 p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md bg-muted">
|
||||
<ng-icon
|
||||
@@ -220,7 +231,10 @@
|
||||
/>
|
||||
}
|
||||
} @else if ((att.receivedBytes || 0) > 0) {
|
||||
<div class="max-w-xl rounded-md border border-border bg-secondary/40 p-3">
|
||||
<div
|
||||
appThemeNode="chatAttachmentCard"
|
||||
class="max-w-xl rounded-md border border-border bg-secondary/40 p-3"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm font-medium">{{ att.filename }}</div>
|
||||
@@ -247,7 +261,10 @@
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="max-w-xl rounded-md border border-dashed border-border bg-secondary/20 p-4">
|
||||
<div
|
||||
appThemeNode="chatAttachmentCard"
|
||||
class="max-w-xl rounded-md border border-dashed border-border bg-secondary/20 p-4"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm font-medium text-foreground">{{ att.filename }}</div>
|
||||
@@ -271,7 +288,10 @@
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<div class="rounded-md border border-border bg-secondary/40 p-2">
|
||||
<div
|
||||
appThemeNode="chatAttachmentCard"
|
||||
class="rounded-md border border-border bg-secondary/40 p-2"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-sm font-medium">{{ att.filename }}</div>
|
||||
@@ -339,6 +359,7 @@
|
||||
<div class="mt-2 flex flex-wrap gap-1">
|
||||
@for (reaction of getGroupedReactions(); track reaction.emoji) {
|
||||
<button
|
||||
appThemeNode="chatReactionPill"
|
||||
(click)="toggleReaction(reaction.emoji)"
|
||||
class="flex items-center gap-1 rounded-full bg-secondary px-2 py-0.5 text-xs transition-colors hover:bg-secondary/80"
|
||||
[class.ring-1]="reaction.hasCurrentUser"
|
||||
@@ -354,6 +375,7 @@
|
||||
|
||||
@if (!msg.isDeleted) {
|
||||
<div
|
||||
appThemeNode="chatMessageActions"
|
||||
class="absolute right-2 top-2 flex items-center gap-1 rounded-lg border border-border bg-card shadow-lg opacity-0 transition-opacity group-hover:opacity-100"
|
||||
>
|
||||
<div class="relative">
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
Message,
|
||||
User
|
||||
} from '../../../../../../shared-kernel';
|
||||
import { ThemeNodeDirective } from '../../../../../theme';
|
||||
|
||||
import {
|
||||
ChatAudioPlayerComponent,
|
||||
@@ -96,7 +97,8 @@ interface ChatMessageAttachmentViewModel extends Attachment {
|
||||
ChatVideoPlayerComponent,
|
||||
ChatMessageMarkdownComponent,
|
||||
ChatLinkEmbedComponent,
|
||||
UserAvatarComponent
|
||||
UserAvatarComponent,
|
||||
ThemeNodeDirective
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
@@ -150,15 +152,17 @@ export class ChatMessageItemComponent {
|
||||
const msg = this.message();
|
||||
const found = this.userLookup().get(msg.senderId);
|
||||
|
||||
return found ?? {
|
||||
id: msg.senderId,
|
||||
oderId: msg.senderId,
|
||||
username: msg.senderName,
|
||||
displayName: msg.senderName,
|
||||
status: 'disconnected',
|
||||
role: 'member',
|
||||
joinedAt: 0
|
||||
};
|
||||
return (
|
||||
found ?? {
|
||||
id: msg.senderId,
|
||||
oderId: msg.senderId,
|
||||
username: msg.senderName,
|
||||
displayName: msg.senderName,
|
||||
status: 'disconnected',
|
||||
role: 'member',
|
||||
joinedAt: 0
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
editContent = '';
|
||||
@@ -175,9 +179,7 @@ export class ChatMessageItemComponent {
|
||||
readonly attachmentViewModels = computed<ChatMessageAttachmentViewModel[]>(() => {
|
||||
void this.attachmentVersion();
|
||||
|
||||
return this.attachmentsSvc.getForMessage(this.message().id).map((attachment) =>
|
||||
this.buildAttachmentViewModel(attachment)
|
||||
);
|
||||
return this.attachmentsSvc.getForMessage(this.message().id).map((attachment) => this.buildAttachmentViewModel(attachment));
|
||||
});
|
||||
private readonly syncAttachmentVersion = effect(() => {
|
||||
const version = this.attachmentsSvc.updated();
|
||||
@@ -320,8 +322,7 @@ export class ChatMessageItemComponent {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
const toDay = (value: Date) =>
|
||||
new Date(value.getFullYear(), value.getMonth(), value.getDate()).getTime();
|
||||
const toDay = (value: Date) => new Date(value.getFullYear(), value.getMonth(), value.getDate()).getTime();
|
||||
const dayDiff = Math.round((toDay(now) - toDay(date)) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (dayDiff === 0)
|
||||
@@ -331,11 +332,7 @@ export class ChatMessageItemComponent {
|
||||
return 'Yesterday ' + time;
|
||||
|
||||
if (dayDiff < 7) {
|
||||
return (
|
||||
date.toLocaleDateString([], { weekday: 'short' }) +
|
||||
' ' +
|
||||
time
|
||||
);
|
||||
return date.toLocaleDateString([], { weekday: 'short' }) + ' ' + time;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -402,10 +399,7 @@ export class ChatMessageItemComponent {
|
||||
}
|
||||
|
||||
requiresMediaDownloadAcceptance(attachment: Attachment): boolean {
|
||||
return (
|
||||
(this.isVideoAttachment(attachment) || this.isAudioAttachment(attachment)) &&
|
||||
attachment.size > MAX_AUTO_SAVE_SIZE_BYTES
|
||||
);
|
||||
return (this.isVideoAttachment(attachment) || this.isAudioAttachment(attachment)) && attachment.size > MAX_AUTO_SAVE_SIZE_BYTES;
|
||||
}
|
||||
|
||||
getMediaAttachmentStatusText(attachment: Attachment): string {
|
||||
@@ -418,9 +412,7 @@ export class ChatMessageItemComponent {
|
||||
: 'Large audio file. Accept the download to play it in chat.';
|
||||
}
|
||||
|
||||
return this.isVideoAttachment(attachment)
|
||||
? 'Waiting for video source...'
|
||||
: 'Waiting for audio source...';
|
||||
return this.isVideoAttachment(attachment) ? 'Waiting for video source...' : 'Waiting for audio source...';
|
||||
}
|
||||
|
||||
getMediaAttachmentActionLabel(attachment: Attachment): string {
|
||||
@@ -484,8 +476,7 @@ export class ChatMessageItemComponent {
|
||||
private buildAttachmentViewModel(attachment: Attachment): ChatMessageAttachmentViewModel {
|
||||
const isVideo = this.isVideoAttachment(attachment);
|
||||
const isAudio = this.isAudioAttachment(attachment);
|
||||
const requiresMediaDownloadAcceptance =
|
||||
(isVideo || isAudio) && attachment.size > MAX_AUTO_SAVE_SIZE_BYTES;
|
||||
const requiresMediaDownloadAcceptance = (isVideo || isAudio) && attachment.size > MAX_AUTO_SAVE_SIZE_BYTES;
|
||||
|
||||
return {
|
||||
...attachment,
|
||||
@@ -493,8 +484,12 @@ export class ChatMessageItemComponent {
|
||||
isUploader: this.isUploader(attachment),
|
||||
isVideo,
|
||||
mediaActionLabel: requiresMediaDownloadAcceptance
|
||||
? attachment.requestError ? 'Retry download' : 'Accept download'
|
||||
: attachment.requestError ? 'Retry' : 'Request',
|
||||
? attachment.requestError
|
||||
? 'Retry download'
|
||||
: 'Accept download'
|
||||
: attachment.requestError
|
||||
? 'Retry'
|
||||
: 'Request',
|
||||
mediaStatusText: attachment.requestError
|
||||
? attachment.requestError
|
||||
: requiresMediaDownloadAcceptance
|
||||
@@ -504,15 +499,11 @@ export class ChatMessageItemComponent {
|
||||
: isVideo
|
||||
? 'Waiting for video source...'
|
||||
: 'Waiting for audio source...',
|
||||
progressPercent: attachment.size > 0
|
||||
? ((attachment.receivedBytes || 0) * 100) / attachment.size
|
||||
: 0
|
||||
progressPercent: attachment.size > 0 ? ((attachment.receivedBytes || 0) * 100) / attachment.size : 0
|
||||
};
|
||||
}
|
||||
|
||||
private getLiveAttachment(attachmentId: string): Attachment | undefined {
|
||||
return this.attachmentsSvc
|
||||
.getForMessage(this.message().id)
|
||||
.find((attachment) => attachment.id === attachmentId);
|
||||
return this.attachmentsSvc.getForMessage(this.message().id).find((attachment) => attachment.id === attachmentId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div
|
||||
#messagesContainer
|
||||
appThemeNode="chatMessageList"
|
||||
class="absolute inset-0 space-y-4 overflow-y-auto p-4"
|
||||
[style.padding-bottom.px]="bottomPadding()"
|
||||
(scroll)="onScroll()"
|
||||
@@ -39,7 +40,10 @@
|
||||
|
||||
@for (message of messages(); track message.id; let index = $index) {
|
||||
@if (dateSeparatorLabels().get(index); as separatorLabel) {
|
||||
<div class="flex items-center gap-3 py-1">
|
||||
<div
|
||||
appThemeNode="chatDateSeparator"
|
||||
class="flex items-center gap-3 py-1"
|
||||
>
|
||||
<div class="h-px flex-1 bg-border"></div>
|
||||
<span class="rounded-full border border-border bg-background/90 px-3 py-1 text-xs font-medium text-muted-foreground shadow-sm">
|
||||
{{ separatorLabel }}
|
||||
@@ -70,7 +74,10 @@
|
||||
|
||||
@if (showNewMessagesBar()) {
|
||||
<div class="pointer-events-none sticky bottom-4 flex justify-center">
|
||||
<div class="pointer-events-auto flex items-center gap-3 rounded-lg border border-border bg-card px-3 py-2 shadow">
|
||||
<div
|
||||
appThemeNode="chatNewMessagesBar"
|
||||
class="pointer-events-auto flex items-center gap-3 rounded-lg border border-border bg-card px-3 py-2 shadow"
|
||||
>
|
||||
<span class="text-sm text-muted-foreground">New messages</span>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
ChatMessageReplyEvent
|
||||
} from '../../models/chat-messages.model';
|
||||
import { selectAllUsers } from '../../../../../../store/users/users.selectors';
|
||||
import { ThemeNodeDirective } from '../../../../../theme';
|
||||
import { ChatMessageItemComponent } from '../message-item/chat-message-item.component';
|
||||
|
||||
interface PrismGlobal {
|
||||
@@ -41,7 +42,11 @@ declare global {
|
||||
@Component({
|
||||
selector: 'app-chat-message-list',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ChatMessageItemComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ChatMessageItemComponent,
|
||||
ThemeNodeDirective
|
||||
],
|
||||
templateUrl: './chat-message-list.component.html',
|
||||
host: {
|
||||
style: 'display: contents;'
|
||||
@@ -94,9 +99,7 @@ export class ChatMessageListComponent implements AfterViewChecked, OnDestroy {
|
||||
return all.slice(all.length - limit);
|
||||
});
|
||||
|
||||
readonly hasMoreMessages = computed(
|
||||
() => this.channelMessages().length > this.displayLimit()
|
||||
);
|
||||
readonly hasMoreMessages = computed(() => this.channelMessages().length > this.displayLimit());
|
||||
|
||||
readonly dateSeparatorLabels = computed(() => {
|
||||
const labels = new Map<number, string>();
|
||||
@@ -165,8 +168,7 @@ export class ChatMessageListComponent implements AfterViewChecked, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
const distanceFromBottom =
|
||||
element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||
const newMessages = currentCount > this.lastMessageCount;
|
||||
|
||||
if (newMessages) {
|
||||
@@ -228,8 +230,7 @@ export class ChatMessageListComponent implements AfterViewChecked, OnDestroy {
|
||||
if (!element || this.isAutoScrolling)
|
||||
return;
|
||||
|
||||
const distanceFromBottom =
|
||||
element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||
const shouldStickToBottom = distanceFromBottom <= 300;
|
||||
|
||||
if (shouldStickToBottom) {
|
||||
@@ -386,11 +387,7 @@ export class ChatMessageListComponent implements AfterViewChecked, OnDestroy {
|
||||
}
|
||||
|
||||
if (this.boundOnImageLoad && this.messagesContainer) {
|
||||
this.messagesContainer.nativeElement.removeEventListener(
|
||||
'load',
|
||||
this.boundOnImageLoad,
|
||||
true
|
||||
);
|
||||
this.messagesContainer.nativeElement.removeEventListener('load', this.boundOnImageLoad, true);
|
||||
|
||||
this.boundOnImageLoad = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user