diff --git a/src/account-info/AccountDetailsScreen.js b/src/account-info/AccountDetailsScreen.js index eb2ecc49a81..0ad805d8968 100644 --- a/src/account-info/AccountDetailsScreen.js +++ b/src/account-info/AccountDetailsScreen.js @@ -7,7 +7,7 @@ import { createStyleSheet } from '../styles'; import { connect } from '../react-redux'; import { Screen, ZulipButton, Label } from '../common'; import { IconPrivateChat } from '../common/Icons'; -import { privateNarrow } from '../utils/narrow'; +import { pmNarrowFromEmail } from '../utils/narrow'; import AccountDetails from './AccountDetails'; import { doNarrow } from '../actions'; import { getUserIsActive, getUserForId } from '../users/userSelectors'; @@ -43,7 +43,7 @@ type Props = $ReadOnly<{| class AccountDetailsScreen extends PureComponent { handleChatPress = () => { const { user, dispatch } = this.props; - dispatch(doNarrow(privateNarrow(user.email))); + dispatch(doNarrow(pmNarrowFromEmail(user.email))); }; render() { diff --git a/src/chat/__tests__/narrowsReducer-test.js b/src/chat/__tests__/narrowsReducer-test.js index 86d70b6dde7..9eec8565ff1 100644 --- a/src/chat/__tests__/narrowsReducer-test.js +++ b/src/chat/__tests__/narrowsReducer-test.js @@ -5,9 +5,9 @@ import narrowsReducer from '../narrowsReducer'; import { HOME_NARROW, HOME_NARROW_STR, - privateNarrow, + pmNarrowFromEmail, ALL_PRIVATE_NARROW_STR, - groupNarrow, + pmNarrowFromEmails, streamNarrow, topicNarrow, STARRED_NARROW_STR, @@ -22,8 +22,10 @@ import { LAST_MESSAGE_ANCHOR, FIRST_UNREAD_ANCHOR } from '../../anchor'; import * as eg from '../../__tests__/lib/exampleData'; describe('narrowsReducer', () => { - const privateNarrowStr = JSON.stringify(privateNarrow(eg.otherUser.email)); - const groupNarrowStr = JSON.stringify(groupNarrow([eg.otherUser.email, eg.thirdUser.email])); + const privateNarrowStr = JSON.stringify(pmNarrowFromEmail(eg.otherUser.email)); + const groupNarrowStr = JSON.stringify( + pmNarrowFromEmails([eg.otherUser.email, eg.thirdUser.email]), + ); const streamNarrowStr = JSON.stringify(streamNarrow(eg.stream.name)); const egTopic = eg.streamMessage().subject; const topicNarrowStr = JSON.stringify(topicNarrow(eg.stream.name, egTopic)); @@ -157,7 +159,7 @@ describe('narrowsReducer', () => { }); test('message sent to self is stored correctly', () => { - const narrowWithSelfStr = JSON.stringify(privateNarrow(eg.selfUser.email)); + const narrowWithSelfStr = JSON.stringify(pmNarrowFromEmail(eg.selfUser.email)); const initialState = deepFreeze({ [HOME_NARROW_STR]: [], [narrowWithSelfStr]: [], @@ -406,7 +408,7 @@ describe('narrowsReducer', () => { const action = deepFreeze({ type: MESSAGE_FETCH_COMPLETE, anchor: 2, - narrow: privateNarrow(eg.otherUser.email), + narrow: pmNarrowFromEmail(eg.otherUser.email), messages: [], numBefore: 100, numAfter: 100, @@ -416,7 +418,7 @@ describe('narrowsReducer', () => { const expectedState = { [HOME_NARROW_STR]: [1, 2, 3], - [JSON.stringify(privateNarrow(eg.otherUser.email))]: [], + [JSON.stringify(pmNarrowFromEmail(eg.otherUser.email))]: [], }; const newState = narrowsReducer(initialState, action); diff --git a/src/chat/__tests__/narrowsSelectors-test.js b/src/chat/__tests__/narrowsSelectors-test.js index 12205a382d4..420edcca029 100644 --- a/src/chat/__tests__/narrowsSelectors-test.js +++ b/src/chat/__tests__/narrowsSelectors-test.js @@ -9,11 +9,11 @@ import { import { HOME_NARROW, HOME_NARROW_STR, - privateNarrow, + pmNarrowFromEmail, streamNarrow, topicNarrow, STARRED_NARROW, - groupNarrow, + pmNarrowFromEmails, } from '../../utils/narrow'; import { NULL_SUBSCRIPTION } from '../../nullObjects'; import * as eg from '../../__tests__/lib/exampleData'; @@ -78,14 +78,14 @@ describe('getMessagesForNarrow', () => { test('do not combine messages and outbox in different narrow', () => { const state = eg.reduxState({ narrows: { - [JSON.stringify(privateNarrow('john@example.com'))]: [123], + [JSON.stringify(pmNarrowFromEmail('john@example.com'))]: [123], }, messages, outbox: [outboxMessage], realm: eg.realmState({ email: eg.selfUser.email }), }); - const result = getMessagesForNarrow(state, privateNarrow('john@example.com')); + const result = getMessagesForNarrow(state, pmNarrowFromEmail('john@example.com')); expect(result).toEqual([message]); }); @@ -190,7 +190,7 @@ describe('getStreamInNarrow', () => { }); test('return NULL_SUBSCRIPTION is narrow is not topic or stream', () => { - expect(getStreamInNarrow(state, privateNarrow('abc@zulip.com'))).toEqual(NULL_SUBSCRIPTION); + expect(getStreamInNarrow(state, pmNarrowFromEmail('abc@zulip.com'))).toEqual(NULL_SUBSCRIPTION); expect(getStreamInNarrow(state, topicNarrow(stream4.name, 'topic'))).toEqual(NULL_SUBSCRIPTION); }); }); @@ -253,7 +253,7 @@ describe('isNarrowValid', () => { streams: [], users: [user], }); - const narrow = privateNarrow(user.email); + const narrow = pmNarrowFromEmail(user.email); const result = isNarrowValid(state, narrow); @@ -271,14 +271,14 @@ describe('isNarrowValid', () => { streams: [], users: [], }); - const narrow = privateNarrow(user.email); + const narrow = pmNarrowFromEmail(user.email); const result = isNarrowValid(state, narrow); expect(result).toBe(false); }); - test('narrowing to a group chat with non-existing user is not valid', () => { + test('narrowing to a group chat with existing users is valid', () => { const john = eg.makeUser({ name: 'john' }); const mark = eg.makeUser({ name: 'mark' }); @@ -291,14 +291,16 @@ describe('isNarrowValid', () => { streams: [], users: [john, mark], }); - const narrow = groupNarrow([john.email, mark.email]); + const narrow = pmNarrowFromEmails([john.email, mark.email]); const result = isNarrowValid(state, narrow); expect(result).toBe(true); }); - test('narrowing to a group chat with non-existing users is also valid', () => { + test('narrowing to a group chat with non-existing users is not valid', () => { + const john = eg.makeUser({ name: 'john' }); + const mark = eg.makeUser({ name: 'mark' }); const state = eg.reduxState({ realm: { ...eg.realmState(), @@ -306,13 +308,13 @@ describe('isNarrowValid', () => { nonActiveUsers: [], }, streams: [], - users: [], + users: [john], }); - const narrow = groupNarrow(['john@example.com', 'mark@example.com']); + const narrow = pmNarrowFromEmails([john.email, mark.email]); const result = isNarrowValid(state, narrow); - expect(result).toBe(true); + expect(result).toBe(false); }); test('narrowing to a PM with bots is valid', () => { @@ -326,7 +328,7 @@ describe('isNarrowValid', () => { streams: [], users: [], }); - const narrow = privateNarrow(bot.email); + const narrow = pmNarrowFromEmail(bot.email); const result = isNarrowValid(state, narrow); @@ -344,7 +346,7 @@ describe('isNarrowValid', () => { streams: [], users: [], }); - const narrow = privateNarrow(notActiveUser.email); + const narrow = pmNarrowFromEmail(notActiveUser.email); const result = isNarrowValid(state, narrow); diff --git a/src/chat/narrowsSelectors.js b/src/chat/narrowsSelectors.js index 66e88f3327a..7ffb100b8f8 100644 --- a/src/chat/narrowsSelectors.js +++ b/src/chat/narrowsSelectors.js @@ -24,10 +24,10 @@ import { import { getCaughtUpForNarrow } from '../caughtup/caughtUpSelectors'; import { getAllUsersByEmail, getOwnEmail } from '../users/userSelectors'; import { - isPrivateNarrow, isStreamOrTopicNarrow, - emailsOfGroupNarrow, + emailsOfGroupPmNarrow, isMessageInNarrow, + caseNarrowDefault, } from '../utils/narrow'; import { shouldBeMuted } from '../utils/message'; import { NULL_ARRAY, NULL_SUBSCRIPTION } from '../nullObjects'; @@ -102,11 +102,11 @@ export const getLastMessageId = (state: GlobalState, narrow: Narrow): number | v return ids.length > 0 ? ids[ids.length - 1] : undefined; }; -export const getRecipientsInGroupNarrow: Selector = createSelector( +export const getRecipientsInGroupPmNarrow: Selector = createSelector( (state, narrow) => narrow, state => getAllUsersByEmail(state), (narrow, allUsersByEmail) => - emailsOfGroupNarrow(narrow).map(r => { + emailsOfGroupPmNarrow(narrow).map(r => { const user = allUsersByEmail.get(r); invariant(user, 'missing user: %s', r); return user; @@ -146,15 +146,14 @@ export const isNarrowValid: Selector = createSelector( (state, narrow) => narrow, state => getStreams(state), state => getAllUsersByEmail(state), - (narrow, streams, allUsersByEmail) => { - if (isStreamOrTopicNarrow(narrow)) { - return streams.find(s => s.name === narrow[0].operand) !== undefined; - } - - if (isPrivateNarrow(narrow)) { - return allUsersByEmail.get(narrow[0].operand) !== undefined; - } - - return true; - }, + (narrow, streams, allUsersByEmail) => + caseNarrowDefault( + narrow, + { + stream: streamName => streams.find(s => s.name === streamName) !== undefined, + topic: streamName => streams.find(s => s.name === streamName) !== undefined, + pm: emails => emails.every(email => allUsersByEmail.get(email) !== undefined), + }, + () => true, + ), ); diff --git a/src/compose/MentionWarnings.js b/src/compose/MentionWarnings.js index 77e1212d983..9f1a2fa1200 100644 --- a/src/compose/MentionWarnings.js +++ b/src/compose/MentionWarnings.js @@ -6,7 +6,7 @@ import { connect } from 'react-redux'; import type { Auth, Stream, Dispatch, Narrow, UserOrBot, Subscription, GetText } from '../types'; import { TranslationContext } from '../boot/TranslationProvider'; import { getActiveUsersById, getAuth } from '../selectors'; -import { isPrivateNarrow } from '../utils/narrow'; +import { is1to1PmNarrow } from '../utils/narrow'; import * as api from '../api'; import { showToast } from '../utils/info'; @@ -94,7 +94,7 @@ class MentionWarnings extends PureComponent { const { narrow, auth, stream } = this.props; const { unsubscribedMentions } = this.state; - if (isPrivateNarrow(narrow)) { + if (is1to1PmNarrow(narrow)) { return; } const mentionedUser = this.getUserFromMention(completion); @@ -139,7 +139,7 @@ class MentionWarnings extends PureComponent { const { unsubscribedMentions } = this.state; const { stream, narrow, usersById } = this.props; - if (isPrivateNarrow(narrow)) { + if (is1to1PmNarrow(narrow)) { return null; } diff --git a/src/compose/__tests__/getComposeInputPlaceholder-test.js b/src/compose/__tests__/getComposeInputPlaceholder-test.js index e5550b325d6..9132e52dfd2 100644 --- a/src/compose/__tests__/getComposeInputPlaceholder-test.js +++ b/src/compose/__tests__/getComposeInputPlaceholder-test.js @@ -1,11 +1,16 @@ import deepFreeze from 'deep-freeze'; import getComposeInputPlaceholder from '../getComposeInputPlaceholder'; -import { privateNarrow, streamNarrow, topicNarrow, groupNarrow } from '../../utils/narrow'; +import { + pmNarrowFromEmail, + streamNarrow, + topicNarrow, + pmNarrowFromEmails, +} from '../../utils/narrow'; describe('getComposeInputPlaceholder', () => { test('returns "Message @ThisPerson" object for person narrow', () => { - const narrow = deepFreeze(privateNarrow('abc@zulip.com')); + const narrow = deepFreeze(pmNarrowFromEmail('abc@zulip.com')); const ownEmail = 'hamlet@zulip.com'; @@ -33,7 +38,7 @@ describe('getComposeInputPlaceholder', () => { }); test('returns "Jot down something" object for self narrow', () => { - const narrow = deepFreeze(privateNarrow('abc@zulip.com')); + const narrow = deepFreeze(pmNarrowFromEmail('abc@zulip.com')); const ownEmail = 'abc@zulip.com'; @@ -58,7 +63,7 @@ describe('getComposeInputPlaceholder', () => { }); test('returns "Message group" object for group narrow', () => { - const narrow = deepFreeze(groupNarrow(['abc@zulip.com, xyz@zulip.com'])); + const narrow = deepFreeze(pmNarrowFromEmails(['abc@zulip.com, xyz@zulip.com'])); const placeholder = getComposeInputPlaceholder(narrow); expect(placeholder).toEqual({ text: 'Message group' }); diff --git a/src/compose/getComposeInputPlaceholder.js b/src/compose/getComposeInputPlaceholder.js index ea465dfb734..ec24a0d1f55 100644 --- a/src/compose/getComposeInputPlaceholder.js +++ b/src/compose/getComposeInputPlaceholder.js @@ -1,17 +1,17 @@ /* @flow strict-local */ import type { Narrow, UserOrBot, LocalizableText } from '../types'; -import { isStreamNarrow, isTopicNarrow, isPrivateNarrow, isGroupNarrow } from '../utils/narrow'; +import { isStreamNarrow, isTopicNarrow, isGroupPmNarrow, is1to1PmNarrow } from '../utils/narrow'; export default ( narrow: Narrow, ownEmail: string, usersByEmail: Map, ): LocalizableText => { - if (isGroupNarrow(narrow)) { + if (isGroupPmNarrow(narrow)) { return { text: 'Message group' }; } - if (isPrivateNarrow(narrow)) { + if (is1to1PmNarrow(narrow)) { if (ownEmail && narrow[0].operand === ownEmail) { return { text: 'Jot down something' }; } diff --git a/src/message/NoMessages.js b/src/message/NoMessages.js index 7a5a4881d83..3ebcdd914a2 100644 --- a/src/message/NoMessages.js +++ b/src/message/NoMessages.js @@ -9,8 +9,8 @@ import { Label } from '../common'; import { isHomeNarrow, - isPrivateNarrow, - isGroupNarrow, + is1to1PmNarrow, + isGroupPmNarrow, isSpecialNarrow, isStreamNarrow, isTopicNarrow, @@ -41,8 +41,8 @@ const messages: EmptyMessage[] = [ { isFunc: isSpecialNarrow, text: 'No messages' }, { isFunc: isStreamNarrow, text: 'No messages in stream' }, { isFunc: isTopicNarrow, text: 'No messages with this topic' }, - { isFunc: isPrivateNarrow, text: 'No messages with this person' }, - { isFunc: isGroupNarrow, text: 'No messages in this group' }, + { isFunc: is1to1PmNarrow, text: 'No messages with this person' }, + { isFunc: isGroupPmNarrow, text: 'No messages in this group' }, { isFunc: isSearchNarrow, text: 'No messages' }, ]; diff --git a/src/message/messageActionSheet.js b/src/message/messageActionSheet.js index d31fcda4b78..40aaa0d2926 100644 --- a/src/message/messageActionSheet.js +++ b/src/message/messageActionSheet.js @@ -17,7 +17,7 @@ import type { import type { BackgroundData } from '../webview/MessageList'; import { getNarrowFromMessage, - isPrivateOrGroupNarrow, + isPmNarrow, isStreamOrTopicNarrow, isTopicNarrow, } from '../utils/narrow'; @@ -299,7 +299,7 @@ export const constructMessageActionButtons = ({ if (message.reactions.length > 0) { buttons.push('showReactions'); } - if (!isTopicNarrow(narrow) && !isPrivateOrGroupNarrow(narrow)) { + if (!isTopicNarrow(narrow) && !isPmNarrow(narrow)) { buttons.push('reply'); } if (messageNotDeleted(message)) { @@ -309,7 +309,7 @@ export const constructMessageActionButtons = ({ if ( message.sender_email === ownUser.email // Our "edit message" UI only works in certain kinds of narrows. - && (isStreamOrTopicNarrow(narrow) || isPrivateOrGroupNarrow(narrow)) + && (isStreamOrTopicNarrow(narrow) || isPmNarrow(narrow)) ) { buttons.push('editMessage'); } diff --git a/src/message/renderMessages.js b/src/message/renderMessages.js index bba308695e1..149f34815ee 100644 --- a/src/message/renderMessages.js +++ b/src/message/renderMessages.js @@ -1,6 +1,6 @@ /* @flow strict-local */ import type { Message, Narrow, Outbox, RenderedSectionDescriptor } from '../types'; -import { isTopicNarrow, isPrivateOrGroupNarrow } from '../utils/narrow'; +import { isTopicNarrow, isPmNarrow } from '../utils/narrow'; import { isSameRecipient } from '../utils/recipient'; import { isSameDay } from '../utils/date'; @@ -8,7 +8,7 @@ export default ( messages: $ReadOnlyArray, narrow: Narrow, ): RenderedSectionDescriptor[] => { - const showHeader = !isPrivateOrGroupNarrow(narrow) && !isTopicNarrow(narrow); + const showHeader = !isPmNarrow(narrow) && !isTopicNarrow(narrow); let prevItem; const sections = [{ key: 0, data: [], message: {} }]; diff --git a/src/notification/__tests__/notification-test.js b/src/notification/__tests__/notification-test.js index 849bafb1200..44a15900883 100644 --- a/src/notification/__tests__/notification-test.js +++ b/src/notification/__tests__/notification-test.js @@ -4,7 +4,7 @@ import deepFreeze from 'deep-freeze'; import type { User } from '../../api/modelTypes'; import type { JSONableDict } from '../../utils/jsonable'; import { getNarrowFromNotificationData } from '..'; -import { topicNarrow, privateNarrow, groupNarrow } from '../../utils/narrow'; +import { topicNarrow, pmNarrowFromEmail, pmNarrowFromEmails } from '../../utils/narrow'; import * as eg from '../../__tests__/lib/exampleData'; import { fromAPNsImpl as extractIosNotificationData } from '../extract'; @@ -37,7 +37,7 @@ describe('getNarrowFromNotificationData', () => { sender_email: 'mark@example.com', }; const narrow = getNarrowFromNotificationData(notification, DEFAULT_MAP, ownUserId); - expect(narrow).toEqual(privateNarrow('mark@example.com')); + expect(narrow).toEqual(pmNarrowFromEmail('mark@example.com')); }); test('on notification for a group message returns a group narrow', () => { @@ -49,7 +49,7 @@ describe('getNarrowFromNotificationData', () => { pm_users: users.map(u => u.user_id).join(','), }; - const expectedNarrow = groupNarrow(users.slice(1).map(u => u.email)); + const expectedNarrow = pmNarrowFromEmails(users.slice(1).map(u => u.email)); const narrow = getNarrowFromNotificationData(notification, usersById, ownUserId); diff --git a/src/notification/index.js b/src/notification/index.js index 5bc92470073..c4be1d31d1d 100644 --- a/src/notification/index.js +++ b/src/notification/index.js @@ -4,7 +4,7 @@ import NotificationsIOS from 'react-native-notifications'; import type { Notification } from './types'; import type { Auth, Dispatch, Identity, Narrow, User } from '../types'; -import { topicNarrow, privateNarrow, groupNarrow } from '../utils/narrow'; +import { topicNarrow, pmNarrowFromEmail, pmNarrowFromEmails } from '../utils/narrow'; import type { JSONable, JSONableDict } from '../utils/jsonable'; import * as api from '../api'; import * as logging from '../utils/logging'; @@ -102,12 +102,12 @@ export const getNarrowFromNotificationData = ( } if (data.pm_users === undefined) { - return privateNarrow(data.sender_email); + return pmNarrowFromEmail(data.sender_email); } const ids = data.pm_users.split(',').map(s => parseInt(s, 10)); const users = pmKeyRecipientsFromIds(ids, usersById, ownUserId); - return users && groupNarrow(users.map(u => u.email)); + return users && pmNarrowFromEmails(users.map(u => u.email)); }; const getInitialNotification = async (): Promise => { diff --git a/src/outbox/outboxActions.js b/src/outbox/outboxActions.js index abbbf20663b..1a1d06f5617 100644 --- a/src/outbox/outboxActions.js +++ b/src/outbox/outboxActions.js @@ -122,15 +122,13 @@ const extractTypeToAndSubjectFromNarrow = ( narrow: Narrow, allUsersByEmail: Map, ownUser: UserOrBot, -): DataFromNarrow => { - const forPm = emails => ({ - type: 'private', - display_recipient: mapEmailsToUsers(emails, allUsersByEmail, ownUser), - subject: '', - }); - return caseNarrowPartial(narrow, { - pm: email => forPm([email]), - groupPm: forPm, +): DataFromNarrow => + caseNarrowPartial(narrow, { + pm: emails => ({ + type: 'private', + display_recipient: mapEmailsToUsers(emails, allUsersByEmail, ownUser), + subject: '', + }), // TODO: we shouldn't ever be passing a whole-stream narrow here; // ensure we don't, then remove this case @@ -142,7 +140,6 @@ const extractTypeToAndSubjectFromNarrow = ( subject: topic, }), }); -}; const getContentPreview = (content: string, state: GlobalState): string => { try { diff --git a/src/pm-conversations/PmConversationList.js b/src/pm-conversations/PmConversationList.js index fb42ba6e03c..f6d335bc9be 100644 --- a/src/pm-conversations/PmConversationList.js +++ b/src/pm-conversations/PmConversationList.js @@ -4,7 +4,7 @@ import { FlatList } from 'react-native'; import type { Dispatch, PmConversationData, UserOrBot } from '../types'; import { createStyleSheet } from '../styles'; -import { privateNarrow, groupNarrow } from '../utils/narrow'; +import { pmNarrowFromEmail, pmNarrowFromEmails } from '../utils/narrow'; import UserItem from '../users/UserItem'; import GroupPmConversationItem from './GroupPmConversationItem'; import { doNarrow } from '../actions'; @@ -27,11 +27,11 @@ type Props = $ReadOnly<{| * */ export default class PmConversationList extends PureComponent { handleUserNarrow = (user: UserOrBot) => { - this.props.dispatch(doNarrow(privateNarrow(user.email))); + this.props.dispatch(doNarrow(pmNarrowFromEmail(user.email))); }; handleGroupNarrow = (email: string) => { - this.props.dispatch(doNarrow(groupNarrow(email.split(',')))); + this.props.dispatch(doNarrow(pmNarrowFromEmails(email.split(',')))); }; render() { diff --git a/src/title-buttons/InfoNavButtonGroup.js b/src/title-buttons/InfoNavButtonGroup.js index 31f802be2e4..aa63af1a391 100644 --- a/src/title-buttons/InfoNavButtonGroup.js +++ b/src/title-buttons/InfoNavButtonGroup.js @@ -5,7 +5,7 @@ import React, { PureComponent } from 'react'; import NavigationService from '../nav/NavigationService'; import type { Dispatch, Narrow, UserOrBot } from '../types'; import { connect } from '../react-redux'; -import { getRecipientsInGroupNarrow } from '../selectors'; +import { getRecipientsInGroupPmNarrow } from '../selectors'; import NavButton from '../nav/NavButton'; import { navigateToGroupDetails } from '../actions'; @@ -35,5 +35,5 @@ class InfoNavButtonGroup extends PureComponent { } export default connect((state, props) => ({ - recipients: getRecipientsInGroupNarrow(state, props.narrow), + recipients: getRecipientsInGroupPmNarrow(state, props.narrow), }))(InfoNavButtonGroup); diff --git a/src/title-buttons/titleButtonFromNarrow.js b/src/title-buttons/titleButtonFromNarrow.js index 3a689395299..a1e7730325f 100644 --- a/src/title-buttons/titleButtonFromNarrow.js +++ b/src/title-buttons/titleButtonFromNarrow.js @@ -5,8 +5,8 @@ import type { ComponentType } from 'react'; import type { Narrow } from '../types'; import { isHomeNarrow, - isPrivateNarrow, - isGroupNarrow, + is1to1PmNarrow, + isGroupPmNarrow, isSpecialNarrow, isStreamNarrow, isTopicNarrow, @@ -29,8 +29,8 @@ const infoButtonHandlers: NarrowNavButtonCandidate[] = [ { isFunc: isSpecialNarrow, ButtonComponent: null }, { isFunc: isStreamNarrow, ButtonComponent: InfoNavButtonStream }, { isFunc: isTopicNarrow, ButtonComponent: InfoNavButtonStream }, - { isFunc: isPrivateNarrow, ButtonComponent: InfoNavButtonPrivate }, - { isFunc: isGroupNarrow, ButtonComponent: InfoNavButtonGroup }, + { isFunc: is1to1PmNarrow, ButtonComponent: InfoNavButtonPrivate }, + { isFunc: isGroupPmNarrow, ButtonComponent: InfoNavButtonGroup }, ]; const extraButtonHandlers: NarrowNavButtonCandidate[] = [ @@ -38,8 +38,8 @@ const extraButtonHandlers: NarrowNavButtonCandidate[] = [ { isFunc: isSpecialNarrow, ButtonComponent: null }, { isFunc: isStreamNarrow, ButtonComponent: ExtraNavButtonStream }, { isFunc: isTopicNarrow, ButtonComponent: ExtraNavButtonTopic }, - { isFunc: isPrivateNarrow, ButtonComponent: null }, - { isFunc: isGroupNarrow, ButtonComponent: null }, + { isFunc: is1to1PmNarrow, ButtonComponent: null }, + { isFunc: isGroupPmNarrow, ButtonComponent: null }, ]; const makeButton = (handlers): NarrowNavButton => props => { diff --git a/src/title/Title.js b/src/title/Title.js index ee5c35bfeff..c25d81084b9 100644 --- a/src/title/Title.js +++ b/src/title/Title.js @@ -30,8 +30,12 @@ export default class Title extends PureComponent { allPrivate: () => , stream: () => , topic: () => , - pm: email => , - groupPm: () => , + pm: emails => + emails.length === 1 ? ( + + ) : ( + + ), search: () => null, }); } diff --git a/src/title/TitleGroup.js b/src/title/TitleGroup.js index 34a7c5ed021..f88b0ac0cfc 100644 --- a/src/title/TitleGroup.js +++ b/src/title/TitleGroup.js @@ -8,7 +8,7 @@ import type { Dispatch, UserOrBot, Narrow } from '../types'; import styles, { createStyleSheet } from '../styles'; import { connect } from '../react-redux'; import { UserAvatarWithPresence } from '../common'; -import { getRecipientsInGroupNarrow } from '../selectors'; +import { getRecipientsInGroupPmNarrow } from '../selectors'; import { navigateToAccountDetails } from '../nav/navActions'; type SelectorProps = $ReadOnly<{| @@ -54,5 +54,5 @@ class TitleGroup extends PureComponent { } export default connect((state, props) => ({ - recipients: getRecipientsInGroupNarrow(state, props.narrow), + recipients: getRecipientsInGroupPmNarrow(state, props.narrow), }))(TitleGroup); diff --git a/src/title/__tests__/titleSelectors-test.js b/src/title/__tests__/titleSelectors-test.js index da3eaca83ed..2ace21ff47b 100644 --- a/src/title/__tests__/titleSelectors-test.js +++ b/src/title/__tests__/titleSelectors-test.js @@ -1,7 +1,7 @@ import deepFreeze from 'deep-freeze'; import { DEFAULT_TITLE_BACKGROUND_COLOR, getTitleBackgroundColor } from '../titleSelectors'; -import { groupNarrow, streamNarrow, privateNarrow } from '../../utils/narrow'; +import { pmNarrowFromEmails, streamNarrow, pmNarrowFromEmail } from '../../utils/narrow'; const subscriptions = [{ name: 'all', color: '#fff' }, { name: 'announce', color: '#000' }]; @@ -35,11 +35,11 @@ describe('getTitleBackgroundColor', () => { subscriptions, }); - expect(getTitleBackgroundColor(state, privateNarrow('abc@zulip.com'))).toEqual( - DEFAULT_TITLE_BACKGROUND_COLOR, - ); - expect(getTitleBackgroundColor(state, groupNarrow(['abc@zulip.com', 'def@zulip.com']))).toEqual( + expect(getTitleBackgroundColor(state, pmNarrowFromEmail('abc@zulip.com'))).toEqual( DEFAULT_TITLE_BACKGROUND_COLOR, ); + expect( + getTitleBackgroundColor(state, pmNarrowFromEmails(['abc@zulip.com', 'def@zulip.com'])), + ).toEqual(DEFAULT_TITLE_BACKGROUND_COLOR); }); }); diff --git a/src/typing/__tests__/typingSelectors-test.js b/src/typing/__tests__/typingSelectors-test.js index 4a763c0cdf7..2445d855f50 100644 --- a/src/typing/__tests__/typingSelectors-test.js +++ b/src/typing/__tests__/typingSelectors-test.js @@ -2,7 +2,7 @@ import type { GlobalState } from '../../types'; import { getCurrentTypingUsers } from '../typingSelectors'; -import { HOME_NARROW, privateNarrow, groupNarrow } from '../../utils/narrow'; +import { HOME_NARROW, pmNarrowFromEmail, pmNarrowFromEmails } from '../../utils/narrow'; import { NULL_ARRAY } from '../../nullObjects'; import * as eg from '../../__tests__/lib/exampleData'; import { normalizeRecipientsAsUserIds } from '../../utils/recipient'; @@ -25,7 +25,7 @@ describe('getCurrentTypingUsers', () => { users: [expectedUser], }); - const typingUsers = getCurrentTypingUsers(state, privateNarrow(expectedUser.email)); + const typingUsers = getCurrentTypingUsers(state, pmNarrowFromEmail(expectedUser.email)); expect(typingUsers).toEqual([expectedUser]); }); @@ -46,7 +46,10 @@ describe('getCurrentTypingUsers', () => { users: [user1, user2], }); - const typingUsers = getCurrentTypingUsers(state, groupNarrow([user1.email, user2.email])); + const typingUsers = getCurrentTypingUsers( + state, + pmNarrowFromEmails([user1.email, user2.email]), + ); expect(typingUsers).toEqual([user1, user2]); }); @@ -63,7 +66,7 @@ describe('getCurrentTypingUsers', () => { users: [user1, user2], }); - const typingUsers = getCurrentTypingUsers(state, privateNarrow(user2.email)); + const typingUsers = getCurrentTypingUsers(state, pmNarrowFromEmail(user2.email)); expect(typingUsers).toEqual(NULL_ARRAY); }); @@ -85,7 +88,7 @@ describe('getCurrentTypingUsers', () => { const typingUsers = getCurrentTypingUsers( state, - groupNarrow([expectedUser.email, anotherUser.email]), + pmNarrowFromEmails([expectedUser.email, anotherUser.email]), ); expect(typingUsers).toEqual([expectedUser]); @@ -98,7 +101,8 @@ describe('getCurrentTypingUsers', () => { realm: eg.realmState({ nonActiveUsers: [deactivatedUser] }), }); - const getTypingUsers = () => getCurrentTypingUsers(state, privateNarrow(deactivatedUser.email)); + const getTypingUsers = () => + getCurrentTypingUsers(state, pmNarrowFromEmail(deactivatedUser.email)); expect(getTypingUsers).not.toThrow(); expect(getTypingUsers()).toEqual([]); @@ -111,7 +115,8 @@ describe('getCurrentTypingUsers', () => { realm: eg.realmState({ crossRealmBots: [crossRealmBot] }), }); - const getTypingUsers = () => getCurrentTypingUsers(state, privateNarrow(crossRealmBot.email)); + const getTypingUsers = () => + getCurrentTypingUsers(state, pmNarrowFromEmail(crossRealmBot.email)); expect(getTypingUsers).not.toThrow(); expect(getTypingUsers()).toEqual([]); diff --git a/src/typing/typingSelectors.js b/src/typing/typingSelectors.js index f0f7172fbc9..149fe635bf5 100644 --- a/src/typing/typingSelectors.js +++ b/src/typing/typingSelectors.js @@ -3,7 +3,7 @@ import { createSelector } from 'reselect'; import type { Narrow, Selector, UserOrBot } from '../types'; import { getTyping } from '../directSelectors'; -import { isPrivateOrGroupNarrow } from '../utils/narrow'; +import { isPmNarrow } from '../utils/narrow'; import { normalizeRecipientsAsUserIds } from '../utils/recipient'; import { NULL_ARRAY, NULL_USER } from '../nullObjects'; import { getAllUsersById, getAllUsersByEmail } from '../users/userSelectors'; @@ -14,7 +14,7 @@ export const getCurrentTypingUsers: Selector<$ReadOnlyArray, Narrow> state => getAllUsersById(state), state => getAllUsersByEmail(state), (narrow, typing, allUsersById, allUsersByEmail): UserOrBot[] => { - if (!isPrivateOrGroupNarrow(narrow)) { + if (!isPmNarrow(narrow)) { return NULL_ARRAY; } diff --git a/src/unread/unreadSelectors.js b/src/unread/unreadSelectors.js index 4d7e55ed68c..7cb9ff5558c 100644 --- a/src/unread/unreadSelectors.js +++ b/src/unread/unreadSelectors.js @@ -18,8 +18,8 @@ import { isHomeNarrow, isStreamNarrow, isTopicNarrow, - isGroupNarrow, - isPrivateNarrow, + isGroupPmNarrow, + is1to1PmNarrow, } from '../utils/narrow'; import { NULL_SUBSCRIPTION, NULL_USER } from '../nullObjects'; @@ -276,7 +276,7 @@ export const getUnreadCountForNarrow: Selector = createSelector( .reduce((sum, x) => sum + x.unread_message_ids.length, 0); } - if (isGroupNarrow(narrow)) { + if (isGroupPmNarrow(narrow)) { const userIds = [...narrow[0].operand.split(','), ownEmail] .map(email => (usersByEmail.get(email) || NULL_USER).user_id) .sort((a, b) => a - b) @@ -285,7 +285,7 @@ export const getUnreadCountForNarrow: Selector = createSelector( return unread ? unread.unread_message_ids.length : 0; } - if (isPrivateNarrow(narrow)) { + if (is1to1PmNarrow(narrow)) { const sender = usersByEmail.get(narrow[0].operand); if (!sender) { return 0; diff --git a/src/user-groups/CreateGroupScreen.js b/src/user-groups/CreateGroupScreen.js index 804293d5515..e5a181894ed 100644 --- a/src/user-groups/CreateGroupScreen.js +++ b/src/user-groups/CreateGroupScreen.js @@ -7,7 +7,7 @@ import type { Dispatch, User } from '../types'; import { connect } from '../react-redux'; import { Screen } from '../common'; import { doNarrow, navigateBack } from '../actions'; -import { groupNarrow } from '../utils/narrow'; +import { pmNarrowFromEmails } from '../utils/narrow'; import UserPickerCard from '../user-picker/UserPickerCard'; type Props = $ReadOnly<{| @@ -36,7 +36,7 @@ class CreateGroupScreen extends PureComponent { const recipients = selected.map(user => user.email); NavigationService.dispatch(navigateBack()); - dispatch(doNarrow(groupNarrow(recipients))); + dispatch(doNarrow(pmNarrowFromEmails(recipients))); }; render() { diff --git a/src/users/UsersCard.js b/src/users/UsersCard.js index 4bf91cbf4b5..bc3582d0467 100644 --- a/src/users/UsersCard.js +++ b/src/users/UsersCard.js @@ -5,7 +5,7 @@ import React, { PureComponent } from 'react'; import NavigationService from '../nav/NavigationService'; import type { Dispatch, PresenceState, User, UserOrBot } from '../types'; import { connect } from '../react-redux'; -import { privateNarrow } from '../utils/narrow'; +import { pmNarrowFromEmail } from '../utils/narrow'; import UserList from './UserList'; import { getUsers, getPresence } from '../selectors'; import { navigateBack, doNarrow } from '../actions'; @@ -21,7 +21,7 @@ class UsersCard extends PureComponent { handleUserNarrow = (user: UserOrBot) => { const { dispatch } = this.props; NavigationService.dispatch(navigateBack()); - dispatch(doNarrow(privateNarrow(user.email))); + dispatch(doNarrow(pmNarrowFromEmail(user.email))); }; render() { diff --git a/src/users/usersActions.js b/src/users/usersActions.js index f6a91e60549..f4e37a815ac 100644 --- a/src/users/usersActions.js +++ b/src/users/usersActions.js @@ -5,7 +5,7 @@ import type { Auth, Dispatch, GetState, GlobalState, Narrow } from '../types'; import * as api from '../api'; import { PRESENCE_RESPONSE } from '../actionConstants'; import { getAuth, tryGetAuth, getServerVersion } from '../selectors'; -import { isPrivateOrGroupNarrow, caseNarrowPartial } from '../utils/narrow'; +import { isPmNarrow, caseNarrowPartial } from '../utils/narrow'; import { getAllUsersByEmail, getUserForId } from './userSelectors'; import { ZulipVersion } from '../utils/zulipVersion'; @@ -60,14 +60,13 @@ export const sendTypingStart = (narrow: Narrow) => async ( dispatch: Dispatch, getState: GetState, ) => { - if (!isPrivateOrGroupNarrow(narrow)) { + if (!isPmNarrow(narrow)) { return; } const usersByEmail = getAllUsersByEmail(getState()); const recipientIds = caseNarrowPartial(narrow, { - pm: email => [email], - groupPm: emails => emails, + pm: emails => emails, }).map(email => { const user = usersByEmail.get(email); if (!user) { @@ -84,7 +83,7 @@ export const sendTypingStop = (narrow: Narrow) => async ( dispatch: Dispatch, getState: GetState, ) => { - if (!isPrivateOrGroupNarrow(narrow)) { + if (!isPmNarrow(narrow)) { return; } diff --git a/src/utils/__tests__/internalLinks-test.js b/src/utils/__tests__/internalLinks-test.js index 865fc783bc2..5a87ce0e222 100644 --- a/src/utils/__tests__/internalLinks-test.js +++ b/src/utils/__tests__/internalLinks-test.js @@ -1,7 +1,7 @@ /* @flow strict-local */ import type { User } from '../../api/modelTypes'; -import { streamNarrow, topicNarrow, groupNarrow, STARRED_NARROW } from '../narrow'; +import { streamNarrow, topicNarrow, pmNarrowFromEmails, STARRED_NARROW } from '../narrow'; import { isInternalLink, isMessageLink, @@ -257,7 +257,7 @@ describe('getNarrowFromLink', () => { test('on group PM link', () => { const ids = `${userB.user_id},${userC.user_id}`; expect(get(`https://example.com/#narrow/pm-with/${ids}-group`)).toEqual( - groupNarrow([userB.email, userC.email]), + pmNarrowFromEmails([userB.email, userC.email]), ); }); @@ -265,7 +265,7 @@ describe('getNarrowFromLink', () => { // The webapp doesn't generate these, but best to handle them anyway. const ids = `${eg.selfUser.user_id},${userB.user_id},${userC.user_id}`; expect(get(`https://example.com/#narrow/pm-with/${ids}-group`)).toEqual( - groupNarrow([userB.email, userC.email]), + pmNarrowFromEmails([userB.email, userC.email]), ); }); @@ -282,7 +282,7 @@ describe('getNarrowFromLink', () => { test('on a message link', () => { const ids = `${userB.user_id},${userC.user_id}`; expect(get(`https://example.com/#narrow/pm-with/${ids}-group/near/2`)).toEqual( - groupNarrow([userB.email, userC.email]), + pmNarrowFromEmails([userB.email, userC.email]), ); expect(get('https://example.com/#narrow/stream/jest/topic/test/near/1')).toEqual( diff --git a/src/utils/__tests__/narrow-test.js b/src/utils/__tests__/narrow-test.js index 94ba30bf37f..876b3575c60 100644 --- a/src/utils/__tests__/narrow-test.js +++ b/src/utils/__tests__/narrow-test.js @@ -3,10 +3,10 @@ import { HOME_NARROW, isHomeNarrow, - privateNarrow, - isPrivateNarrow, - groupNarrow, - isGroupNarrow, + pmNarrowFromEmail, + is1to1PmNarrow, + pmNarrowFromEmails, + isGroupPmNarrow, specialNarrow, isSpecialNarrow, ALL_PRIVATE_NARROW, @@ -16,7 +16,7 @@ import { isTopicNarrow, SEARCH_NARROW, isSearchNarrow, - isPrivateOrGroupNarrow, + isPmNarrow, isMessageInNarrow, isSameNarrow, isStreamOrTopicNarrow, @@ -38,9 +38,9 @@ describe('HOME_NARROW', () => { }); }); -describe('privateNarrow', () => { +describe('pmNarrowFromEmail', () => { test('produces an one item list, pm-with operator and single email', () => { - expect(privateNarrow('bob@example.com')).toEqual([ + expect(pmNarrowFromEmail('bob@example.com')).toEqual([ { operator: 'pm-with', operand: 'bob@example.com', @@ -49,10 +49,10 @@ describe('privateNarrow', () => { }); test('if operator is "pm-with" and only one email, then it is a private narrow', () => { - expect(isPrivateNarrow(HOME_NARROW)).toBe(false); - expect(isPrivateNarrow(privateNarrow('bob@example.com'))).toBe(true); + expect(is1to1PmNarrow(HOME_NARROW)).toBe(false); + expect(is1to1PmNarrow(pmNarrowFromEmail('bob@example.com'))).toBe(true); expect( - isPrivateNarrow([ + is1to1PmNarrow([ { operator: 'pm-with', operand: 'bob@example.com', @@ -62,9 +62,9 @@ describe('privateNarrow', () => { }); }); -describe('groupNarrow', () => { +describe('pmNarrowFromEmails', () => { test('returns a narrow with specified recipients', () => { - expect(groupNarrow(['bob@example.com', 'john@example.com'])).toEqual([ + expect(pmNarrowFromEmails(['bob@example.com', 'john@example.com'])).toEqual([ { operator: 'pm-with', operand: 'bob@example.com,john@example.com', @@ -73,11 +73,11 @@ describe('groupNarrow', () => { }); test('a group narrow is only private chat with more than one recipients', () => { - expect(isGroupNarrow(HOME_NARROW)).toBe(false); - expect(isGroupNarrow(privateNarrow('bob@example.com'))).toBe(false); - expect(isGroupNarrow(groupNarrow(['bob@example.com', 'john@example.com']))).toBe(true); + expect(isGroupPmNarrow(HOME_NARROW)).toBe(false); + expect(isGroupPmNarrow(pmNarrowFromEmail('bob@example.com'))).toBe(false); + expect(isGroupPmNarrow(pmNarrowFromEmails(['bob@example.com', 'john@example.com']))).toBe(true); expect( - isGroupNarrow([ + isGroupPmNarrow([ { operator: 'pm-with', operand: 'bob@example.com', @@ -85,7 +85,7 @@ describe('groupNarrow', () => { ]), ).toBe(false); expect( - isGroupNarrow([ + isGroupPmNarrow([ { operator: 'pm-with', operand: 'bob@example.com,john@example.com', @@ -95,14 +95,14 @@ describe('groupNarrow', () => { }); }); -describe('isPrivateOrGroupNarrow', () => { +describe('isPmNarrow', () => { test('a private or group narrow is any "pm-with" narrow', () => { - expect(isPrivateOrGroupNarrow(undefined)).toBe(false); - expect(isPrivateOrGroupNarrow(HOME_NARROW)).toBe(false); - expect(isPrivateOrGroupNarrow(privateNarrow('bob@example.com'))).toBe(true); - expect(isPrivateOrGroupNarrow(groupNarrow(['bob@example.com', 'john@example.com']))).toBe(true); + expect(isPmNarrow(undefined)).toBe(false); + expect(isPmNarrow(HOME_NARROW)).toBe(false); + expect(isPmNarrow(pmNarrowFromEmail('bob@example.com'))).toBe(true); + expect(isPmNarrow(pmNarrowFromEmails(['bob@example.com', 'john@example.com']))).toBe(true); expect( - isPrivateOrGroupNarrow([ + isPmNarrow([ { operator: 'pm-with', operand: 'bob@example.com', @@ -110,7 +110,7 @@ describe('isPrivateOrGroupNarrow', () => { ]), ).toBe(true); expect( - isPrivateOrGroupNarrow([ + isPmNarrow([ { operator: 'pm-with', operand: 'bob@example.com,john@example.com', @@ -126,10 +126,10 @@ describe('isStreamOrTopicNarrow', () => { expect(isStreamOrTopicNarrow(streamNarrow('some stream'))).toBe(true); expect(isStreamOrTopicNarrow(topicNarrow('some stream', 'some topic'))).toBe(true); expect(isStreamOrTopicNarrow(HOME_NARROW)).toBe(false); - expect(isStreamOrTopicNarrow(privateNarrow('a@a.com'))).toBe(false); - expect(isStreamOrTopicNarrow(groupNarrow(['john@example.com', 'mark@example.com']))).toBe( - false, - ); + expect(isStreamOrTopicNarrow(pmNarrowFromEmail('a@a.com'))).toBe(false); + expect( + isStreamOrTopicNarrow(pmNarrowFromEmails(['john@example.com', 'mark@example.com'])), + ).toBe(false); expect(isStreamOrTopicNarrow(STARRED_NARROW)).toBe(false); }); }); @@ -238,7 +238,7 @@ describe('isMessageInNarrow', () => { ['PM', false, eg.pmMessage()], ]], - ['1:1 PM conversation, non-self', privateNarrow(eg.otherUser.email), [ + ['1:1 PM conversation, non-self', pmNarrowFromEmail(eg.otherUser.email), [ ['matching PM, inbound', true, eg.pmMessage()], ['matching PM, outbound', true, eg.pmMessage({ sender: eg.selfUser })], ['self-1:1 message', false, eg.pmMessage({ sender: eg.selfUser, recipients: [eg.selfUser] })], @@ -246,7 +246,7 @@ describe('isMessageInNarrow', () => { ['group-PM including this user, outbound', false, eg.pmMessage({ sender: eg.selfUser, recipients: [eg.selfUser, eg.otherUser, eg.thirdUser] })], ['stream message', false, eg.streamMessage()], ]], - ['self-1:1 conversation', privateNarrow(eg.selfUser.email), [ + ['self-1:1 conversation', pmNarrowFromEmail(eg.selfUser.email), [ ['self-1:1 message', true, eg.pmMessage({ sender: eg.selfUser, recipients: [eg.selfUser] })], ['other 1:1 message, inbound', false, eg.pmMessage()], ['other 1:1 message, outbound', false, eg.pmMessage({ sender: eg.selfUser })], @@ -254,7 +254,7 @@ describe('isMessageInNarrow', () => { ['group-PM, outbound', false, eg.pmMessage({ sender: eg.selfUser, recipients: [eg.selfUser, eg.otherUser, eg.thirdUser] })], ['stream message', false, eg.streamMessage()], ]], - ['group-PM conversation', groupNarrow([eg.otherUser.email, eg.thirdUser.email]), [ + ['group-PM conversation', pmNarrowFromEmails([eg.otherUser.email, eg.thirdUser.email]), [ ['matching group-PM, inbound', true, eg.pmMessage({ recipients: [eg.selfUser, eg.otherUser, eg.thirdUser] })], ['matching group-PM, outbound', true, eg.pmMessage({ sender: eg.selfUser, recipients: [eg.selfUser, eg.otherUser, eg.thirdUser] })], ['1:1 within group, inbound', false, eg.pmMessage()], @@ -262,7 +262,7 @@ describe('isMessageInNarrow', () => { ['self-1:1 message', false, eg.pmMessage({ sender: eg.selfUser, recipients: [eg.selfUser] })], ['stream message', false, eg.streamMessage()], ]], - ['group-PM conversation, including self', groupNarrow([eg.selfUser.email, eg.otherUser.email, eg.thirdUser.email]), [ + ['group-PM conversation, including self', pmNarrowFromEmails([eg.selfUser.email, eg.otherUser.email, eg.thirdUser.email]), [ ['matching group-PM, inbound', true, eg.pmMessage({ recipients: [eg.selfUser, eg.otherUser, eg.thirdUser] })], ['matching group-PM, outbound', true, eg.pmMessage({ sender: eg.selfUser, recipients: [eg.selfUser, eg.otherUser, eg.thirdUser] })], ['1:1 within group, inbound', false, eg.pmMessage()], @@ -304,12 +304,12 @@ describe('getNarrowFromMessage', () => { eg.pmMessage({ sender: eg.selfUser, recipients: [eg.selfUser] }), eg.selfUser, ), - ).toEqual(privateNarrow(eg.selfUser.email)); + ).toEqual(pmNarrowFromEmail(eg.selfUser.email)); }); test('for 1:1 PM, returns a 1:1 PM narrow', () => { const message = eg.pmMessage(); - const expectedNarrow = privateNarrow(eg.otherUser.email); + const expectedNarrow = pmNarrowFromEmail(eg.otherUser.email); const actualNarrow = getNarrowFromMessage(message, eg.selfUser); @@ -320,7 +320,7 @@ describe('getNarrowFromMessage', () => { const message = eg.pmMessage({ recipients: [eg.selfUser, eg.otherUser, eg.thirdUser], }); - const expectedNarrow = groupNarrow([eg.otherUser.email, eg.thirdUser.email]); + const expectedNarrow = pmNarrowFromEmails([eg.otherUser.email, eg.thirdUser.email]); const actualNarrow = getNarrowFromMessage(message, eg.selfUser); diff --git a/src/utils/internalLinks.js b/src/utils/internalLinks.js index d81097de323..c4d73eb7e6e 100644 --- a/src/utils/internalLinks.js +++ b/src/utils/internalLinks.js @@ -1,7 +1,7 @@ /* @flow strict-local */ import { addBreadcrumb } from '@sentry/react-native'; import type { Narrow, Stream, User } from '../types'; -import { topicNarrow, streamNarrow, groupNarrow, specialNarrow } from './narrow'; +import { topicNarrow, streamNarrow, pmNarrowFromEmails, specialNarrow } from './narrow'; import { pmKeyRecipientsFromIds } from './recipient'; import { isUrlOnRealm } from './url'; @@ -130,7 +130,7 @@ export const getNarrowFromLink = ( case 'pm': { const ids = parsePmOperand(paths[1], usersById, ownUserId); const users = pmKeyRecipientsFromIds(ids, usersById, ownUserId); - return users && groupNarrow(users.map(u => u.email)); + return users && pmNarrowFromEmails(users.map(u => u.email)); } case 'topic': return topicNarrow(parseStreamOperand(paths[1], streamsById), parseTopicOperand(paths[3])); diff --git a/src/utils/narrow.js b/src/utils/narrow.js index 6568c760d4b..1dbcf1d5aba 100644 --- a/src/utils/narrow.js +++ b/src/utils/narrow.js @@ -19,10 +19,20 @@ export const HOME_NARROW: Narrow = []; export const HOME_NARROW_STR: string = '[]'; -export const privateNarrow = (email: string): Narrow => [ +/** + * A PM narrow -- either 1:1 or group. + * + * Private to this module because the input format is kind of odd. + * Use `pmNarrowFromEmail` or `pmNarrowFromEmails` instead. + * + * For the quirks of the underlying format in the Zulip API, see: + * https://zulipchat.com/api/construct-narrow + * https://github.com/zulip/zulip/issues/13167 + */ +const pmNarrowByString = (emails: string): Narrow => [ { operator: 'pm-with', - operand: email, + operand: emails, }, ]; @@ -66,12 +76,10 @@ export const privateNarrow = (email: string): Narrow => [ // notification's pm_users, which is sorted. // * Good: messageHeaderAsHtml: comes from pmKeyRecipientsFromMessage, // which filters and sorts by ID -export const groupNarrow = (emails: string[]): Narrow => [ - { - operator: 'pm-with', - operand: emails.join(), - }, -]; +export const pmNarrowFromEmails = (emails: string[]): Narrow => pmNarrowByString(emails.join()); + +/** Convenience wrapper for `pmNarrowFromEmails`. */ +export const pmNarrowFromEmail = (email: string): Narrow => pmNarrowFromEmails([email]); export const specialNarrow = (operand: string): Narrow => [ { @@ -119,8 +127,7 @@ export const SEARCH_NARROW = (query: string): Narrow => [ type NarrowCases = {| home: () => T, - pm: (email: string) => T, - groupPm: (emails: string[]) => T, + pm: (emails: string[]) => T, starred: () => T, mentioned: () => T, allPrivate: () => T, @@ -139,12 +146,9 @@ export function caseNarrow(narrow: Narrow, cases: NarrowCases): T { case 0: return cases.home(); case 1: switch (narrow[0].operator) { - case 'pm-with': - if (narrow[0].operand.indexOf(',') < 0) { - return cases.pm(narrow[0].operand); - } else { /* eslint-disable-line */ + case 'pm-with': { const emails = narrow[0].operand.split(','); - return cases.groupPm(emails); + return cases.pm(emails); } case 'is': switch (narrow[0].operand) { @@ -172,7 +176,6 @@ export function caseNarrowPartial(narrow: Narrow, cases: $Shape( ({ home: defaultCase, pm: defaultCase, - groupPm: defaultCase, starred: defaultCase, mentioned: defaultCase, allPrivate: defaultCase, @@ -212,11 +214,11 @@ export function caseNarrowDefault( export const isHomeNarrow = (narrow?: Narrow): boolean => !!narrow && caseNarrowDefault(narrow, { home: () => true }, () => false); -export const isPrivateNarrow = (narrow?: Narrow): boolean => - !!narrow && caseNarrowDefault(narrow, { pm: () => true }, () => false); +export const is1to1PmNarrow = (narrow?: Narrow): boolean => + !!narrow && caseNarrowDefault(narrow, { pm: emails => emails.length === 1 }, () => false); -export const isGroupNarrow = (narrow?: Narrow): boolean => - !!narrow && caseNarrowDefault(narrow, { groupPm: () => true }, () => false); +export const isGroupPmNarrow = (narrow?: Narrow): boolean => + !!narrow && caseNarrowDefault(narrow, { pm: emails => emails.length > 1 }, () => false); /** * The recipients' emails if a group PM narrow; else error. @@ -224,11 +226,18 @@ export const isGroupNarrow = (narrow?: Narrow): boolean => * Any use of this probably means something higher up should be refactored * to use caseNarrow. */ -export const emailsOfGroupNarrow = (narrow: Narrow): string[] => - caseNarrowPartial(narrow, { groupPm: emails => emails }); +export const emailsOfGroupPmNarrow = (narrow: Narrow): string[] => + caseNarrowPartial(narrow, { + pm: emails => { + if (emails.length === 1) { + throw new Error('emailsOfGroupPmNarrow: got 1:1 narrow'); + } + return emails; + }, + }); -export const isPrivateOrGroupNarrow = (narrow?: Narrow): boolean => - !!narrow && caseNarrowDefault(narrow, { pm: () => true, groupPm: () => true }, () => false); +export const isPmNarrow = (narrow?: Narrow): boolean => + !!narrow && caseNarrowDefault(narrow, { pm: () => true }, () => false); export const isSpecialNarrow = (narrow?: Narrow): boolean => !!narrow @@ -269,39 +278,34 @@ export const isMessageInNarrow = ( flags: $ReadOnlyArray, narrow: Narrow, ownEmail: string, -): boolean => { - const matchPmRecipients = (emails: string[]) => { - if (message.type !== 'private') { - return false; - } - const recipients = recipientsOfPrivateMessage(message); - const narrowAsRecipients = emails.map(email => ({ email })); - return ( - normalizeRecipientsSansMe(recipients, ownEmail) - === normalizeRecipientsSansMe(narrowAsRecipients, ownEmail) - ); - }; - - return caseNarrow(narrow, { +): boolean => + caseNarrow(narrow, { home: () => true, stream: name => message.type === 'stream' && name === streamNameOfStreamMessage(message), topic: (streamName, topic) => message.type === 'stream' && streamName === streamNameOfStreamMessage(message) && topic === message.subject, - pm: email => matchPmRecipients([email]), - groupPm: matchPmRecipients, + pm: emails => { + if (message.type !== 'private') { + return false; + } + const recipients = recipientsOfPrivateMessage(message); + const narrowAsRecipients = emails.map(email => ({ email })); + return ( + normalizeRecipientsSansMe(recipients, ownEmail) + === normalizeRecipientsSansMe(narrowAsRecipients, ownEmail) + ); + }, starred: () => flags.includes('starred'), mentioned: () => flags.includes('mentioned') || flags.includes('wildcard_mentioned'), allPrivate: () => message.type === 'private', search: () => false, }); -}; export const canSendToNarrow = (narrow: Narrow): boolean => caseNarrow(narrow, { pm: () => true, - groupPm: () => true, stream: () => true, topic: () => true, home: () => false, @@ -321,7 +325,7 @@ export const canSendToNarrow = (narrow: Narrow): boolean => // TODO: do that, or just make this a private local helper of its one caller export const getNarrowFromMessage = (message: Message | Outbox, ownUser: User) => { if (message.type === 'private') { - return groupNarrow(pmKeyRecipientsFromMessage(message, ownUser).map(x => x.email)); + return pmNarrowFromEmails(pmKeyRecipientsFromMessage(message, ownUser).map(x => x.email)); } else { const streamName = streamNameOfStreamMessage(message); const topic = message.subject; diff --git a/src/webview/html/messageHeaderAsHtml.js b/src/webview/html/messageHeaderAsHtml.js index c43d9b504e3..c47115a6cd2 100644 --- a/src/webview/html/messageHeaderAsHtml.js +++ b/src/webview/html/messageHeaderAsHtml.js @@ -5,8 +5,8 @@ import type { BackgroundData } from '../MessageList'; import { streamNarrow, topicNarrow, - privateNarrow, - groupNarrow, + pmNarrowFromEmail, + pmNarrowFromEmails, caseNarrow, } from '../../utils/narrow'; import { foregroundColorFromBackground } from '../../utils/color'; @@ -34,7 +34,6 @@ export default ( topic: () => 'none', pm: () => 'none', - groupPm: () => 'none', home: () => 'full', starred: () => 'full', @@ -94,8 +93,8 @@ export default ( const keyRecipients = pmKeyRecipientsFromMessage(item, ownUser); const narrowObj = keyRecipients.length === 1 - ? privateNarrow(keyRecipients[0].email) - : groupNarrow(keyRecipients.map(r => r.email)); + ? pmNarrowFromEmail(keyRecipients[0].email) + : pmNarrowFromEmails(keyRecipients.map(r => r.email)); const privateNarrowStr = JSON.stringify(narrowObj); const uiRecipients = pmUiRecipientsFromMessage(item, ownUser);