Skip to content

Commit

Permalink
Chat - add date headers
Browse files Browse the repository at this point in the history
  • Loading branch information
Zedwag committed Oct 9, 2024
1 parent 677ec57 commit 62b8f1d
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 11 deletions.
42 changes: 42 additions & 0 deletions e2e/testcafe-devextreme/tests/chat/messageList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,45 @@ test('Messagelist should scrolled to the latest messages after being rendered in
}],
});
});

test('Messagelist with date headers', async (t) => {
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);

await testScreenshot(t, takeScreenshot, 'Messagelist with date headers.png', { element: '#container' });

await t
.expect(compareResults.isValid())
.ok(compareResults.errorMessages());
}).before(async () => {
const userFirst = createUser(1, 'First');
const userSecond = createUser(2, 'Second');

const items = [{
timestamp: new Date('05.01.2024'),
author: userFirst,
text: 'AAA',
}, {
timestamp: new Date('06.01.2024'),
author: userFirst,
text: 'BBB',
}, {
timestamp: new Date('06.01.2024'),
author: userSecond,
text: 'CCC',
}, {
timestamp: new Date('06.01.2024'),
author: userSecond,
text: 'DDD',
}, {
timestamp: new Date('10.01.2024'),
author: userFirst,
text: 'EEE',
}];

return createWidget('dxChat', {
items,
user: userSecond,
width: 400,
height: 600,
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@
border-radius: 999em;
}

.dx-chat-messagelist-date-header {
text-align: center;
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
@use "sass:math";

@mixin chat-messagelist($padding) {
@mixin chat-messagelist(
$padding,
$date-header-color,
) {
.dx-chat-messagelist {
.dx-scrollable-content {
padding-inline: $padding;
}
}

.dx-chat-messagelist-date-header {
padding: $padding;
color: $date-header-color;
}
}

@mixin chat-messagelist-empty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@
$chat-messagelist-empty-prompt-font-size,
$chat-messagelist-empty-prompt-color,
);
@include chat-messagelist($chat-messagelist-padding);
@include chat-messagelist(
$chat-messagelist-padding,
$chat-messagelist-empty-icon-color,
);
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@
$chat-messagelist-empty-prompt-font-size,
$chat-messagelist-empty-prompt-color,
);
@include chat-messagelist($chat-messagelist-padding);
@include chat-messagelist(
$chat-messagelist-padding,
$chat-messagelist-empty-icon-color,
);
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@
$chat-messagelist-empty-prompt-font-size,
$chat-messagelist-empty-prompt-color,
);
@include chat-messagelist($chat-messagelist-padding);
@include chat-messagelist(
$chat-messagelist-padding,
$chat-messagelist-empty-icon-color,
);
70 changes: 63 additions & 7 deletions packages/devextreme/js/__internal/ui/chat/messagelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Guid from '@js/core/guid';
import type { dxElementWrapper } from '@js/core/renderer';
import $ from '@js/core/renderer';
import resizeObserverSingleton from '@js/core/resize_observer';
import dateUtils from '@js/core/utils/date';
import dateSerialization from '@js/core/utils/date_serialization';
import { isElementInDom } from '@js/core/utils/dom';
import { isDefined } from '@js/core/utils/type';
Expand All @@ -23,6 +24,7 @@ const CHAT_MESSAGELIST_EMPTY_VIEW_CLASS = 'dx-chat-messagelist-empty-view';
const CHAT_MESSAGELIST_EMPTY_IMAGE_CLASS = 'dx-chat-messagelist-empty-image';
const CHAT_MESSAGELIST_EMPTY_MESSAGE_CLASS = 'dx-chat-messagelist-empty-message';
const CHAT_MESSAGELIST_EMPTY_PROMPT_CLASS = 'dx-chat-messagelist-empty-prompt';
const CHAT_MESSAGELIST_DATE_HEADER_CLASS = 'dx-chat-messagelist-date-header';

const SCROLLABLE_CONTAINER_CLASS = 'dx-scrollable-container';
export const MESSAGEGROUP_TIMEOUT = 5 * 1000 * 60;
Expand All @@ -35,6 +37,8 @@ export interface Properties extends WidgetOptions<MessageList> {
class MessageList extends Widget<Properties> {
private _messageGroups?: MessageGroup[];

private _messageDate?: null | string | number | Date;

private _containerClientHeight!: number;

private _scrollable!: Scrollable<unknown>;
Expand All @@ -51,6 +55,7 @@ class MessageList extends Widget<Properties> {
super._init();

this._messageGroups = [];
this._messageDate = null;
}

_initMarkup(): void {
Expand Down Expand Up @@ -169,6 +174,46 @@ class MessageList extends Widget<Properties> {
});
}

_shouldAddDateHeader(timestamp: undefined | string | number | Date): boolean {
if (timestamp === null) {
return false;
}

const deserializedDate = dateSerialization.deserializeDate(timestamp);

return !dateUtils.sameDate(this._messageDate, deserializedDate);
}

_createMessageDateHeader(timestamp: string | number | Date | undefined): void {
if (timestamp === undefined) {
return;
}

const deserializedDate = dateSerialization.deserializeDate(timestamp);
const today = new Date();
const yesterday = new Date(new Date().setDate(today.getDate() - 1));
this._messageDate = deserializedDate;

let headerDate = deserializedDate.toLocaleDateString(undefined, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
}).replace(/[/-]/g, '.');

if (dateUtils.sameDate(deserializedDate, today)) {
headerDate = `Today ${headerDate}`;
}

if (dateUtils.sameDate(deserializedDate, yesterday)) {
headerDate = `Yesterday ${headerDate}`;
}

$('<div>')
.addClass(CHAT_MESSAGELIST_DATE_HEADER_CLASS)
.text(headerDate)
.appendTo(this._$content());
}

_renderMessageListContent(): void {
if (this._isEmpty()) {
this._renderEmptyViewContent();
Expand All @@ -184,20 +229,27 @@ class MessageList extends Widget<Properties> {
items.forEach((item, index) => {
const newMessageGroupItem = item ?? {};
const id = newMessageGroupItem.author?.id;

const shouldCreateDateHeader = this._shouldAddDateHeader(item?.timestamp);
const isTimeoutExceeded = this._isTimeoutExceeded(
currentMessageGroupItems[currentMessageGroupItems.length - 1] ?? {},
item,
);
const shouldCreateMessageGroup = (shouldCreateDateHeader && currentMessageGroupItems.length)
|| isTimeoutExceeded
|| id !== currentMessageGroupUserId;

if (id === currentMessageGroupUserId && !isTimeoutExceeded) {
currentMessageGroupItems.push(newMessageGroupItem);
} else {
if (shouldCreateMessageGroup) {
this._createMessageGroupComponent(currentMessageGroupItems, currentMessageGroupUserId);

currentMessageGroupUserId = id;
currentMessageGroupItems = [];
currentMessageGroupItems.push(newMessageGroupItem);
} else {
currentMessageGroupItems.push(newMessageGroupItem);
}

if (shouldCreateDateHeader) {
this._createMessageDateHeader(item?.timestamp);
}

if (items.length - 1 === index) {
Expand All @@ -207,18 +259,22 @@ class MessageList extends Widget<Properties> {
}

_renderMessage(message: Message): void {
const { author } = message;
const { author, timestamp } = message;

const lastMessageGroup = this._messageGroups?.[this._messageGroups.length - 1];

if (lastMessageGroup) {
const { items } = lastMessageGroup.option();
const lastMessageGroupItem = items[items.length - 1];
const lastMessageGroupUserId = lastMessageGroupItem.author?.id;

const shouldCreateDateHeader = this._shouldAddDateHeader(timestamp);
const isTimeoutExceeded = this._isTimeoutExceeded(lastMessageGroupItem, message);

if (author?.id === lastMessageGroupUserId && !isTimeoutExceeded) {
if (shouldCreateDateHeader) {
this._createMessageDateHeader(timestamp);
}

if (author?.id === lastMessageGroupUserId && !isTimeoutExceeded && !shouldCreateDateHeader) {
lastMessageGroup.renderMessage(message);
this._scrollContentToLastMessage();

Expand Down

0 comments on commit 62b8f1d

Please sign in to comment.