Various fixes for message forwarding
This commit is contained in:
parent
3face767aa
commit
353becffac
8 changed files with 222 additions and 44 deletions
|
@ -27,7 +27,7 @@ import { BodyRangeType, LocalizerType } from '../types/Util';
|
||||||
import { ModalHost } from './ModalHost';
|
import { ModalHost } from './ModalHost';
|
||||||
import { StagedLinkPreview } from './conversation/StagedLinkPreview';
|
import { StagedLinkPreview } from './conversation/StagedLinkPreview';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
import { filterAndSortConversations } from '../util/filterAndSortConversations';
|
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
||||||
|
|
||||||
export type DataPropsType = {
|
export type DataPropsType = {
|
||||||
attachments?: Array<AttachmentType>;
|
attachments?: Array<AttachmentType>;
|
||||||
|
@ -85,8 +85,8 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
Array<ConversationType>
|
Array<ConversationType>
|
||||||
>([]);
|
>([]);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [filteredContacts, setFilteredContacts] = useState(
|
const [filteredConversations, setFilteredConversations] = useState(
|
||||||
filterAndSortConversations(candidateConversations, '')
|
filterAndSortConversationsByRecent(candidateConversations, '')
|
||||||
);
|
);
|
||||||
const [attachmentsToForward, setAttachmentsToForward] = useState(attachments);
|
const [attachmentsToForward, setAttachmentsToForward] = useState(attachments);
|
||||||
const [isEditingMessage, setIsEditingMessage] = useState(false);
|
const [isEditingMessage, setIsEditingMessage] = useState(false);
|
||||||
|
@ -148,14 +148,17 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
const normalizedSearchTerm = searchTerm.trim();
|
const normalizedSearchTerm = searchTerm.trim();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
setFilteredContacts(
|
setFilteredConversations(
|
||||||
filterAndSortConversations(candidateConversations, normalizedSearchTerm)
|
filterAndSortConversationsByRecent(
|
||||||
|
candidateConversations,
|
||||||
|
normalizedSearchTerm
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}, 200);
|
}, 200);
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
};
|
};
|
||||||
}, [candidateConversations, normalizedSearchTerm, setFilteredContacts]);
|
}, [candidateConversations, normalizedSearchTerm, setFilteredConversations]);
|
||||||
|
|
||||||
const contactLookup = useMemo(() => {
|
const contactLookup = useMemo(() => {
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
|
@ -165,7 +168,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
return map;
|
return map;
|
||||||
}, [candidateConversations]);
|
}, [candidateConversations]);
|
||||||
|
|
||||||
const toggleSelectedContact = useCallback(
|
const toggleSelectedConversation = useCallback(
|
||||||
(conversationId: string) => {
|
(conversationId: string) => {
|
||||||
let removeContact = false;
|
let removeContact = false;
|
||||||
const nextSelectedContacts = selectedContacts.filter(contact => {
|
const nextSelectedContacts = selectedContacts.filter(contact => {
|
||||||
|
@ -187,9 +190,17 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
[contactLookup, selectedContacts, setSelectedContacts]
|
[contactLookup, selectedContacts, setSelectedContacts]
|
||||||
);
|
);
|
||||||
|
|
||||||
const rowCount = filteredContacts.length;
|
const handleBackOrClose = useCallback(() => {
|
||||||
|
if (isEditingMessage) {
|
||||||
|
setIsEditingMessage(false);
|
||||||
|
} else {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}, [isEditingMessage, onClose, setIsEditingMessage]);
|
||||||
|
|
||||||
|
const rowCount = filteredConversations.length;
|
||||||
const getRow = (index: number): undefined | Row => {
|
const getRow = (index: number): undefined | Row => {
|
||||||
const contact = filteredContacts[index];
|
const contact = filteredConversations[index];
|
||||||
if (!contact) {
|
if (!contact) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -209,8 +220,18 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalHost onClose={onClose}>
|
<ModalHost onEscape={handleBackOrClose} onClose={onClose}>
|
||||||
<div className="module-ForwardMessageModal">
|
<div className="module-ForwardMessageModal">
|
||||||
<div
|
<div
|
||||||
className={classNames('module-ForwardMessageModal__header', {
|
className={classNames('module-ForwardMessageModal__header', {
|
||||||
|
@ -328,11 +349,6 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
<div
|
<div
|
||||||
className="module-ForwardMessageModal__list-wrapper"
|
className="module-ForwardMessageModal__list-wrapper"
|
||||||
ref={measureRef}
|
ref={measureRef}
|
||||||
onKeyDown={event => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
inputRef.current?.focus();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ConversationList
|
<ConversationList
|
||||||
dimensions={contentRect.bounds}
|
dimensions={contentRect.bounds}
|
||||||
|
@ -349,7 +365,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
|
||||||
disabledReason !==
|
disabledReason !==
|
||||||
ContactCheckboxDisabledReason.MaximumContactsSelected
|
ContactCheckboxDisabledReason.MaximumContactsSelected
|
||||||
) {
|
) {
|
||||||
toggleSelectedContact(conversationId);
|
toggleSelectedConversation(conversationId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onSelectConversation={shouldNeverBeCalled}
|
onSelectConversation={shouldNeverBeCalled}
|
||||||
|
|
|
@ -7,13 +7,14 @@ import { createPortal } from 'react-dom';
|
||||||
import { Theme, themeClassName } from '../util/theme';
|
import { Theme, themeClassName } from '../util/theme';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
|
readonly onEscape?: () => unknown;
|
||||||
readonly onClose: () => unknown;
|
readonly onClose: () => unknown;
|
||||||
readonly children: React.ReactElement;
|
readonly children: React.ReactElement;
|
||||||
readonly theme?: Theme;
|
readonly theme?: Theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModalHost = React.memo(
|
export const ModalHost = React.memo(
|
||||||
({ onClose, children, theme }: PropsType) => {
|
({ onEscape, onClose, children, theme }: PropsType) => {
|
||||||
const [root, setRoot] = React.useState<HTMLElement | null>(null);
|
const [root, setRoot] = React.useState<HTMLElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -30,7 +31,11 @@ export const ModalHost = React.memo(
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (event: KeyboardEvent) => {
|
const handler = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
onClose();
|
if (onEscape) {
|
||||||
|
onEscape();
|
||||||
|
} else {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -41,7 +46,7 @@ export const ModalHost = React.memo(
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handler);
|
document.removeEventListener('keydown', handler);
|
||||||
};
|
};
|
||||||
}, [onClose]);
|
}, [onEscape, onClose]);
|
||||||
|
|
||||||
// This makes it easier to write dialogs to be hosted here; they won't have to worry
|
// This makes it easier to write dialogs to be hosted here; they won't have to worry
|
||||||
// as much about preventing propagation of mouse events.
|
// as much about preventing propagation of mouse events.
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { LocalizerType } from '../../../../types/Util';
|
||||||
import { assert } from '../../../../util/assert';
|
import { assert } from '../../../../util/assert';
|
||||||
import { getOwn } from '../../../../util/getOwn';
|
import { getOwn } from '../../../../util/getOwn';
|
||||||
import { missingCaseError } from '../../../../util/missingCaseError';
|
import { missingCaseError } from '../../../../util/missingCaseError';
|
||||||
import { filterAndSortConversations } from '../../../../util/filterAndSortConversations';
|
import { filterAndSortConversationsByTitle } from '../../../../util/filterAndSortConversations';
|
||||||
import { ConversationType } from '../../../../state/ducks/conversations';
|
import { ConversationType } from '../../../../state/ducks/conversations';
|
||||||
import { ModalHost } from '../../../ModalHost';
|
import { ModalHost } from '../../../ModalHost';
|
||||||
import { ContactPills } from '../../../ContactPills';
|
import { ContactPills } from '../../../ContactPills';
|
||||||
|
@ -72,13 +72,16 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
|
||||||
const canContinue = Boolean(selectedContacts.length);
|
const canContinue = Boolean(selectedContacts.length);
|
||||||
|
|
||||||
const [filteredContacts, setFilteredContacts] = useState(
|
const [filteredContacts, setFilteredContacts] = useState(
|
||||||
filterAndSortConversations(candidateContacts, '')
|
filterAndSortConversationsByTitle(candidateContacts, '')
|
||||||
);
|
);
|
||||||
const normalizedSearchTerm = searchTerm.trim();
|
const normalizedSearchTerm = searchTerm.trim();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
setFilteredContacts(
|
setFilteredContacts(
|
||||||
filterAndSortConversations(candidateContacts, normalizedSearchTerm)
|
filterAndSortConversationsByTitle(
|
||||||
|
candidateContacts,
|
||||||
|
normalizedSearchTerm
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}, 200);
|
}, 200);
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -3324,6 +3324,10 @@ export class ConversationModel extends window.Backbone.Model<
|
||||||
mentions?: BodyRangesType,
|
mentions?: BodyRangesType,
|
||||||
{ dontClearDraft = false } = {}
|
{ dontClearDraft = false } = {}
|
||||||
): void {
|
): void {
|
||||||
|
if (this.isGroupV1AndDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.clearTypingTimers();
|
this.clearTypingTimers();
|
||||||
|
|
||||||
const { clearUnreadMetrics } = window.reduxActions.conversations;
|
const { clearUnreadMetrics } = window.reduxActions.conversations;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { PropsDataType as TimelinePropsType } from '../../components/conversatio
|
||||||
import { TimelineItemType } from '../../components/conversation/TimelineItem';
|
import { TimelineItemType } from '../../components/conversation/TimelineItem';
|
||||||
import { assert } from '../../util/assert';
|
import { assert } from '../../util/assert';
|
||||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||||
import { filterAndSortConversations } from '../../util/filterAndSortConversations';
|
import { filterAndSortConversationsByTitle } from '../../util/filterAndSortConversations';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getInteractionMode,
|
getInteractionMode,
|
||||||
|
@ -353,10 +353,13 @@ export const getAllComposableConversations = createSelector(
|
||||||
getConversationLookup,
|
getConversationLookup,
|
||||||
(conversationLookup: ConversationLookupType): Array<ConversationType> =>
|
(conversationLookup: ConversationLookupType): Array<ConversationType> =>
|
||||||
Object.values(conversationLookup).filter(
|
Object.values(conversationLookup).filter(
|
||||||
contact =>
|
conversation =>
|
||||||
!contact.isBlocked &&
|
!conversation.isBlocked &&
|
||||||
!isConversationUnregistered(contact) &&
|
!conversation.isGroupV1AndDisabled &&
|
||||||
(isString(contact.name) || contact.profileSharing)
|
!isConversationUnregistered(conversation) &&
|
||||||
|
// All conversation should have a title except in weird cases where
|
||||||
|
// they don't, in that case we don't want to show these for Forwarding.
|
||||||
|
conversation.title
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -410,7 +413,7 @@ export const getComposeContacts = createSelector(
|
||||||
searchTerm: string,
|
searchTerm: string,
|
||||||
contacts: Array<ConversationType>
|
contacts: Array<ConversationType>
|
||||||
): Array<ConversationType> => {
|
): Array<ConversationType> => {
|
||||||
return filterAndSortConversations(contacts, searchTerm);
|
return filterAndSortConversationsByTitle(contacts, searchTerm);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -421,14 +424,14 @@ export const getComposeGroups = createSelector(
|
||||||
searchTerm: string,
|
searchTerm: string,
|
||||||
groups: Array<ConversationType>
|
groups: Array<ConversationType>
|
||||||
): Array<ConversationType> => {
|
): Array<ConversationType> => {
|
||||||
return filterAndSortConversations(groups, searchTerm);
|
return filterAndSortConversationsByTitle(groups, searchTerm);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getCandidateContactsForNewGroup = createSelector(
|
export const getCandidateContactsForNewGroup = createSelector(
|
||||||
getComposableContacts,
|
getComposableContacts,
|
||||||
getNormalizedComposerConversationSearchTerm,
|
getNormalizedComposerConversationSearchTerm,
|
||||||
filterAndSortConversations
|
filterAndSortConversationsByTitle
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getCantAddContactForModal = createSelector(
|
export const getCantAddContactForModal = createSelector(
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
import {
|
import {
|
||||||
_getConversationComparator,
|
_getConversationComparator,
|
||||||
_getLeftPaneLists,
|
_getLeftPaneLists,
|
||||||
|
getAllComposableConversations,
|
||||||
getCandidateContactsForNewGroup,
|
getCandidateContactsForNewGroup,
|
||||||
getCantAddContactForModal,
|
getCantAddContactForModal,
|
||||||
getComposeContacts,
|
getComposeContacts,
|
||||||
|
@ -450,6 +451,85 @@ describe('both/state/selectors/conversations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getAllComposableConversations', () => {
|
||||||
|
const getRootState = (): StateType => {
|
||||||
|
const rootState = getEmptyRootState();
|
||||||
|
return {
|
||||||
|
...rootState,
|
||||||
|
conversations: {
|
||||||
|
...getEmptyState(),
|
||||||
|
conversationLookup: {
|
||||||
|
'our-conversation-id': {
|
||||||
|
...getDefaultConversation('our-conversation-id'),
|
||||||
|
isMe: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
...rootState.user,
|
||||||
|
ourConversationId: 'our-conversation-id',
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRootStateWithConversations = (): StateType => {
|
||||||
|
const result = getRootState();
|
||||||
|
Object.assign(result.conversations.conversationLookup, {
|
||||||
|
'convo-1': {
|
||||||
|
...getDefaultConversation('convo-1'),
|
||||||
|
title: 'A',
|
||||||
|
},
|
||||||
|
'convo-2': {
|
||||||
|
...getDefaultConversation('convo-2'),
|
||||||
|
type: 'group',
|
||||||
|
isGroupV1AndDisabled: true,
|
||||||
|
title: 'Should Be Dropped (GV1)',
|
||||||
|
},
|
||||||
|
'convo-3': {
|
||||||
|
...getDefaultConversation('convo-3'),
|
||||||
|
type: 'group',
|
||||||
|
title: 'B',
|
||||||
|
},
|
||||||
|
'convo-4': {
|
||||||
|
...getDefaultConversation('convo-4'),
|
||||||
|
isBlocked: true,
|
||||||
|
title: 'Should Be Dropped (blocked)',
|
||||||
|
},
|
||||||
|
'convo-5': {
|
||||||
|
...getDefaultConversation('convo-5'),
|
||||||
|
discoveredUnregisteredAt: new Date(1999, 3, 20).getTime(),
|
||||||
|
title: 'C',
|
||||||
|
},
|
||||||
|
'convo-6': {
|
||||||
|
...getDefaultConversation('convo-6'),
|
||||||
|
profileSharing: true,
|
||||||
|
name: 'Should Be Droped (no title)',
|
||||||
|
title: null,
|
||||||
|
},
|
||||||
|
'convo-7': {
|
||||||
|
...getDefaultConversation('convo-7'),
|
||||||
|
discoveredUnregisteredAt: Date.now(),
|
||||||
|
title: 'Should Be Dropped (unregistered)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
it('returns no gv1, no blocked, no missing titles', () => {
|
||||||
|
const state = getRootStateWithConversations();
|
||||||
|
const result = getAllComposableConversations(state);
|
||||||
|
|
||||||
|
const ids = result.map(contact => contact.id);
|
||||||
|
assert.deepEqual(ids, [
|
||||||
|
'our-conversation-id',
|
||||||
|
'convo-1',
|
||||||
|
'convo-3',
|
||||||
|
'convo-5',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#getComposeContacts', () => {
|
describe('#getComposeContacts', () => {
|
||||||
const getRootState = (searchTerm = ''): StateType => {
|
const getRootState = (searchTerm = ''): StateType => {
|
||||||
const rootState = getEmptyRootState();
|
const rootState = getEmptyRootState();
|
||||||
|
@ -558,7 +638,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
assert.deepEqual(ids, ['convo-0']);
|
assert.deepEqual(ids, ['convo-0']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns not to self when searching for your own name', () => {
|
it('returns note to self when searching for your own name', () => {
|
||||||
const state = getRootStateWithConversations('Myself');
|
const state = getRootStateWithConversations('Myself');
|
||||||
const result = getComposeContacts(state);
|
const result = getComposeContacts(state);
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,12 @@
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { getDefaultConversation } from '../helpers/getDefaultConversation';
|
import { getDefaultConversation } from '../helpers/getDefaultConversation';
|
||||||
|
|
||||||
import { filterAndSortConversations } from '../../util/filterAndSortConversations';
|
import {
|
||||||
|
filterAndSortConversationsByTitle,
|
||||||
|
filterAndSortConversationsByRecent,
|
||||||
|
} from '../../util/filterAndSortConversations';
|
||||||
|
|
||||||
describe('filterAndSortConversations', () => {
|
describe('filterAndSortConversationsByTitle', () => {
|
||||||
const conversations = [
|
const conversations = [
|
||||||
getDefaultConversation({
|
getDefaultConversation({
|
||||||
title: '+16505551234',
|
title: '+16505551234',
|
||||||
|
@ -34,7 +37,7 @@ describe('filterAndSortConversations', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
it('without a search term, sorts conversations by title (but puts no-name contacts at the bottom)', () => {
|
it('without a search term, sorts conversations by title (but puts no-name contacts at the bottom)', () => {
|
||||||
const titles = filterAndSortConversations(conversations, '').map(
|
const titles = filterAndSortConversationsByTitle(conversations, '').map(
|
||||||
contact => contact.title
|
contact => contact.title
|
||||||
);
|
);
|
||||||
assert.deepEqual(titles, [
|
assert.deepEqual(titles, [
|
||||||
|
@ -47,16 +50,56 @@ describe('filterAndSortConversations', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can search for contacts by title', () => {
|
it('can search for contacts by title', () => {
|
||||||
const titles = filterAndSortConversations(conversations, 'belind').map(
|
const titles = filterAndSortConversationsByTitle(
|
||||||
contact => contact.title
|
conversations,
|
||||||
);
|
'belind'
|
||||||
|
).map(contact => contact.title);
|
||||||
assert.sameMembers(titles, ['Belinda Beetle', 'Belinda Zephyr']);
|
assert.sameMembers(titles, ['Belinda Beetle', 'Belinda Zephyr']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can search for contacts by phone number (and puts no-name contacts at the bottom)', () => {
|
it('can search for contacts by phone number (and puts no-name contacts at the bottom)', () => {
|
||||||
const titles = filterAndSortConversations(conversations, '650555').map(
|
const titles = filterAndSortConversationsByTitle(
|
||||||
contact => contact.title
|
conversations,
|
||||||
);
|
'650555'
|
||||||
|
).map(contact => contact.title);
|
||||||
assert.sameMembers(titles, ['Carlos Santana', '+16505551234']);
|
assert.sameMembers(titles, ['Carlos Santana', '+16505551234']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('filterAndSortConversationsByRecent', () => {
|
||||||
|
const conversations = [
|
||||||
|
getDefaultConversation({
|
||||||
|
title: '+16505551234',
|
||||||
|
activeAt: 1,
|
||||||
|
}),
|
||||||
|
getDefaultConversation({
|
||||||
|
title: 'Abraham Lincoln',
|
||||||
|
activeAt: 4,
|
||||||
|
}),
|
||||||
|
getDefaultConversation({
|
||||||
|
title: 'Boxing Club',
|
||||||
|
activeAt: 3,
|
||||||
|
}),
|
||||||
|
getDefaultConversation({
|
||||||
|
title: 'Not recent',
|
||||||
|
}),
|
||||||
|
getDefaultConversation({
|
||||||
|
title: 'George Washington',
|
||||||
|
e164: '+16505559876',
|
||||||
|
activeAt: 2,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
it('sorts by recency when no search term is provided', () => {
|
||||||
|
const titles = filterAndSortConversationsByRecent(conversations, '').map(
|
||||||
|
contact => contact.title
|
||||||
|
);
|
||||||
|
assert.sameMembers(titles, [
|
||||||
|
'+16505551234',
|
||||||
|
'George Washington',
|
||||||
|
'Boxing Club',
|
||||||
|
'Abraham Lincoln',
|
||||||
|
'Not recent',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -32,14 +32,38 @@ const FUSE_OPTIONS: FuseOptions<ConversationType> = {
|
||||||
|
|
||||||
const collator = new Intl.Collator();
|
const collator = new Intl.Collator();
|
||||||
|
|
||||||
export function filterAndSortConversations(
|
function searchConversations(
|
||||||
|
conversations: ReadonlyArray<ConversationType>,
|
||||||
|
searchTerm: string
|
||||||
|
): Array<ConversationType> {
|
||||||
|
return new Fuse<ConversationType>(conversations, FUSE_OPTIONS).search(
|
||||||
|
searchTerm
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterAndSortConversationsByRecent(
|
||||||
conversations: ReadonlyArray<ConversationType>,
|
conversations: ReadonlyArray<ConversationType>,
|
||||||
searchTerm: string
|
searchTerm: string
|
||||||
): Array<ConversationType> {
|
): Array<ConversationType> {
|
||||||
if (searchTerm.length) {
|
if (searchTerm.length) {
|
||||||
return new Fuse<ConversationType>(conversations, FUSE_OPTIONS).search(
|
return searchConversations(conversations, searchTerm);
|
||||||
searchTerm
|
}
|
||||||
);
|
|
||||||
|
return conversations.concat().sort((a, b) => {
|
||||||
|
if (a.activeAt && b.activeAt) {
|
||||||
|
return a.activeAt > b.activeAt ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.activeAt && !b.activeAt ? -1 : 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterAndSortConversationsByTitle(
|
||||||
|
conversations: ReadonlyArray<ConversationType>,
|
||||||
|
searchTerm: string
|
||||||
|
): Array<ConversationType> {
|
||||||
|
if (searchTerm.length) {
|
||||||
|
return searchConversations(conversations, searchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
return conversations.concat().sort((a, b) => {
|
return conversations.concat().sort((a, b) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue