Group messages by status, grouping everything delivered+ together
This commit is contained in:
parent
356f123092
commit
e8e18ff7e4
2 changed files with 73 additions and 6 deletions
|
@ -5,6 +5,7 @@ import { assert } from 'chai';
|
||||||
import { times } from 'lodash';
|
import { times } from 'lodash';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { MINUTE, SECOND } from '../../util/durations';
|
import { MINUTE, SECOND } from '../../util/durations';
|
||||||
|
import type { MaybeMessageTimelineItemType } from '../../util/timelineUtil';
|
||||||
import {
|
import {
|
||||||
ScrollAnchor,
|
ScrollAnchor,
|
||||||
areMessagesInSameGroup,
|
areMessagesInSameGroup,
|
||||||
|
@ -14,18 +15,20 @@ import {
|
||||||
|
|
||||||
describe('<Timeline> utilities', () => {
|
describe('<Timeline> utilities', () => {
|
||||||
describe('areMessagesInSameGroup', () => {
|
describe('areMessagesInSameGroup', () => {
|
||||||
const defaultNewer = {
|
const defaultNewer: MaybeMessageTimelineItemType = {
|
||||||
type: 'message' as const,
|
type: 'message' as const,
|
||||||
data: {
|
data: {
|
||||||
author: { id: uuid() },
|
author: { id: uuid() },
|
||||||
timestamp: new Date(1998, 10, 21, 12, 34, 56, 123).valueOf(),
|
timestamp: new Date(1998, 10, 21, 12, 34, 56, 123).valueOf(),
|
||||||
|
status: 'delivered',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const defaultOlder = {
|
const defaultOlder: MaybeMessageTimelineItemType = {
|
||||||
...defaultNewer,
|
...defaultNewer,
|
||||||
data: {
|
data: {
|
||||||
...defaultNewer.data,
|
...defaultNewer.data,
|
||||||
timestamp: defaultNewer.data.timestamp - MINUTE,
|
timestamp: defaultNewer.data.timestamp - MINUTE,
|
||||||
|
status: 'delivered',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -115,7 +118,56 @@ describe('<Timeline> utilities', () => {
|
||||||
assert.isFalse(areMessagesInSameGroup(defaultOlder, true, defaultNewer));
|
assert.isFalse(areMessagesInSameGroup(defaultOlder, true, defaultNewer));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if the everything above works out', () => {
|
it("returns false if they don't have matching sent status (and not delivered)", () => {
|
||||||
|
const older = {
|
||||||
|
...defaultOlder,
|
||||||
|
data: { ...defaultOlder.data, status: 'sent' as const },
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.isFalse(areMessagesInSameGroup(older, false, defaultNewer));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns false if newer is deletedForEveryone and older isn't", () => {
|
||||||
|
const newer = {
|
||||||
|
...defaultNewer,
|
||||||
|
data: { ...defaultNewer.data, deletedForEveryone: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.isFalse(areMessagesInSameGroup(defaultOlder, false, newer));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns true if older is deletedForEveryone and newer isn't", () => {
|
||||||
|
const older = {
|
||||||
|
...defaultOlder,
|
||||||
|
data: { ...defaultOlder.data, deletedForEveryone: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.isTrue(areMessagesInSameGroup(older, false, defaultNewer));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if both are deletedForEveryone', () => {
|
||||||
|
const older = {
|
||||||
|
...defaultOlder,
|
||||||
|
data: { ...defaultOlder.data, deletedForEveryone: true },
|
||||||
|
};
|
||||||
|
const newer = {
|
||||||
|
...defaultNewer,
|
||||||
|
data: { ...defaultNewer.data, deletedForEveryone: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.isTrue(areMessagesInSameGroup(older, false, newer));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if they have delivered status or above', () => {
|
||||||
|
const older = {
|
||||||
|
...defaultOlder,
|
||||||
|
data: { ...defaultOlder.data, status: 'read' as const },
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.isTrue(areMessagesInSameGroup(older, false, defaultNewer));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if everything above works out', () => {
|
||||||
assert.isTrue(areMessagesInSameGroup(defaultOlder, false, defaultNewer));
|
assert.isTrue(areMessagesInSameGroup(defaultOlder, false, defaultNewer));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { WidthBreakpoint } from '../components/_util';
|
||||||
import { MINUTE } from './durations';
|
import { MINUTE } from './durations';
|
||||||
import { missingCaseError } from './missingCaseError';
|
import { missingCaseError } from './missingCaseError';
|
||||||
import { isSameDay } from './timestamp';
|
import { isSameDay } from './timestamp';
|
||||||
|
import type { LastMessageStatus } from '../model-types.d';
|
||||||
|
|
||||||
const COLLAPSE_WITHIN = 3 * MINUTE;
|
const COLLAPSE_WITHIN = 3 * MINUTE;
|
||||||
|
|
||||||
|
@ -32,15 +33,17 @@ export enum UnreadIndicatorPlacement {
|
||||||
JustBelow,
|
JustBelow,
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageTimelineItemDataType = Readonly<{
|
export type MessageTimelineItemDataType = Readonly<{
|
||||||
author: { id: string };
|
author: { id: string };
|
||||||
|
deletedForEveryone?: boolean;
|
||||||
reactions?: ReadonlyArray<unknown>;
|
reactions?: ReadonlyArray<unknown>;
|
||||||
|
status?: LastMessageStatus;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// This lets us avoid passing a full `MessageType`. That's useful for tests and for
|
// This lets us avoid passing a full `MessageType`. That's useful for tests and for
|
||||||
// documentation.
|
// documentation.
|
||||||
type MaybeMessageTimelineItemType = Readonly<
|
export type MaybeMessageTimelineItemType = Readonly<
|
||||||
| undefined
|
| undefined
|
||||||
| TimelineItemType
|
| TimelineItemType
|
||||||
| { type: 'message'; data: MessageTimelineItemDataType }
|
| { type: 'message'; data: MessageTimelineItemDataType }
|
||||||
|
@ -51,6 +54,10 @@ const getMessageTimelineItemData = (
|
||||||
): undefined | MessageTimelineItemDataType =>
|
): undefined | MessageTimelineItemDataType =>
|
||||||
timelineItem?.type === 'message' ? timelineItem.data : undefined;
|
timelineItem?.type === 'message' ? timelineItem.data : undefined;
|
||||||
|
|
||||||
|
function isDelivered(status?: LastMessageStatus) {
|
||||||
|
return status === 'delivered' || status === 'read' || status === 'viewed';
|
||||||
|
}
|
||||||
|
|
||||||
export function areMessagesInSameGroup(
|
export function areMessagesInSameGroup(
|
||||||
olderTimelineItem: MaybeMessageTimelineItemType,
|
olderTimelineItem: MaybeMessageTimelineItemType,
|
||||||
unreadIndicator: boolean,
|
unreadIndicator: boolean,
|
||||||
|
@ -70,12 +77,20 @@ export function areMessagesInSameGroup(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We definitely don't want to group if we transition from non-deleted to deleted, since
|
||||||
|
// deleted messages don't show status.
|
||||||
|
if (newerMessage.deletedForEveryone && !olderMessage.deletedForEveryone) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return Boolean(
|
return Boolean(
|
||||||
!olderMessage.reactions?.length &&
|
!olderMessage.reactions?.length &&
|
||||||
olderMessage.author.id === newerMessage.author.id &&
|
olderMessage.author.id === newerMessage.author.id &&
|
||||||
newerMessage.timestamp >= olderMessage.timestamp &&
|
newerMessage.timestamp >= olderMessage.timestamp &&
|
||||||
newerMessage.timestamp - olderMessage.timestamp < COLLAPSE_WITHIN &&
|
newerMessage.timestamp - olderMessage.timestamp < COLLAPSE_WITHIN &&
|
||||||
isSameDay(olderMessage.timestamp, newerMessage.timestamp)
|
isSameDay(olderMessage.timestamp, newerMessage.timestamp) &&
|
||||||
|
(olderMessage.status === newerMessage.status ||
|
||||||
|
(isDelivered(newerMessage.status) && isDelivered(olderMessage.status)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue