Skip to content

Commit 2c2180e

Browse files
Amin Mahboubivini-btc
andauthored
Feature/new messages indicator (#548)
* DateSeparator supports unread prop * show New indicator in MessageList for unread msgs * check for undefined lastRead Co-authored-by: Vinícius Andrade <[email protected]> Co-authored-by: Vinícius Andrade <[email protected]>
1 parent 75a5dfb commit 2c2180e

File tree

12 files changed

+94
-14
lines changed

12 files changed

+94
-14
lines changed

src/components/DateSeparator/DateSeparator.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,21 @@ import { TranslationContext } from '../../context';
1010
* @example ../../docs/DateSeparator.md
1111
* @type {React.FC<import('types').DateSeparatorProps>}
1212
*/
13-
const DateSeparator = ({ position = 'right', formatDate, date }) => {
14-
const { tDateTimeParser } = useContext(TranslationContext);
13+
const DateSeparator = ({ position = 'right', formatDate, date, unread }) => {
14+
const { t, tDateTimeParser } = useContext(TranslationContext);
1515
if (typeof date === 'string') return null;
1616

17+
const formattedDate = formatDate
18+
? formatDate(date)
19+
: tDateTimeParser(date.toISOString()).calendar();
20+
1721
return (
1822
<div className="str-chat__date-separator">
1923
{(position === 'right' || position === 'center') && (
2024
<hr className="str-chat__date-separator-line" />
2125
)}
2226
<div className="str-chat__date-separator-date">
23-
{formatDate
24-
? formatDate(date)
25-
: tDateTimeParser(date.toISOString()).calendar()}
27+
{unread ? t('New') : formattedDate}
2628
</div>
2729
{(position === 'left' || position === 'center') && (
2830
<hr className="str-chat__date-separator-line" />
@@ -34,6 +36,8 @@ const DateSeparator = ({ position = 'right', formatDate, date }) => {
3436
DateSeparator.propTypes = {
3537
/** The date to format */
3638
date: PropTypes.instanceOf(Date).isRequired,
39+
/** If following messages are not new */
40+
unread: PropTypes.bool,
3741
/** Set the position of the date in the separator */
3842
position: PropTypes.oneOf(['left', 'center', 'right']),
3943
/** Override the default formatting of the date. This is a function that has access to the original date object. Returns a string or Node */

src/components/DateSeparator/__tests__/DateSeparator.test.js

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
11
import React from 'react';
22
import renderer from 'react-test-renderer';
3+
import Dayjs from 'dayjs';
4+
import calendar from 'dayjs/plugin/calendar';
35
import { cleanup, render } from '@testing-library/react';
46
import '@testing-library/jest-dom';
57

68
import DateSeparator from '../DateSeparator';
9+
import { TranslationContext } from '../../../context';
10+
11+
Dayjs.extend(calendar);
712

813
afterEach(cleanup); // eslint-disable-line
914

1015
// this changes every time tests are run,
1116
// but by mocking the actual renderers tests are still deterministic
1217
const now = new Date();
1318

19+
const withContext = (props) => {
20+
const t = jest.fn((key) => key);
21+
const tDateTimeParser = jest.fn((input) => Dayjs(input));
22+
const Component = (
23+
<TranslationContext.Provider value={{ t, tDateTimeParser }}>
24+
<DateSeparator {...props} />
25+
</TranslationContext.Provider>
26+
);
27+
28+
return { Component, t, tDateTimeParser };
29+
};
30+
1431
describe('DateSeparator', () => {
1532
it('should use formatDate if it is provided', () => {
1633
const { queryByText } = render(
@@ -20,9 +37,42 @@ describe('DateSeparator', () => {
2037
expect(queryByText('the date')).toBeInTheDocument();
2138
});
2239

23-
it.todo(
24-
"should use tDateTimeParser's calendar method to format dates if formatDate prop is not specified",
25-
);
40+
it('should render New text if unread prop is true', () => {
41+
const { Component, t } = withContext({ date: now, unread: true });
42+
const { queryByText } = render(Component);
43+
44+
expect(queryByText('New')).toBeInTheDocument();
45+
expect(t).toHaveBeenCalledWith('New');
46+
});
47+
48+
it('should render properly for unread', () => {
49+
const { Component } = withContext({ date: now, unread: true });
50+
const tree = renderer.create(Component).toJSON();
51+
expect(tree).toMatchInlineSnapshot(`
52+
<div
53+
className="str-chat__date-separator"
54+
>
55+
<hr
56+
className="str-chat__date-separator-line"
57+
/>
58+
<div
59+
className="str-chat__date-separator-date"
60+
>
61+
New
62+
</div>
63+
</div>
64+
`);
65+
});
66+
67+
it("should use tDateTimeParser's calendar method by default", () => {
68+
const { Component, tDateTimeParser } = withContext({ date: now });
69+
const { queryByText } = render(Component);
70+
71+
expect(tDateTimeParser).toHaveBeenCalledWith(now.toISOString());
72+
expect(
73+
queryByText(Dayjs(now.toISOString()).calendar()),
74+
).toBeInTheDocument();
75+
});
2676

2777
describe('Position prop', () => {
2878
const renderWithPosition = (position) => (

src/components/MessageList/MessageList.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ class MessageList extends PureComponent {
238238
noGroupByUser={this.props.noGroupByUser}
239239
threadList={this.props.threadList}
240240
client={this.props.client}
241+
channel={this.props.channel}
241242
read={this.props.read}
242243
bottomRef={this.bottomRef}
243244
onMessageLoadCaptured={this.onMessageLoadCaptured}

src/components/MessageList/MessageListInner.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ const getReadStates = (messages, read) => {
3535
return readData;
3636
};
3737

38-
const insertDates = (messages) => {
38+
const insertDates = (messages, lastRead, userID) => {
39+
let unread = false;
3940
const newMessages = [];
4041
for (let i = 0, l = messages.length; i < l; i += 1) {
4142
const message = messages[i];
@@ -49,7 +50,21 @@ const insertDates = (messages) => {
4950
prevMessageDate = messages[i - 1].created_at.toDateString();
5051
}
5152

52-
if (i === 0 || messageDate !== prevMessageDate) {
53+
if (!unread) {
54+
unread = lastRead && lastRead.getTime() < message.created_at.getTime();
55+
// userId check makes sure New is not shown for current user messages
56+
if (unread && message.user.id !== userID)
57+
newMessages.push({
58+
type: 'message.date',
59+
date: message.created_at,
60+
unread,
61+
});
62+
}
63+
64+
if (
65+
(i === 0 || messageDate !== prevMessageDate) &&
66+
newMessages?.[newMessages.length - 1]?.type !== 'message.date' // prevent two subsequent DateSeparator
67+
) {
5368
newMessages.push(
5469
{ type: 'message.date', date: message.created_at },
5570
message,
@@ -173,17 +188,18 @@ const MessageListInner = (props) => {
173188
noGroupByUser,
174189
client,
175190
threadList,
191+
channel,
176192
read,
177193
internalMessageProps,
178194
internalInfiniteScrollProps,
179195
} = props;
196+
const lastRead = useMemo(() => channel.lastRead(), [channel]);
180197

181198
const enrichedMessages = useMemo(() => {
182-
const messageWithDates = insertDates(messages);
183-
// messageWithDates.sort((a, b) => a.created_at - b.created_at); // TODO: remove if no issue came up
199+
const messageWithDates = insertDates(messages, lastRead, client.userID);
184200
if (HeaderComponent) return insertIntro(messageWithDates, headerPosition);
185201
return messageWithDates;
186-
}, [HeaderComponent, headerPosition, messages]);
202+
}, [messages, lastRead, client.userID, HeaderComponent, headerPosition]);
187203

188204
const messageGroupStyles = useMemo(
189205
() =>
@@ -221,7 +237,7 @@ const MessageListInner = (props) => {
221237

222238
return (
223239
<li key={`${message.date.toISOString()}-i`}>
224-
<DateSeparator date={message.date} />
240+
<DateSeparator date={message.date} unread={message.unread} />
225241
</li>
226242
);
227243
}

src/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"Message failed. Click to try again.": "Message failed. Click to try again.",
2222
"Message has been successfully flagged": "Message has been successfully flagged",
2323
"Mute": "Mute",
24+
"New": "New",
2425
"New Messages!": "New Messages!",
2526
"Nothing yet...": "Nothing yet...",
2627
"Only visible to you": "Only visible to you",

src/i18n/fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"Message failed. Click to try again.": "Échec de l'envoi du message - Cliquez pour réessayer",
2222
"Message has been successfully flagged": "Le message a été signalé avec succès",
2323
"Mute": "Muet",
24+
"New": "Nouveaux",
2425
"New Messages!": "Nouveaux Messages!",
2526
"Nothing yet...": "Aucun message...",
2627
"Only visible to you": "Visible uniquement pour vous",

src/i18n/hi.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"Message failed. Click to try again.": "मैसेज फ़ैल - पुनः कोशिश करें",
2222
"Message has been successfully flagged": "मैसेज को फ्लैग कर दिया गया है",
2323
"Mute": "म्यूट करे",
24+
"New": "नए",
2425
"New Messages!": "नए मैसेज!",
2526
"Nothing yet...": "कोई मैसेज नहीं है",
2627
"Only visible to you": "सिर्फ आपको दिखाई दे रहा है",

src/i18n/it.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"Message failed. Click to try again.": "Invio messaggio fallito. Clicca per riprovare.",
2222
"Message has been successfully flagged": "Il messaggio é stato segnalato con successo",
2323
"Mute": "Silenzia",
24+
"New": "Nuovo",
2425
"New Messages!": "Nuovo messaggio!",
2526
"Nothing yet...": "Ancora niente...",
2627
"Only visible to you": "Visibile soltanto da te",

src/i18n/nl.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"Message failed. Click to try again.": "Bericht mislukt, klik om het nogmaals te proberen",
2222
"Message has been successfully flagged": "Bericht is succesvol gemarkeerd",
2323
"Mute": "Mute",
24+
"New": "Nieuwe",
2425
"New Messages!": "Nieuwe Berichten!",
2526
"Nothing yet...": "Nog niets ...",
2627
"Only visible to you": "Alleen zichtbaar voor jou",

src/i18n/ru.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"Message failed. Click to try again.": "Ошибка отправки сообщения · Нажмите чтобы повторить",
2222
"Message has been successfully flagged": "Жалоба на сообщение была принята",
2323
"Mute": "Отключить уведомления",
24+
"New": "Новые",
2425
"New Messages!": "Новые сообщения!",
2526
"Nothing yet...": "Пока ничего нет...",
2627
"Only visible to you": "Только видно для вас",

0 commit comments

Comments
 (0)