Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/howto/shared.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ It's published to NPM as the package `@zulip/shared`.
* To develop and test the shared code, use `yarn link` so that the
shared code comes from your local zulip/zulip worktree (just like
the mobile app code comes from your local zulip-mobile worktree)
rather than from NPM. See our [yarn-link.md][].
rather than from NPM. See our [yarn-link.md](yarn-link.md).

* For a new module `static/shared/js/foo.js`, you'll typically want to
add a file `foo.js.flow` next to it with type definitions. See
Expand Down Expand Up @@ -61,9 +61,10 @@ $ cd static/shared # the root of the @zulip/shared package's source
$ git checkout master
$ git pull --ff-only

$ npm version patch --no-git-tag-version \
--message 'shared: Bump version to %s.'
# (These steps can probably become a `version` NPM script.)
$ npm version patch --no-git-tag-version
# Suppose the new version is 0.0.3. Then:
$ git commit -am 'shared: Bump version to 0.0.3.'
$ git tag shared-0.0.3

$ git log --stat -p upstream/master.. # check your work!
Expand Down
7 changes: 7 additions & 0 deletions docs/howto/yarn-link.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ $ readlink node_modules/@zulip/shared
# ... to the worktree for the package source.
$ readlink -f node_modules/@zulip/shared
/home/greg/z/zulip/static/shared

# Restart Flow to make it notice the symlink.
# (Then it'll automatically notice edits, as usual.)
$ npx flow stop && npx flow start

```

When done, be sure to run `yarn unlink` to go back to letting the
Expand All @@ -59,10 +64,12 @@ Quick reference for the second and subsequent time you do it:
```
# in your zulip-mobile clone
$ yarn link @zulip/shared && yarn
$ npx flow stop && npx flow start

# ... develop, test, etc. ...

$ yarn unlink @zulip/shared && yarn install --force
# no need to restart Flow in this direction
```

### Making our toolchain work
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@react-native-community/netinfo": "^5.9.5",
"@sentry/react-native": "^1.0.9",
"@unimodules/core": "~5.3.0",
"@zulip/shared": "^0.0.2",
"@zulip/shared": "^0.0.4",
"base-64": "^0.1.0",
"blueimp-md5": "^2.10.0",
"color": "^3.0.0",
Expand Down
31 changes: 16 additions & 15 deletions src/api/modelTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,21 +296,22 @@ export type Topic = {|
//
//

export type NarrowOperator =
| 'is'
| 'in'
| 'near'
| 'id'
| 'stream'
| 'topic'
| 'sender'
| 'pm-with'
| 'search';

export type NarrowElement = $ReadOnly<{|
operand: string,
operator: NarrowOperator,
|}>;
// See docs: https://zulip.com/api/construct-narrow
// prettier-ignore
/* eslint-disable semi-style */
export type NarrowElement =
| {| +operator: 'is' | 'in' | 'topic' | 'search', +operand: string |}
// The server started accepting numeric user IDs and stream IDs in 2.1:
// * `stream` since 2.1-dev-2302-g3680393b4
// * `group-pm-with` since 2.1-dev-1813-gb338fd130
// * `sender` since 2.1-dev-1812-gc067c155a
// * `pm-with` since 2.1-dev-1350-gd7b4de234
| {| +operator: 'stream', +operand: string | number |} // stream ID
| {| +operator: 'pm-with', +operand: string | $ReadOnlyArray<number> |} // user IDs
| {| +operator: 'sender', +operand: string | number |} // user ID
| {| +operator: 'group-pm-with', +operand: string | number |} // user ID
| {| +operator: 'near' | 'id', +operand: number |} // message ID
;

/**
* A narrow, in the form used in the Zulip API at get-messages.
Expand Down
25 changes: 11 additions & 14 deletions src/typing/__tests__/typingSelectors-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getCurrentTypingUsers } from '../typingSelectors';
import { HOME_NARROW, pm1to1NarrowFromUser, pmNarrowFromUsersUnsafe } from '../../utils/narrow';
import { NULL_ARRAY } from '../../nullObjects';
import * as eg from '../../__tests__/lib/exampleData';
import { normalizeRecipientsAsUserIds } from '../../utils/recipient';
import { pmTypingKeyFromPmKeyIds } from '../../utils/recipient';

describe('getCurrentTypingUsers', () => {
test('return NULL_ARRAY when current narrow is not private or group', () => {
Expand Down Expand Up @@ -33,25 +33,27 @@ describe('getCurrentTypingUsers', () => {
test('when two people are typing, return details for all of them', () => {
const user1 = eg.makeUser();
const user2 = eg.makeUser();
const users = [user1, user2].sort((a, b) => a.user_id - b.user_id);
const userIds = users.map(u => u.user_id);

const normalizedRecipients = normalizeRecipientsAsUserIds([user1.user_id, user2.user_id]);
const normalizedRecipients = pmTypingKeyFromPmKeyIds(userIds);

const state = eg.reduxState({
typing: {
[normalizedRecipients]: { userIds: [user1.user_id, user2.user_id] },
[normalizedRecipients]: { userIds },
},
users: [user1, user2],
});

const typingUsers = getCurrentTypingUsers(state, pmNarrowFromUsersUnsafe([user1, user2]));
const typingUsers = getCurrentTypingUsers(state, pmNarrowFromUsersUnsafe(users));

expect(typingUsers).toEqual([user1, user2]);
expect(typingUsers).toEqual(users);
});

test('when in private narrow but different user is typing return NULL_ARRAY', () => {
const user1 = eg.makeUser();
const user2 = eg.makeUser();
const normalizedRecipients = normalizeRecipientsAsUserIds([user1.user_id]);
const normalizedRecipients = pmTypingKeyFromPmKeyIds([user1.user_id]);

const state = eg.reduxState({
typing: {
Expand All @@ -68,22 +70,17 @@ describe('getCurrentTypingUsers', () => {
test('when in group narrow and someone is typing in that narrow return details', () => {
const expectedUser = eg.makeUser();
const anotherUser = eg.makeUser();
const users = [expectedUser, anotherUser].sort((a, b) => a.user_id - b.user_id);

const normalizedRecipients = normalizeRecipientsAsUserIds([
expectedUser.user_id,
anotherUser.user_id,
]);
const normalizedRecipients = pmTypingKeyFromPmKeyIds(users.map(u => u.user_id));
const state = eg.reduxState({
typing: {
[normalizedRecipients]: { userIds: [expectedUser.user_id] },
},
users: [expectedUser, anotherUser],
});

const typingUsers = getCurrentTypingUsers(
state,
pmNarrowFromUsersUnsafe([expectedUser, anotherUser]),
);
const typingUsers = getCurrentTypingUsers(state, pmNarrowFromUsersUnsafe(users));

expect(typingUsers).toEqual([expectedUser]);
});
Expand Down
6 changes: 3 additions & 3 deletions src/typing/typingReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
LOGIN_SUCCESS,
ACCOUNT_SWITCH,
} from '../actionConstants';
import { normalizeRecipientsAsUserIdsSansMe } from '../utils/recipient';
import { pmTypingKeyFromRecipients } from '../utils/recipient';
import { NULL_OBJECT } from '../nullObjects';

const initialState: TypingState = NULL_OBJECT;
Expand All @@ -20,7 +20,7 @@ const eventTypingStart = (state, action) => {
return state;
}

const normalizedRecipients: string = normalizeRecipientsAsUserIdsSansMe(
const normalizedRecipients: string = pmTypingKeyFromRecipients(
action.recipients.map(r => r.user_id),
action.ownUserId,
);
Expand All @@ -47,7 +47,7 @@ const eventTypingStart = (state, action) => {
};

const eventTypingStop = (state, action) => {
const normalizedRecipients: string = normalizeRecipientsAsUserIdsSansMe(
const normalizedRecipients: string = pmTypingKeyFromRecipients(
action.recipients.map(r => r.user_id),
action.ownUserId,
);
Expand Down
20 changes: 6 additions & 14 deletions src/typing/typingSelectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,22 @@ import { createSelector } from 'reselect';

import type { Narrow, Selector, UserOrBot } from '../types';
import { getTyping } from '../directSelectors';
import { emailsOfPmNarrow, isPmNarrow } from '../utils/narrow';
import { normalizeRecipientsAsUserIds } from '../utils/recipient';
import { userIdsOfPmNarrow, isPmNarrow } from '../utils/narrow';
import { pmTypingKeyFromPmKeyIds } from '../utils/recipient';
import { NULL_ARRAY, NULL_USER } from '../nullObjects';
import { getAllUsersById, getAllUsersByEmail } from '../users/userSelectors';
import { getAllUsersById } from '../users/userSelectors';

export const getCurrentTypingUsers: Selector<$ReadOnlyArray<UserOrBot>, Narrow> = createSelector(
(state, narrow) => narrow,
state => getTyping(state),
state => getAllUsersById(state),
state => getAllUsersByEmail(state),
(narrow, typing, allUsersById, allUsersByEmail): UserOrBot[] => {
(narrow, typing, allUsersById): UserOrBot[] => {
if (!isPmNarrow(narrow)) {
return NULL_ARRAY;
}

const recipients = emailsOfPmNarrow(narrow).map(email => {
const userId = allUsersByEmail.get(email)?.user_id;
if (userId === undefined) {
throw new Error(`Narrow contains email '${email}' that does not map to any user.`);
}
return userId;
});
const normalizedRecipients = normalizeRecipientsAsUserIds(recipients);
const currentTyping = typing[normalizedRecipients];
const typingKey = pmTypingKeyFromPmKeyIds(userIdsOfPmNarrow(narrow));
const currentTyping = typing[typingKey];

if (!currentTyping || !currentTyping.userIds) {
return NULL_ARRAY;
Expand Down
17 changes: 5 additions & 12 deletions src/users/usersActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ 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 { isPmNarrow, emailsOfPmNarrow } from '../utils/narrow';
import { getAllUsersByEmail, getUserForId } from './userSelectors';
import { isPmNarrow, userIdsOfPmNarrow } from '../utils/narrow';
import { getUserForId } from './userSelectors';
import { ZulipVersion } from '../utils/zulipVersion';

export const reportPresence = (isActive: boolean = true, newUserInput: boolean = false) => async (
Expand Down Expand Up @@ -46,11 +46,11 @@ const typingWorker = (state: GlobalState) => {
return {
get_current_time: () => new Date().getTime(),

notify_server_start: (user_ids_array: number[]) => {
notify_server_start: (user_ids_array: $ReadOnlyArray<number>) => {
api.typing(auth, getRecipients(user_ids_array), 'start');
},

notify_server_stop: (user_ids_array: number[]) => {
notify_server_stop: (user_ids_array: $ReadOnlyArray<number>) => {
api.typing(auth, getRecipients(user_ids_array), 'stop');
},
};
Expand All @@ -64,14 +64,7 @@ export const sendTypingStart = (narrow: Narrow) => async (
return;
}

const allUsersByEmail = getAllUsersByEmail(getState());
const recipientIds = emailsOfPmNarrow(narrow).map(email => {
const user = allUsersByEmail.get(email);
if (!user) {
throw new Error('unknown user');
}
return user.user_id;
});
const recipientIds = userIdsOfPmNarrow(narrow);
typing_status.update(typingWorker(getState()), recipientIds);
};

Expand Down
8 changes: 4 additions & 4 deletions src/utils/narrow.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,13 @@ export const isGroupPmNarrow = (narrow?: Narrow): boolean =>
!!narrow && caseNarrowDefault(narrow, { pm: (emails, ids) => ids.length > 1 }, () => false);

/**
* The "PM key recipients" emails for a PM narrow; else error.
* The "PM key recipients" IDs for a PM narrow; else error.
*
* This is the same list of users that can appear in a `PmKeyRecipients` or
* `PmKeyUsers`, but contains only their emails.
* `PmKeyUsers`, but contains only their user IDs.
*/
export const emailsOfPmNarrow = (narrow: Narrow): $ReadOnlyArray<string> =>
caseNarrowPartial(narrow, { pm: emails => emails });
export const userIdsOfPmNarrow = (narrow: Narrow): $ReadOnlyArray<number> =>
caseNarrowPartial(narrow, { pm: (emails, ids) => ids });

/**
* The stream name for a stream or topic narrow; else error.
Expand Down
37 changes: 37 additions & 0 deletions src/utils/recipient.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,43 @@ export const pmUnreadsKeyFromPmKeyIds = (
}
};

/**
* The key for a PM thread in typing-status data, given the IDs we use generally.
*
* This produces the key string we use in `state.typing`, given the list of
* users that `pmKeyRecipientsFromMessage` would provide and that we use in
* most of our other data structures indexed on narrows.
*
* See also `pmTypingKeyFromRecipients`.
*/
// That key string is: just the usual "PM key" list of users, stringified
// and comma-separated.
//
// TODO: It'd be neat to have another opaque type like PmKeyIds, for this
// and pmUnreadsKeyFromPmKeyIds to consume. Perhaps simplest to do after
// Narrow no longer contains emails.
export const pmTypingKeyFromPmKeyIds = (userIds: $ReadOnlyArray<number>): string =>
userIds.join(',');

/**
* The key for a PM thread in typing-status data, given a recipients list.
*
* This produces the key string we use in `state.typing`, given the list of
* users that a typing-status event provides in `recipients`.
*
* See also `pmTypingKeyFromPmKeyIds`.
*/
// This implementation works because:
// * For all but self-PMs, we want the list of non-self users, which
// `filterRecipientsAsUserIds` will give regardless of whether self was
// in the input. (So it doesn't matter what convention the server uses
// for these events.)
// * Self-PMs don't have typing-status events in the first place.
export const pmTypingKeyFromRecipients = (
recipients: $ReadOnlyArray<number>,
ownUserId: number,
): string => pmTypingKeyFromPmKeyIds(filterRecipientsAsUserIds(recipients, ownUserId));

export const isSameRecipient = (
message1: Message | Outbox,
message2: Message | Outbox,
Expand Down
23 changes: 13 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2725,12 +2725,13 @@
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==

"@zulip/shared@^0.0.2":
version "0.0.2"
resolved "https://registry.yarnpkg.com/@zulip/shared/-/shared-0.0.2.tgz#14613950551c96d88f7b8929056ac1b640f15343"
integrity sha512-Thts/Mu/9ry4M0K4RjL4Gym8MTxkcnk7lkBuH8hiMkIEbWoK5CaHSRxvn96wCsUJJcQ7A99Sd+RVQLPMorRLAg==
"@zulip/shared@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@zulip/shared/-/shared-0.0.4.tgz#147a939bd71a284ed6b890843f9e4a003466637e"
integrity sha512-qsSWfXSFUoaWDa8GeoIOKpQVGgs744DRZ2fspw52NeWa7Ac8GnhxIvu9EohBTmq6jApRIO5wg/8DkgnvsSqxyA==
dependencies:
underscore "^1.9.1"
katex "^0.12.0"
lodash "^4.17.19"

abab@^2.0.0, abab@^2.0.3:
version "2.0.5"
Expand Down Expand Up @@ -7857,6 +7858,13 @@ katex@^0.11.1:
dependencies:
commander "^2.19.0"

katex@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/katex/-/katex-0.12.0.tgz#2fb1c665dbd2b043edcf8a1f5c555f46beaa0cb9"
integrity sha512-y+8btoc/CK70XqcHqjxiGWBOeIL8upbS0peTPXTvgrh21n1RiWWcIpSWM+4uXq+IAgNh9YYQWdc7LVDPDAEEAg==
dependencies:
commander "^2.19.0"

katex@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/katex/-/katex-0.7.1.tgz#06bb5298efad05e1e7228035ba8e1591f3061b8f"
Expand Down Expand Up @@ -11788,11 +11796,6 @@ ultron@1.0.x:
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
integrity sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=

underscore@^1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==

unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
Expand Down