2021-03-16 23:38:59 +01:00
|
|
|
import * as blueslip from "./blueslip";
|
2021-03-19 14:38:22 +01:00
|
|
|
import * as compose_fade_users from "./compose_fade_users";
|
2021-02-28 01:12:35 +01:00
|
|
|
import * as hash_util from "./hash_util";
|
2021-04-13 06:51:54 +02:00
|
|
|
import {$t} from "./i18n";
|
2021-03-25 22:35:45 +01:00
|
|
|
import {page_params} from "./page_params";
|
2021-02-28 01:12:35 +01:00
|
|
|
import * as people from "./people";
|
|
|
|
import * as presence from "./presence";
|
2021-02-28 01:14:36 +01:00
|
|
|
import * as timerender from "./timerender";
|
2021-02-28 21:30:38 +01:00
|
|
|
import * as unread from "./unread";
|
2021-02-28 01:12:35 +01:00
|
|
|
import * as user_status from "./user_status";
|
|
|
|
import * as util from "./util";
|
|
|
|
|
2018-04-19 15:46:56 +02:00
|
|
|
/*
|
|
|
|
|
|
|
|
This is the main model code for building the buddy list.
|
|
|
|
We also rely on presence.js to compute the actual presence
|
|
|
|
for users. We glue in other "people" data and do
|
|
|
|
filtering/sorting of the data that we'll send into the view.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export const max_size_before_shrinking = 600;
|
2018-04-20 18:22:28 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const fade_config = {
|
2020-07-20 22:18:43 +02:00
|
|
|
get_user_id(item) {
|
2018-04-22 17:46:20 +02:00
|
|
|
return item.user_id;
|
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
fade(item) {
|
2018-04-22 17:46:20 +02:00
|
|
|
item.faded = true;
|
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
unfade(item) {
|
2018-04-22 17:46:20 +02:00
|
|
|
item.faded = false;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function get_user_circle_class(user_id) {
|
|
|
|
const status = buddy_status(user_id);
|
2019-02-17 02:10:42 +01:00
|
|
|
|
|
|
|
switch (status) {
|
2020-07-15 02:14:03 +02:00
|
|
|
case "active":
|
|
|
|
return "user_circle_green";
|
|
|
|
case "idle":
|
|
|
|
return "user_circle_orange";
|
|
|
|
case "away_them":
|
|
|
|
case "away_me":
|
|
|
|
return "user_circle_empty_line";
|
|
|
|
default:
|
|
|
|
return "user_circle_empty";
|
2019-02-17 02:10:42 +01:00
|
|
|
}
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2019-02-17 02:10:42 +01:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function status_description(user_id) {
|
|
|
|
const status = buddy_status(user_id);
|
2019-08-04 14:57:32 +02:00
|
|
|
|
|
|
|
switch (status) {
|
2020-07-15 02:14:03 +02:00
|
|
|
case "active":
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "Active"});
|
2020-07-15 02:14:03 +02:00
|
|
|
case "idle":
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "Idle"});
|
2020-07-15 02:14:03 +02:00
|
|
|
case "away_them":
|
|
|
|
case "away_me":
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "Unavailable"});
|
2020-07-15 02:14:03 +02:00
|
|
|
default:
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "Offline"});
|
2019-08-04 14:57:32 +02:00
|
|
|
}
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2019-08-04 14:57:32 +02:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function level(user_id) {
|
2018-12-18 18:50:58 +01:00
|
|
|
if (people.is_my_user_id(user_id)) {
|
|
|
|
// Always put current user at the top.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
const status = buddy_status(user_id);
|
2018-09-08 15:54:52 +02:00
|
|
|
|
2018-04-20 18:22:28 +02:00
|
|
|
switch (status) {
|
2020-07-15 02:14:03 +02:00
|
|
|
case "active":
|
|
|
|
return 1;
|
|
|
|
case "idle":
|
|
|
|
return 2;
|
|
|
|
case "away_them":
|
|
|
|
return 3;
|
|
|
|
default:
|
|
|
|
return 3;
|
2018-04-19 15:46:56 +02:00
|
|
|
}
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2018-04-19 15:46:56 +02:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function buddy_status(user_id) {
|
2018-12-19 21:16:03 +01:00
|
|
|
if (user_status.is_away(user_id)) {
|
|
|
|
if (people.is_my_user_id(user_id)) {
|
2020-07-15 01:29:15 +02:00
|
|
|
return "away_me";
|
2018-12-19 21:16:03 +01:00
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return "away_them";
|
2018-12-19 21:16:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// get active/idle/etc.
|
|
|
|
return presence.get_status(user_id);
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2018-12-19 21:16:03 +01:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function compare_function(a, b) {
|
|
|
|
const level_a = level(a);
|
|
|
|
const level_b = level(b);
|
2019-11-02 00:06:25 +01:00
|
|
|
const diff = level_a - level_b;
|
2018-04-19 15:46:56 +02:00
|
|
|
if (diff !== 0) {
|
|
|
|
return diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort equivalent PM names alphabetically
|
2020-02-05 14:30:59 +01:00
|
|
|
const person_a = people.get_by_user_id(a);
|
|
|
|
const person_b = people.get_by_user_id(b);
|
2018-04-19 15:46:56 +02:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const full_name_a = person_a ? person_a.full_name : "";
|
|
|
|
const full_name_b = person_b ? person_b.full_name : "";
|
2018-04-19 15:46:56 +02:00
|
|
|
|
|
|
|
return util.strcmp(full_name_a, full_name_b);
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2018-04-19 15:46:56 +02:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function sort_users(user_ids) {
|
2018-04-19 15:46:56 +02:00
|
|
|
// TODO sort by unread count first, once we support that
|
2021-02-28 01:12:35 +01:00
|
|
|
user_ids.sort(compare_function);
|
2018-04-19 15:46:56 +02:00
|
|
|
return user_ids;
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2018-04-19 15:46:56 +02:00
|
|
|
|
2019-08-01 12:49:18 +02:00
|
|
|
function filter_user_ids(user_filter_text, user_ids) {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (user_filter_text === "") {
|
2018-04-19 15:46:56 +02:00
|
|
|
return user_ids;
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
user_ids = user_ids.filter((user_id) => !people.is_my_user_id(user_id));
|
2018-12-18 19:29:08 +01:00
|
|
|
|
2020-10-07 12:37:15 +02:00
|
|
|
let search_terms = user_filter_text.toLowerCase().split(/[,|]+/);
|
2020-07-02 01:39:34 +02:00
|
|
|
search_terms = search_terms.map((s) => s.trim());
|
2018-04-19 15:46:56 +02:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
const persons = user_ids.map((user_id) => people.get_by_user_id(user_id));
|
2018-04-19 15:46:56 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const user_id_dict = people.filter_people_by_search_terms(persons, search_terms);
|
2020-02-04 23:46:56 +01:00
|
|
|
return Array.from(user_id_dict.keys());
|
2018-04-19 15:46:56 +02:00
|
|
|
}
|
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function matches_filter(user_filter_text, user_id) {
|
2018-04-19 15:46:56 +02:00
|
|
|
// This is a roundabout way of checking a user if you look
|
|
|
|
// too hard at it, but it should be fine for now.
|
2019-08-01 12:49:18 +02:00
|
|
|
return filter_user_ids(user_filter_text, [user_id]).length === 1;
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2018-04-19 15:46:56 +02:00
|
|
|
|
|
|
|
function get_num_unread(user_id) {
|
2020-02-01 02:47:48 +01:00
|
|
|
return unread.num_unread_for_person(user_id.toString());
|
2018-04-19 15:46:56 +02:00
|
|
|
}
|
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function get_my_user_status(user_id) {
|
2018-12-21 20:10:27 +01:00
|
|
|
if (!people.is_my_user_id(user_id)) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-12-21 20:10:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (user_status.is_away(user_id)) {
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "(unavailable)"});
|
2018-12-21 20:10:27 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "(you)"});
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2018-12-21 20:10:27 +01:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function user_last_seen_time_status(user_id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const status = presence.get_status(user_id);
|
2019-02-28 16:09:03 +01:00
|
|
|
if (status === "active") {
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "Active now"});
|
2019-02-28 16:09:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (page_params.realm_is_zephyr_mirror_realm) {
|
|
|
|
// We don't send presence data to clients in Zephyr mirroring realms
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "Unknown"});
|
2019-02-28 16:09:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// There are situations where the client has incomplete presence
|
|
|
|
// history on a user. This can happen when users are deactivated,
|
|
|
|
// or when they just haven't been present in a long time (and we
|
|
|
|
// may have queries on presence that go back only N weeks).
|
|
|
|
//
|
|
|
|
// We give the somewhat vague status of "Unknown" for these users.
|
2019-11-02 00:06:25 +01:00
|
|
|
const last_active_date = presence.last_active_date(user_id);
|
2019-02-28 16:09:03 +01:00
|
|
|
if (last_active_date === undefined) {
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "More than 2 weeks ago"});
|
2019-02-28 16:09:03 +01:00
|
|
|
}
|
2021-02-05 21:20:14 +01:00
|
|
|
return timerender.last_seen_status_from_date(last_active_date);
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2019-02-28 16:09:03 +01:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function info_for(user_id) {
|
|
|
|
const user_circle_class = get_user_circle_class(user_id);
|
2020-02-05 14:30:59 +01:00
|
|
|
const person = people.get_by_user_id(user_id);
|
2021-02-28 01:12:35 +01:00
|
|
|
const my_user_status = get_my_user_status(user_id);
|
|
|
|
const user_circle_status = status_description(user_id);
|
2018-04-19 15:46:56 +02:00
|
|
|
|
|
|
|
return {
|
2018-08-04 16:46:17 +02:00
|
|
|
href: hash_util.pm_with_uri(person.email),
|
2018-04-19 15:46:56 +02:00
|
|
|
name: person.full_name,
|
2020-07-20 22:18:43 +02:00
|
|
|
user_id,
|
|
|
|
my_user_status,
|
2018-11-28 00:07:01 +01:00
|
|
|
is_current_user: people.is_my_user_id(user_id),
|
2018-04-19 15:46:56 +02:00
|
|
|
num_unread: get_num_unread(user_id),
|
2020-07-20 22:18:43 +02:00
|
|
|
user_circle_class,
|
|
|
|
user_circle_status,
|
2019-08-04 14:57:32 +02:00
|
|
|
};
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2019-08-04 14:57:32 +02:00
|
|
|
|
2019-11-20 23:39:10 +01:00
|
|
|
function get_last_seen(active_status, last_seen) {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (active_status === "active") {
|
2019-11-20 23:39:10 +01:00
|
|
|
return last_seen;
|
|
|
|
}
|
|
|
|
|
2021-04-13 06:51:54 +02:00
|
|
|
const last_seen_text = $t({defaultMessage: "Last active: {last_seen}"}, {last_seen});
|
2019-11-20 23:39:10 +01:00
|
|
|
return last_seen_text;
|
|
|
|
}
|
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function get_title_data(user_ids_string, is_group) {
|
2019-08-04 14:57:32 +02:00
|
|
|
if (is_group === true) {
|
|
|
|
// For groups, just return a string with recipient names.
|
|
|
|
return {
|
2019-11-20 23:39:10 +01:00
|
|
|
first_line: people.get_recipients(user_ids_string),
|
2020-07-15 01:29:15 +02:00
|
|
|
second_line: "",
|
|
|
|
third_line: "",
|
2019-08-04 14:57:32 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since it's not a group, user_ids_string is a single user ID.
|
2020-10-07 09:17:30 +02:00
|
|
|
const user_id = Number.parseInt(user_ids_string, 10);
|
2020-02-05 14:30:59 +01:00
|
|
|
const person = people.get_by_user_id(user_id);
|
2019-08-04 14:57:32 +02:00
|
|
|
|
|
|
|
if (person.is_bot) {
|
2020-03-24 22:42:58 +01:00
|
|
|
const bot_owner = people.get_bot_owner_user(person);
|
2019-11-20 23:39:10 +01:00
|
|
|
|
2020-03-24 22:42:58 +01:00
|
|
|
if (bot_owner) {
|
2021-04-13 06:51:54 +02:00
|
|
|
const bot_owner_name = $t(
|
|
|
|
{defaultMessage: "Owner: {name}"},
|
|
|
|
{name: bot_owner.full_name},
|
|
|
|
);
|
2019-11-20 23:39:10 +01:00
|
|
|
|
|
|
|
return {
|
|
|
|
first_line: person.full_name,
|
|
|
|
second_line: bot_owner_name,
|
2020-07-15 01:29:15 +02:00
|
|
|
third_line: "",
|
2019-11-20 23:39:10 +01:00
|
|
|
};
|
2019-08-04 14:57:32 +02:00
|
|
|
}
|
|
|
|
|
2019-11-20 23:39:10 +01:00
|
|
|
// Bot does not have an owner.
|
2019-08-04 14:57:32 +02:00
|
|
|
return {
|
2019-11-20 23:39:10 +01:00
|
|
|
first_line: person.full_name,
|
2020-07-15 01:29:15 +02:00
|
|
|
second_line: "",
|
|
|
|
third_line: "",
|
2019-08-04 14:57:32 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// For buddy list and individual PMS. Since is_group=False, it's
|
|
|
|
// a single, human, user.
|
2019-11-02 00:06:25 +01:00
|
|
|
const active_status = presence.get_status(user_id);
|
2021-02-28 01:12:35 +01:00
|
|
|
const last_seen = user_last_seen_time_status(user_id);
|
2021-04-02 13:05:26 +02:00
|
|
|
const is_my_user = people.is_my_user_id(user_id);
|
2019-08-04 14:57:32 +02:00
|
|
|
|
2019-11-20 23:39:10 +01:00
|
|
|
// Users has a status.
|
|
|
|
if (user_status.get_status_text(user_id)) {
|
|
|
|
return {
|
|
|
|
first_line: person.full_name,
|
|
|
|
second_line: user_status.get_status_text(user_id),
|
|
|
|
third_line: get_last_seen(active_status, last_seen),
|
2021-04-02 13:05:26 +02:00
|
|
|
show_you: is_my_user,
|
2019-11-20 23:39:10 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Users does not have a status.
|
2019-08-04 14:57:32 +02:00
|
|
|
return {
|
2019-11-20 23:39:10 +01:00
|
|
|
first_line: person.full_name,
|
|
|
|
second_line: get_last_seen(active_status, last_seen),
|
2020-07-15 01:29:15 +02:00
|
|
|
third_line: "",
|
2021-04-02 13:05:26 +02:00
|
|
|
show_you: is_my_user,
|
2018-04-19 15:46:56 +02:00
|
|
|
};
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2018-04-19 15:46:56 +02:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function get_item(user_id) {
|
|
|
|
const info = info_for(user_id);
|
2021-03-19 14:38:22 +01:00
|
|
|
compose_fade_users.update_user_info([info], fade_config);
|
2018-04-22 17:46:20 +02:00
|
|
|
return info;
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2018-04-22 17:46:20 +02:00
|
|
|
|
2018-04-20 18:22:28 +02:00
|
|
|
function user_is_recently_active(user_id) {
|
2020-03-28 01:25:56 +01:00
|
|
|
// return true if the user has a green/orange circle
|
2021-02-28 01:12:35 +01:00
|
|
|
return level(user_id) <= 2;
|
2018-04-20 18:22:28 +02:00
|
|
|
}
|
|
|
|
|
2019-08-01 12:49:18 +02:00
|
|
|
function maybe_shrink_list(user_ids, user_filter_text) {
|
2021-02-28 01:12:35 +01:00
|
|
|
if (user_ids.length <= max_size_before_shrinking) {
|
2018-04-23 23:13:52 +02:00
|
|
|
return user_ids;
|
|
|
|
}
|
|
|
|
|
2019-08-01 12:49:18 +02:00
|
|
|
if (user_filter_text) {
|
2018-04-23 23:13:52 +02:00
|
|
|
// If the user types something, we want to show all
|
|
|
|
// users matching the text, even if they have not been
|
|
|
|
// online recently.
|
|
|
|
// For super common letters like "s", we may
|
|
|
|
// eventually want to filter down to only users that
|
|
|
|
// are in presence.get_user_ids().
|
2018-04-20 18:22:28 +02:00
|
|
|
return user_ids;
|
|
|
|
}
|
|
|
|
|
2021-01-23 02:36:54 +01:00
|
|
|
user_ids = user_ids.filter((user_id) => user_is_recently_active(user_id));
|
2018-04-20 18:22:28 +02:00
|
|
|
|
|
|
|
return user_ids;
|
|
|
|
}
|
|
|
|
|
2019-08-01 12:51:20 +02:00
|
|
|
function get_user_id_list(user_filter_text) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let user_ids;
|
2018-04-19 15:46:56 +02:00
|
|
|
|
2019-08-01 12:49:18 +02:00
|
|
|
if (user_filter_text) {
|
2018-04-19 15:46:56 +02:00
|
|
|
// If there's a filter, select from all users, not just those
|
|
|
|
// recently active.
|
2019-08-01 12:49:18 +02:00
|
|
|
user_ids = filter_user_ids(user_filter_text, people.get_active_user_ids());
|
2018-04-19 15:46:56 +02:00
|
|
|
} else {
|
|
|
|
// From large realms, the user_ids in presence may exclude
|
|
|
|
// users who have been idle more than three weeks. When the
|
|
|
|
// filter text is blank, we show only those recently active users.
|
|
|
|
user_ids = presence.get_user_ids();
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
user_ids = user_ids.filter((user_id) => {
|
2020-02-05 14:30:59 +01:00
|
|
|
const person = people.get_by_user_id(user_id);
|
2018-07-14 14:06:30 +02:00
|
|
|
|
2020-02-04 15:38:37 +01:00
|
|
|
if (!person) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.warn("Got user_id in presence but not people: " + user_id);
|
2020-02-04 15:38:37 +01:00
|
|
|
return false;
|
2018-07-14 14:06:30 +02:00
|
|
|
}
|
2020-02-04 15:38:37 +01:00
|
|
|
|
|
|
|
// if the user is bot, do not show in presence data.
|
|
|
|
return !person.is_bot;
|
2018-07-14 14:06:30 +02:00
|
|
|
});
|
2019-08-01 12:51:20 +02:00
|
|
|
return user_ids;
|
|
|
|
}
|
2018-04-20 18:22:28 +02:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function get_filtered_and_sorted_user_ids(user_filter_text) {
|
2019-08-01 12:51:20 +02:00
|
|
|
let user_ids;
|
|
|
|
user_ids = get_user_id_list(user_filter_text);
|
2019-08-01 12:49:18 +02:00
|
|
|
user_ids = maybe_shrink_list(user_ids, user_filter_text);
|
2021-02-28 01:12:35 +01:00
|
|
|
return sort_users(user_ids);
|
|
|
|
}
|
2018-07-16 15:25:11 +02:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function get_items_for_users(user_ids) {
|
|
|
|
const user_info = user_ids.map((user_id) => info_for(user_id));
|
2021-03-19 14:38:22 +01:00
|
|
|
compose_fade_users.update_user_info(user_info, fade_config);
|
2018-04-19 15:46:56 +02:00
|
|
|
return user_info;
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|
2018-04-19 15:46:56 +02:00
|
|
|
|
2021-02-28 01:12:35 +01:00
|
|
|
export function huddle_fraction_present(huddle) {
|
2020-10-07 09:17:30 +02:00
|
|
|
const user_ids = huddle.split(",").map((s) => Number.parseInt(s, 10));
|
2019-02-18 16:31:30 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let num_present = 0;
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
|
|
|
|
for (const user_id of user_ids) {
|
2019-02-18 16:31:30 +01:00
|
|
|
if (presence.is_active(user_id)) {
|
|
|
|
num_present += 1;
|
|
|
|
}
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2019-02-18 16:31:30 +01:00
|
|
|
|
|
|
|
if (num_present === user_ids.length) {
|
|
|
|
return 1;
|
|
|
|
} else if (num_present !== 0) {
|
|
|
|
return 0.5;
|
|
|
|
}
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2021-02-28 01:12:35 +01:00
|
|
|
}
|