2020-08-20 21:33:00 +02:00
|
|
|
import md5 from "blueimp-md5";
|
|
|
|
import _ from "lodash";
|
|
|
|
import moment from "moment-timezone";
|
|
|
|
import "unorm"; // String.prototype.normalize polyfill for IE11
|
2020-08-01 03:43:15 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
import * as typeahead from "../shared/js/typeahead";
|
2020-07-25 02:02:35 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
import {FoldDict} from "./fold_dict";
|
|
|
|
import * as settings_data from "./settings_data";
|
|
|
|
import * as util from "./util";
|
2019-02-08 11:56:33 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let people_dict;
|
|
|
|
let people_by_name_dict;
|
|
|
|
let people_by_user_id_dict;
|
|
|
|
let active_user_dict;
|
2020-05-26 19:14:41 +02:00
|
|
|
let non_active_user_dict;
|
2019-11-02 00:06:25 +01:00
|
|
|
let cross_realm_dict;
|
|
|
|
let pm_recipient_count_dict;
|
|
|
|
let duplicate_full_name_data;
|
|
|
|
let my_user_id;
|
2016-12-15 22:18:59 +01:00
|
|
|
|
|
|
|
// We have an init() function so that our automated tests
|
|
|
|
// can easily clear data.
|
2020-08-20 21:33:00 +02:00
|
|
|
export function init() {
|
2020-02-03 09:39:58 +01:00
|
|
|
// The following three dicts point to the same objects
|
2017-01-31 20:44:51 +01:00
|
|
|
// (all people we've seen), but people_dict can have duplicate
|
|
|
|
// keys related to email changes. We want to deprecate
|
|
|
|
// people_dict over time and always do lookups by user_id.
|
2019-12-26 15:34:17 +01:00
|
|
|
people_dict = new FoldDict();
|
|
|
|
people_by_name_dict = new FoldDict();
|
2020-02-03 09:42:50 +01:00
|
|
|
people_by_user_id_dict = new Map();
|
2017-01-31 20:44:51 +01:00
|
|
|
|
2017-10-26 18:26:28 +02:00
|
|
|
// The next dictionary includes all active users (human/user)
|
|
|
|
// in our realm, but it excludes non-active users and
|
|
|
|
// cross-realm bots.
|
2020-02-03 09:42:50 +01:00
|
|
|
active_user_dict = new Map();
|
2020-05-26 19:14:41 +02:00
|
|
|
non_active_user_dict = new Map();
|
2020-02-03 09:42:50 +01:00
|
|
|
cross_realm_dict = new Map(); // keyed by user_id
|
|
|
|
pm_recipient_count_dict = new Map();
|
2018-08-08 21:56:07 +02:00
|
|
|
|
2019-12-29 15:07:05 +01:00
|
|
|
// This maintains a set of ids of people with same full names.
|
2019-12-26 15:34:17 +01:00
|
|
|
duplicate_full_name_data = new FoldDict();
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-12-15 22:18:59 +01:00
|
|
|
|
|
|
|
// WE INITIALIZE DATA STRUCTURES HERE!
|
2020-08-20 21:33:00 +02:00
|
|
|
init();
|
2014-01-30 22:42:19 +01:00
|
|
|
|
2019-12-29 15:07:05 +01:00
|
|
|
function split_to_ints(lst) {
|
2020-07-15 01:29:15 +02:00
|
|
|
return lst.split(",").map((s) => parseInt(s, 10));
|
2019-12-29 15:07:05 +01:00
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_by_user_id(user_id, ignore_missing) {
|
2020-02-13 20:56:37 +01:00
|
|
|
if (!people_by_user_id_dict.has(user_id) && !ignore_missing) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("Unknown user_id in get_by_user_id: " + user_id);
|
2018-03-13 13:04:16 +01:00
|
|
|
return;
|
2017-01-24 23:10:01 +01:00
|
|
|
}
|
2016-10-31 15:56:57 +01:00
|
|
|
return people_by_user_id_dict.get(user_id);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-10-31 15:56:57 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_by_email(email) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const person = people_dict.get(email);
|
2017-01-31 20:44:51 +01:00
|
|
|
|
|
|
|
if (!person) {
|
2018-03-13 13:04:16 +01:00
|
|
|
return;
|
2017-01-31 20:44:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (person.email.toLowerCase() !== email.toLowerCase()) {
|
|
|
|
blueslip.warn(
|
2020-07-15 00:34:28 +02:00
|
|
|
"Obsolete email passed to get_by_email: " + email + " new email = " + person.email,
|
2017-01-31 20:44:51 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return person;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-31 20:44:51 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_bot_owner_user(user) {
|
2020-03-24 22:42:58 +01:00
|
|
|
const owner_id = user.bot_owner_id;
|
|
|
|
|
|
|
|
if (owner_id === undefined || owner_id === null) {
|
|
|
|
// This is probably a cross-realm bot.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
return get_by_user_id(owner_id);
|
|
|
|
}
|
2020-03-24 22:42:58 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function id_matches_email_operand(user_id, email) {
|
|
|
|
const person = get_by_email(email);
|
2017-02-07 17:59:11 +01:00
|
|
|
|
|
|
|
if (!person) {
|
|
|
|
// The user may type bad data into the search bar, so
|
|
|
|
// we don't complain too loud here.
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.debug("User email operand unknown: " + email);
|
2017-02-07 17:59:11 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-06-06 18:19:09 +02:00
|
|
|
return person.user_id === user_id;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-02-07 17:59:11 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function update_email(user_id, new_email) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const person = people_by_user_id_dict.get(user_id);
|
2017-01-31 20:44:51 +01:00
|
|
|
person.email = new_email;
|
|
|
|
people_dict.set(new_email, person);
|
|
|
|
|
|
|
|
// For legacy reasons we don't delete the old email
|
|
|
|
// keys in our dictionaries, so that reverse lookups
|
|
|
|
// still work correctly.
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2014-01-30 22:42:19 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_visible_email(user) {
|
2020-04-06 20:14:36 +02:00
|
|
|
if (user.delivery_email) {
|
|
|
|
return user.delivery_email;
|
|
|
|
}
|
|
|
|
return user.email;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-04-06 20:14:36 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_user_id(email) {
|
|
|
|
const person = get_by_email(email);
|
2016-10-30 15:22:24 +01:00
|
|
|
if (person === undefined) {
|
2020-07-15 01:29:15 +02:00
|
|
|
const error_msg = "Unknown email for get_user_id: " + email;
|
2017-03-04 01:36:02 +01:00
|
|
|
blueslip.error(error_msg);
|
2018-03-13 13:04:16 +01:00
|
|
|
return;
|
2016-10-30 15:22:24 +01:00
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const user_id = person.user_id;
|
2016-10-30 15:22:24 +01:00
|
|
|
if (!user_id) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("No user_id found for " + email);
|
2018-03-13 13:04:16 +01:00
|
|
|
return;
|
2016-10-30 15:22:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return user_id;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-10-30 15:22:24 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function is_known_user_id(user_id) {
|
2017-03-26 19:32:54 +02:00
|
|
|
/*
|
|
|
|
For certain low-stakes operations, such as emoji reactions,
|
|
|
|
we may get a user_id that we don't know about, because the
|
|
|
|
user may have been deactivated. (We eventually want to track
|
|
|
|
deactivated users on the client, but until then, this is an
|
|
|
|
expedient thing we can check.)
|
|
|
|
*/
|
|
|
|
return people_by_user_id_dict.has(user_id);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-03-26 19:32:54 +02:00
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
function sort_numerically(user_ids) {
|
2020-07-02 01:45:54 +02:00
|
|
|
user_ids.sort((a, b) => a - b);
|
2017-08-01 15:54:47 +02:00
|
|
|
|
|
|
|
return user_ids;
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function huddle_string(message) {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (message.type !== "private") {
|
2017-02-05 00:22:16 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
let user_ids = message.display_recipient.map((recip) => recip.id);
|
2017-02-05 00:22:16 +01:00
|
|
|
|
|
|
|
function is_huddle_recip(user_id) {
|
2020-08-20 21:33:00 +02:00
|
|
|
return user_id && people_by_user_id_dict.has(user_id) && !is_my_user_id(user_id);
|
2017-02-05 00:22:16 +01:00
|
|
|
}
|
|
|
|
|
2020-02-08 03:33:46 +01:00
|
|
|
user_ids = user_ids.filter(is_huddle_recip);
|
2017-02-05 00:22:16 +01:00
|
|
|
|
|
|
|
if (user_ids.length <= 1) {
|
|
|
|
return;
|
|
|
|
}
|
2017-08-01 15:54:47 +02:00
|
|
|
|
|
|
|
user_ids = sort_numerically(user_ids);
|
2017-02-05 00:22:16 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return user_ids.join(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-02-05 00:22:16 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function user_ids_string_to_emails_string(user_ids_string) {
|
2019-12-29 15:07:05 +01:00
|
|
|
const user_ids = split_to_ints(user_ids_string);
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
let emails = user_ids.map((user_id) => {
|
2019-11-02 00:06:25 +01:00
|
|
|
const person = people_by_user_id_dict.get(user_id);
|
js: Convert _.map(a, …) to a.map(…).
And convert the corresponding function expressions to arrow style
while we’re here.
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 K from "ast-types/gen/kinds";
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);
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;
recast.visit(ast, {
visitCallExpression(path) {
const { callee, arguments: args } = path.node;
if (
n.MemberExpression.check(callee) &&
!callee.computed &&
n.Identifier.check(callee.object) &&
callee.object.name === "_" &&
n.Identifier.check(callee.property) &&
callee.property.name === "map" &&
args.length === 2 &&
checkExpression(args[0]) &&
checkExpression(args[1])
) {
const [arr, fn] = args;
path.replace(
b.callExpression(b.memberExpression(arr, b.identifier("map")), [
n.FunctionExpression.check(fn) ||
n.ArrowFunctionExpression.check(fn)
? b.arrowFunctionExpression(
fn.params,
n.BlockStatement.check(fn.body) &&
fn.body.body.length === 1 &&
n.ReturnStatement.check(fn.body.body[0])
? fn.body.body[0].argument || b.identifier("undefined")
: fn.body
)
: fn,
])
);
changed = true;
}
this.traverse(path);
},
});
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-08 02:43:49 +01:00
|
|
|
return person && person.email;
|
2016-11-15 22:55:37 +01:00
|
|
|
});
|
|
|
|
|
2020-02-08 05:11:31 +01:00
|
|
|
if (!emails.every(Boolean)) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.warn("Unknown user ids: " + user_ids_string);
|
2016-11-15 22:55:37 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-11-19 00:33:32 +01:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
emails = emails.map((email) => email.toLowerCase());
|
2016-11-19 00:33:32 +01:00
|
|
|
|
|
|
|
emails.sort();
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return emails.join(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-11-15 22:55:37 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function user_ids_string_to_ids_array(user_ids_string) {
|
2020-07-15 01:29:15 +02:00
|
|
|
const user_ids = user_ids_string.split(",");
|
2020-07-02 01:39:34 +02:00
|
|
|
const ids = user_ids.map((id) => Number(id));
|
2019-06-06 21:49:01 +02:00
|
|
|
return ids;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2019-06-06 21:49:01 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function emails_strings_to_user_ids_array(emails_string) {
|
|
|
|
const user_ids_string = emails_strings_to_user_ids_string(emails_string);
|
2019-07-11 18:54:28 +02:00
|
|
|
if (user_ids_string === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
const user_ids_array = user_ids_string_to_ids_array(user_ids_string);
|
2019-07-11 18:54:28 +02:00
|
|
|
return user_ids_array;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2019-07-11 18:54:28 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function reply_to_to_user_ids_string(emails_string) {
|
2017-02-25 00:30:51 +01:00
|
|
|
// This is basically emails_strings_to_user_ids_string
|
|
|
|
// without blueslip warnings, since it can be called with
|
|
|
|
// invalid data.
|
2020-07-15 01:29:15 +02:00
|
|
|
const emails = emails_string.split(",");
|
2017-02-25 00:30:51 +01:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
let user_ids = emails.map((email) => {
|
2020-08-20 21:33:00 +02:00
|
|
|
const person = get_by_email(email);
|
js: Convert _.map(a, …) to a.map(…).
And convert the corresponding function expressions to arrow style
while we’re here.
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 K from "ast-types/gen/kinds";
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);
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;
recast.visit(ast, {
visitCallExpression(path) {
const { callee, arguments: args } = path.node;
if (
n.MemberExpression.check(callee) &&
!callee.computed &&
n.Identifier.check(callee.object) &&
callee.object.name === "_" &&
n.Identifier.check(callee.property) &&
callee.property.name === "map" &&
args.length === 2 &&
checkExpression(args[0]) &&
checkExpression(args[1])
) {
const [arr, fn] = args;
path.replace(
b.callExpression(b.memberExpression(arr, b.identifier("map")), [
n.FunctionExpression.check(fn) ||
n.ArrowFunctionExpression.check(fn)
? b.arrowFunctionExpression(
fn.params,
n.BlockStatement.check(fn.body) &&
fn.body.body.length === 1 &&
n.ReturnStatement.check(fn.body.body[0])
? fn.body.body[0].argument || b.identifier("undefined")
: fn.body
)
: fn,
])
);
changed = true;
}
this.traverse(path);
},
});
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-08 02:43:49 +01:00
|
|
|
return person && person.user_id;
|
2017-02-25 00:30:51 +01:00
|
|
|
});
|
|
|
|
|
2020-02-08 05:11:31 +01:00
|
|
|
if (!user_ids.every(Boolean)) {
|
2017-02-25 00:30:51 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
user_ids = sort_numerically(user_ids);
|
2017-02-25 00:30:51 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return user_ids.join(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-02-25 00:30:51 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_user_time_preferences(user_id) {
|
|
|
|
const user_timezone = get_by_user_id(user_id).timezone;
|
2017-04-02 21:01:58 +02:00
|
|
|
if (user_timezone) {
|
2020-02-25 12:46:14 +01:00
|
|
|
return settings_data.get_time_preferences(user_timezone);
|
2017-06-02 05:46:09 +02:00
|
|
|
}
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-06-02 05:46:09 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_user_time(user_id) {
|
|
|
|
const user_pref = get_user_time_preferences(user_id);
|
2017-06-02 05:46:09 +02:00
|
|
|
if (user_pref) {
|
|
|
|
return moment().tz(user_pref.timezone).format(user_pref.format);
|
2017-04-02 21:01:58 +02:00
|
|
|
}
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-04-02 21:01:58 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_user_type(user_id) {
|
|
|
|
const user_profile = get_by_user_id(user_id);
|
2018-11-02 20:08:24 +01:00
|
|
|
|
2020-06-03 23:30:34 +02:00
|
|
|
if (user_profile.is_owner) {
|
|
|
|
return i18n.t("Owner");
|
|
|
|
} else if (user_profile.is_admin) {
|
2018-11-02 20:08:24 +01:00
|
|
|
return i18n.t("Administrator");
|
|
|
|
} else if (user_profile.is_guest) {
|
|
|
|
return i18n.t("Guest");
|
|
|
|
} else if (user_profile.is_bot) {
|
|
|
|
return i18n.t("Bot");
|
|
|
|
}
|
|
|
|
return i18n.t("Member");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-11-02 20:08:24 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function emails_strings_to_user_ids_string(emails_string) {
|
2020-07-15 01:29:15 +02:00
|
|
|
const emails = emails_string.split(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
return email_list_to_user_ids_string(emails);
|
|
|
|
}
|
2017-02-24 16:58:33 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function email_list_to_user_ids_string(emails) {
|
2020-07-02 01:39:34 +02:00
|
|
|
let user_ids = emails.map((email) => {
|
2020-08-20 21:33:00 +02:00
|
|
|
const person = get_by_email(email);
|
js: Convert _.map(a, …) to a.map(…).
And convert the corresponding function expressions to arrow style
while we’re here.
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 K from "ast-types/gen/kinds";
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);
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;
recast.visit(ast, {
visitCallExpression(path) {
const { callee, arguments: args } = path.node;
if (
n.MemberExpression.check(callee) &&
!callee.computed &&
n.Identifier.check(callee.object) &&
callee.object.name === "_" &&
n.Identifier.check(callee.property) &&
callee.property.name === "map" &&
args.length === 2 &&
checkExpression(args[0]) &&
checkExpression(args[1])
) {
const [arr, fn] = args;
path.replace(
b.callExpression(b.memberExpression(arr, b.identifier("map")), [
n.FunctionExpression.check(fn) ||
n.ArrowFunctionExpression.check(fn)
? b.arrowFunctionExpression(
fn.params,
n.BlockStatement.check(fn.body) &&
fn.body.body.length === 1 &&
n.ReturnStatement.check(fn.body.body[0])
? fn.body.body[0].argument || b.identifier("undefined")
: fn.body
)
: fn,
])
);
changed = true;
}
this.traverse(path);
},
});
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-08 02:43:49 +01:00
|
|
|
return person && person.user_id;
|
2016-11-15 22:55:37 +01:00
|
|
|
});
|
|
|
|
|
2020-02-08 05:11:31 +01:00
|
|
|
if (!user_ids.every(Boolean)) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.warn("Unknown emails: " + emails);
|
2016-11-15 22:55:37 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-11-19 00:33:32 +01:00
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
user_ids = sort_numerically(user_ids);
|
2016-11-19 00:33:32 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return user_ids.join(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-11-15 22:55:37 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function safe_full_names(user_ids) {
|
2020-07-02 01:39:34 +02:00
|
|
|
let names = user_ids.map((user_id) => {
|
2019-11-02 00:06:25 +01:00
|
|
|
const person = people_by_user_id_dict.get(user_id);
|
js: Convert _.map(a, …) to a.map(…).
And convert the corresponding function expressions to arrow style
while we’re here.
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 K from "ast-types/gen/kinds";
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);
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;
recast.visit(ast, {
visitCallExpression(path) {
const { callee, arguments: args } = path.node;
if (
n.MemberExpression.check(callee) &&
!callee.computed &&
n.Identifier.check(callee.object) &&
callee.object.name === "_" &&
n.Identifier.check(callee.property) &&
callee.property.name === "map" &&
args.length === 2 &&
checkExpression(args[0]) &&
checkExpression(args[1])
) {
const [arr, fn] = args;
path.replace(
b.callExpression(b.memberExpression(arr, b.identifier("map")), [
n.FunctionExpression.check(fn) ||
n.ArrowFunctionExpression.check(fn)
? b.arrowFunctionExpression(
fn.params,
n.BlockStatement.check(fn.body) &&
fn.body.body.length === 1 &&
n.ReturnStatement.check(fn.body.body[0])
? fn.body.body[0].argument || b.identifier("undefined")
: fn.body
)
: fn,
])
);
changed = true;
}
this.traverse(path);
},
});
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-08 02:43:49 +01:00
|
|
|
return person && person.full_name;
|
2018-02-23 16:16:55 +01:00
|
|
|
});
|
|
|
|
|
2020-02-08 03:33:46 +01:00
|
|
|
names = names.filter(Boolean);
|
2018-02-23 16:16:55 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return names.join(", ");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-02-23 16:16:55 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_full_name(user_id) {
|
2017-01-25 16:23:22 +01:00
|
|
|
return people_by_user_id_dict.get(user_id).full_name;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-25 16:23:22 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_recipients(user_ids_string) {
|
2017-01-25 16:23:22 +01:00
|
|
|
// See message_store.get_pm_full_names() for a similar function.
|
|
|
|
|
2019-12-29 15:07:05 +01:00
|
|
|
const user_ids = split_to_ints(user_ids_string);
|
2020-08-20 21:33:00 +02:00
|
|
|
const other_ids = user_ids.filter((user_id) => !is_my_user_id(user_id));
|
2017-01-25 16:23:22 +01:00
|
|
|
|
|
|
|
if (other_ids.length === 0) {
|
|
|
|
// private message with oneself
|
2020-08-20 21:33:00 +02:00
|
|
|
return my_full_name();
|
2017-01-25 16:23:22 +01:00
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
const names = other_ids.map(get_full_name).sort();
|
2020-07-15 01:29:15 +02:00
|
|
|
return names.join(", ");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-25 16:23:22 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function pm_reply_user_string(message) {
|
|
|
|
const user_ids = pm_with_user_ids(message);
|
2017-02-14 01:15:26 +01:00
|
|
|
|
|
|
|
if (!user_ids) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return user_ids.join(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-02-14 01:15:26 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function pm_reply_to(message) {
|
|
|
|
const user_ids = pm_with_user_ids(message);
|
2017-02-09 03:18:40 +01:00
|
|
|
|
2017-02-14 00:46:51 +01:00
|
|
|
if (!user_ids) {
|
2017-02-09 03:18:40 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
const emails = user_ids.map((user_id) => {
|
2019-11-02 00:06:25 +01:00
|
|
|
const person = people_by_user_id_dict.get(user_id);
|
2017-02-09 03:18:40 +01:00
|
|
|
if (!person) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("Unknown user id in message: " + user_id);
|
|
|
|
return "?";
|
2017-02-09 03:18:40 +01:00
|
|
|
}
|
|
|
|
return person.email;
|
|
|
|
});
|
|
|
|
|
|
|
|
emails.sort();
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const reply_to = emails.join(",");
|
2017-02-09 03:18:40 +01:00
|
|
|
|
|
|
|
return reply_to;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-02-09 03:18:40 +01:00
|
|
|
|
2017-08-01 15:35:07 +02:00
|
|
|
function sorted_other_user_ids(user_ids) {
|
|
|
|
// This excludes your own user id unless you're the only user
|
|
|
|
// (i.e. you sent a message to yourself).
|
2017-02-06 20:48:01 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
const other_user_ids = user_ids.filter((user_id) => !is_my_user_id(user_id));
|
2017-02-06 20:48:01 +01:00
|
|
|
|
|
|
|
if (other_user_ids.length >= 1) {
|
|
|
|
user_ids = other_user_ids;
|
|
|
|
} else {
|
|
|
|
user_ids = [my_user_id];
|
|
|
|
}
|
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
user_ids = sort_numerically(user_ids);
|
2017-02-06 20:48:01 +01:00
|
|
|
|
|
|
|
return user_ids;
|
2017-08-01 15:35:07 +02:00
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function concat_huddle(user_ids, user_id) {
|
2020-05-26 18:46:23 +02:00
|
|
|
/*
|
|
|
|
We assume user_ids and user_id have already
|
|
|
|
been validated by the caller.
|
|
|
|
|
|
|
|
The only logic we're encapsulating here is
|
|
|
|
how to encode huddles.
|
|
|
|
*/
|
bug fix: Fix sorting for group-pm edge cases.
If you have a group PM where some users have
three-digit user_ids and some with four-digit
user_ids (or similar), a huddle could effectively
be ignored when determining the order of
search search suggestions.
Basically, we need a way to canonically sort
user_ids in "huddle" strings, and it's somewhat
arbitrary whether you sort lexically or sort
numerically, but you do need to be consistent
about it.
And JS is not exactly helpful here:
> [99, 101].sort()
[ 101, 99 ]
This is a pretty obscure bug with pretty low
user-facing consequences, and it was never
reported to us as far as I know, but the fix
here is pretty straightforward.
We have had similar bugs of slightly more consequence
in the past. The reason this bug has shown
up multiple times in our codebase is that every
component that deals with huddles has slightly
different forces that determine how it wants
to serialize the huddle. It's just one of those
annoying things. Plus, bugs with group PMs
do tend to escape detection, since most people
spend most of their time either on streams
or in 1:1 PMs.
2020-05-27 01:29:50 +02:00
|
|
|
const sorted_ids = sort_numerically([...user_ids, user_id]);
|
2020-07-15 01:29:15 +02:00
|
|
|
return sorted_ids.join(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-05-26 18:46:23 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function pm_lookup_key(user_ids_string) {
|
2017-08-01 15:51:56 +02:00
|
|
|
/*
|
|
|
|
The server will sometimes include our own user id
|
|
|
|
in keys for PMs, but we only want our user id if
|
|
|
|
we sent a message to ourself.
|
|
|
|
*/
|
2019-12-29 15:07:05 +01:00
|
|
|
let user_ids = split_to_ints(user_ids_string);
|
2017-08-01 15:51:56 +02:00
|
|
|
user_ids = sorted_other_user_ids(user_ids);
|
2020-07-15 01:29:15 +02:00
|
|
|
return user_ids.join(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-08-01 15:51:56 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function all_user_ids_in_pm(message) {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (message.type !== "private") {
|
2018-10-18 22:05:28 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (message.display_recipient.length === 0) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("Empty recipient list in message");
|
2018-10-18 22:05:28 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
let user_ids = message.display_recipient.map((recip) => recip.id);
|
2018-10-18 22:05:28 +02:00
|
|
|
|
|
|
|
user_ids = sort_numerically(user_ids);
|
|
|
|
return user_ids;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-10-18 22:05:28 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function pm_with_user_ids(message) {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (message.type !== "private") {
|
2017-08-01 15:35:07 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (message.display_recipient.length === 0) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("Empty recipient list in message");
|
2017-08-01 15:35:07 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
const user_ids = message.display_recipient.map((recip) => recip.id);
|
2017-08-01 15:35:07 +02:00
|
|
|
|
|
|
|
return sorted_other_user_ids(user_ids);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-02-06 20:48:01 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function group_pm_with_user_ids(message) {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (message.type !== "private") {
|
2017-06-04 01:51:53 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (message.display_recipient.length === 0) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("Empty recipient list in message");
|
2017-06-04 01:51:53 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-01-01 13:02:34 +01:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
const user_ids = message.display_recipient.map((recip) => recip.id);
|
2020-08-20 21:33:00 +02:00
|
|
|
const is_user_present = user_ids.some((user_id) => is_my_user_id(user_id));
|
2017-06-04 01:51:53 +02:00
|
|
|
if (is_user_present) {
|
|
|
|
user_ids.sort();
|
|
|
|
if (user_ids.length > 2) {
|
|
|
|
return user_ids;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-06-04 01:51:53 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function pm_perma_link(message) {
|
|
|
|
const user_ids = all_user_ids_in_pm(message);
|
2018-10-18 22:05:28 +02:00
|
|
|
|
|
|
|
if (!user_ids) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let suffix;
|
2018-10-18 22:05:28 +02:00
|
|
|
|
|
|
|
if (user_ids.length >= 3) {
|
2020-07-15 01:29:15 +02:00
|
|
|
suffix = "group";
|
2018-10-18 22:05:28 +02:00
|
|
|
} else {
|
2020-07-15 01:29:15 +02:00
|
|
|
suffix = "pm";
|
2018-10-18 22:05:28 +02:00
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const slug = user_ids.join(",") + "-" + suffix;
|
2019-11-02 00:06:25 +01:00
|
|
|
const uri = "#narrow/pm-with/" + slug;
|
2018-10-18 22:05:28 +02:00
|
|
|
return uri;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-10-18 22:05:28 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function pm_with_url(message) {
|
|
|
|
const user_ids = pm_with_user_ids(message);
|
2017-02-06 20:48:01 +01:00
|
|
|
|
|
|
|
if (!user_ids) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let suffix;
|
2017-02-06 20:48:01 +01:00
|
|
|
|
|
|
|
if (user_ids.length > 1) {
|
2020-07-15 01:29:15 +02:00
|
|
|
suffix = "group";
|
2017-02-06 20:48:01 +01:00
|
|
|
} else {
|
2020-08-20 21:33:00 +02:00
|
|
|
const person = get_by_user_id(user_ids[0]);
|
2017-02-06 20:48:01 +01:00
|
|
|
if (person && person.email) {
|
2020-07-15 01:29:15 +02:00
|
|
|
suffix = person.email.split("@")[0].toLowerCase();
|
2017-02-06 20:48:01 +01:00
|
|
|
} else {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("Unknown people in message");
|
|
|
|
suffix = "unk";
|
2017-02-06 20:48:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const slug = user_ids.join(",") + "-" + suffix;
|
2019-11-02 00:06:25 +01:00
|
|
|
const uri = "#narrow/pm-with/" + slug;
|
2017-02-06 20:48:01 +01:00
|
|
|
return uri;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-02-06 20:48:01 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function update_email_in_reply_to(reply_to, user_id, new_email) {
|
2017-02-10 03:18:57 +01:00
|
|
|
// We try to replace an old email with a new email in a reply_to,
|
|
|
|
// but we try to avoid changing the reply_to if we don't have to,
|
|
|
|
// and we don't warn on any errors.
|
2020-07-15 01:29:15 +02:00
|
|
|
let emails = reply_to.split(",");
|
2017-02-10 03:18:57 +01:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
const persons = emails.map((email) => people_dict.get(email.trim()));
|
2017-02-10 03:18:57 +01:00
|
|
|
|
2020-02-08 05:11:31 +01:00
|
|
|
if (!persons.every(Boolean)) {
|
2017-02-10 03:18:57 +01:00
|
|
|
return reply_to;
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
const needs_patch = persons.some((person) => person.user_id === user_id);
|
2017-02-10 03:18:57 +01:00
|
|
|
|
|
|
|
if (!needs_patch) {
|
|
|
|
return reply_to;
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
emails = persons.map((person) => {
|
2017-02-10 03:18:57 +01:00
|
|
|
if (person.user_id === user_id) {
|
|
|
|
return new_email;
|
|
|
|
}
|
|
|
|
return person.email;
|
|
|
|
});
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return emails.join(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-02-08 22:10:06 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function pm_with_operand_ids(operand) {
|
2020-07-15 01:29:15 +02:00
|
|
|
let emails = operand.split(",");
|
2020-07-02 01:39:34 +02:00
|
|
|
emails = emails.map((email) => email.trim());
|
|
|
|
let persons = emails.map((email) => people_dict.get(email));
|
2017-02-08 22:10:06 +01:00
|
|
|
|
2017-06-19 09:32:56 +02:00
|
|
|
// If your email is included in a PM group with other people, just ignore it
|
|
|
|
if (persons.length > 1) {
|
|
|
|
persons = _.without(persons, people_by_user_id_dict.get(my_user_id));
|
|
|
|
}
|
|
|
|
|
2020-02-08 05:11:31 +01:00
|
|
|
if (!persons.every(Boolean)) {
|
2017-02-08 22:10:06 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
let user_ids = persons.map((person) => person.user_id);
|
2017-02-08 22:10:06 +01:00
|
|
|
|
2017-08-01 15:54:47 +02:00
|
|
|
user_ids = sort_numerically(user_ids);
|
2017-02-08 22:10:06 +01:00
|
|
|
|
|
|
|
return user_ids;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-02-08 22:10:06 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function emails_to_slug(emails_string) {
|
|
|
|
let slug = reply_to_to_user_ids_string(emails_string);
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
|
|
|
|
if (!slug) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
slug += "-";
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const emails = emails_string.split(",");
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
|
|
|
|
if (emails.length === 1) {
|
2020-07-15 01:29:15 +02:00
|
|
|
slug += emails[0].split("@")[0].toLowerCase();
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
} else {
|
2020-07-15 01:29:15 +02:00
|
|
|
slug += "group";
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return slug;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function slug_to_emails(slug) {
|
2020-07-03 17:02:30 +02:00
|
|
|
/*
|
|
|
|
It's not super important to be flexible about
|
|
|
|
PM-related slugs, since you would rarely post
|
|
|
|
them to the web, but we we do want to support
|
|
|
|
reasonable variations:
|
|
|
|
|
|
|
|
99-alice@example.com
|
|
|
|
99
|
|
|
|
|
|
|
|
Our canonical version is 99-alice@example.com,
|
|
|
|
and we only care about the "99" prefix.
|
|
|
|
*/
|
|
|
|
const m = /^([\d,]+)(-.*)?/.exec(slug);
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
if (m) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let user_ids_string = m[1];
|
2020-08-20 21:33:00 +02:00
|
|
|
user_ids_string = exclude_me_from_string(user_ids_string);
|
|
|
|
return user_ids_string_to_emails_string(user_ids_string);
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
}
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function exclude_me_from_string(user_ids_string) {
|
2018-10-18 19:41:44 +02:00
|
|
|
// Exclude me from a user_ids_string UNLESS I'm the
|
|
|
|
// only one in it.
|
2019-12-29 15:07:05 +01:00
|
|
|
let user_ids = split_to_ints(user_ids_string);
|
2018-10-18 19:41:44 +02:00
|
|
|
|
|
|
|
if (user_ids.length <= 1) {
|
|
|
|
// We either have a message to ourself, an empty
|
|
|
|
// slug, or a message to somebody else where we weren't
|
|
|
|
// part of the slug.
|
2020-07-15 01:29:15 +02:00
|
|
|
return user_ids.join(",");
|
2018-10-18 19:41:44 +02:00
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
user_ids = user_ids.filter((user_id) => !is_my_user_id(user_id));
|
2018-10-18 19:41:44 +02:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
return user_ids.join(",");
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-10-18 19:41:44 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function format_small_avatar_url(raw_url) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const url = raw_url + "&s=50";
|
2017-01-21 21:45:52 +01:00
|
|
|
return url;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-21 21:45:52 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function sender_is_bot(message) {
|
2017-01-11 16:45:06 +01:00
|
|
|
if (message.sender_id) {
|
2020-08-20 21:33:00 +02:00
|
|
|
const person = get_by_user_id(message.sender_id);
|
2017-01-11 16:45:06 +01:00
|
|
|
return person.is_bot;
|
|
|
|
}
|
|
|
|
return false;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-11 16:45:06 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function sender_is_guest(message) {
|
2018-10-31 18:15:29 +01:00
|
|
|
if (message.sender_id) {
|
2020-08-20 21:33:00 +02:00
|
|
|
const person = get_by_user_id(message.sender_id);
|
2018-10-31 18:15:29 +01:00
|
|
|
return person.is_guest;
|
|
|
|
}
|
|
|
|
return false;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-10-31 18:15:29 +01:00
|
|
|
|
2018-04-05 21:30:15 +02:00
|
|
|
function gravatar_url_for_email(email) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const hash = md5(email.toLowerCase());
|
2020-07-15 01:29:15 +02:00
|
|
|
const avatar_url = "https://secure.gravatar.com/avatar/" + hash + "?d=identicon";
|
2020-08-20 21:33:00 +02:00
|
|
|
const small_avatar_url = format_small_avatar_url(avatar_url);
|
2018-04-05 21:30:15 +02:00
|
|
|
return small_avatar_url;
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function small_avatar_url_for_person(person) {
|
2018-04-05 21:36:32 +02:00
|
|
|
if (person.avatar_url) {
|
2020-08-20 21:33:00 +02:00
|
|
|
return format_small_avatar_url(person.avatar_url);
|
2018-04-05 21:36:32 +02:00
|
|
|
}
|
2020-06-24 14:25:17 +02:00
|
|
|
|
|
|
|
if (person.avatar_url === null) {
|
|
|
|
return gravatar_url_for_email(person.email);
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
return format_small_avatar_url("/avatar/" + person.user_id);
|
|
|
|
}
|
2018-04-05 21:36:32 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function sender_info_with_small_avatar_urls_for_sender_ids(sender_ids) {
|
2020-05-22 21:04:03 +02:00
|
|
|
const senders_info = [];
|
|
|
|
for (const id of sender_ids) {
|
2020-08-20 21:33:00 +02:00
|
|
|
const sender = {...get_by_user_id(id)};
|
|
|
|
sender.avatar_url_small = small_avatar_url_for_person(sender);
|
2020-05-22 21:04:03 +02:00
|
|
|
senders_info.push(sender);
|
|
|
|
}
|
|
|
|
return senders_info;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-05-22 21:04:03 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function small_avatar_url(message) {
|
2017-01-21 20:29:39 +01:00
|
|
|
// Try to call this function in all places where we need 25px
|
|
|
|
// avatar images, so that the browser can help
|
|
|
|
// us avoid unnecessary network trips. (For user-uploaded avatars,
|
|
|
|
// the s=25 parameter is essentially ignored, but it's harmless.)
|
|
|
|
//
|
|
|
|
// We actually request these at s=50, so that we look better
|
|
|
|
// on retina displays.
|
2017-01-21 21:34:27 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let person;
|
2017-01-21 21:34:27 +01:00
|
|
|
if (message.sender_id) {
|
|
|
|
// We should always have message.sender_id, except for in the
|
|
|
|
// tutorial, where it's ok to fall back to the url in the fake
|
|
|
|
// messages.
|
2020-08-20 21:33:00 +02:00
|
|
|
person = get_by_user_id(message.sender_id);
|
2017-01-21 21:34:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// The first time we encounter a sender in a message, we may
|
|
|
|
// not have person.avatar_url set, but if we do, then use that.
|
2018-04-05 21:36:32 +02:00
|
|
|
if (person && person.avatar_url) {
|
2020-08-20 21:33:00 +02:00
|
|
|
return small_avatar_url_for_person(person);
|
2017-11-03 17:25:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Try to get info from the message if we didn't have a `person` object
|
|
|
|
// or if the avatar was missing. We do this verbosely to avoid false
|
|
|
|
// positives on line coverage (we don't do branch checking).
|
2018-04-05 21:40:05 +02:00
|
|
|
if (message.avatar_url) {
|
2020-08-20 21:33:00 +02:00
|
|
|
return format_small_avatar_url(message.avatar_url);
|
2017-01-21 21:34:27 +01:00
|
|
|
}
|
|
|
|
|
2020-06-24 14:25:17 +02:00
|
|
|
if (person && person.avatar_url === undefined) {
|
|
|
|
// If we don't have an avatar_url at all, we use `GET
|
|
|
|
// /avatar/{user_id}` endpoint to obtain avatar url. This is
|
|
|
|
// required to take advantage of the user_avatar_url_field_optional
|
|
|
|
// optimization, which saves a huge amount of network traffic on
|
|
|
|
// servers with 10,000s of user accounts.
|
2020-08-20 21:33:00 +02:00
|
|
|
return format_small_avatar_url("/avatar/" + person.user_id);
|
2020-06-24 14:25:17 +02:00
|
|
|
}
|
|
|
|
|
2018-04-05 21:39:24 +02:00
|
|
|
// For computing the user's email, we first trust the person
|
|
|
|
// object since that is updated via our real-time sync system, but
|
|
|
|
// if unavailable, we use the sender email.
|
2019-11-02 00:06:25 +01:00
|
|
|
let email;
|
2018-04-05 21:39:24 +02:00
|
|
|
if (person) {
|
|
|
|
email = person.email;
|
|
|
|
} else {
|
2017-11-03 17:25:47 +01:00
|
|
|
email = message.sender_email;
|
|
|
|
}
|
|
|
|
|
2018-04-05 21:40:05 +02:00
|
|
|
return gravatar_url_for_email(email);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-21 20:29:39 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function is_valid_email_for_compose(email) {
|
|
|
|
if (is_cross_realm_email(email)) {
|
2017-10-26 18:45:08 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
const person = get_by_email(email);
|
2017-10-26 18:45:08 +02:00
|
|
|
if (!person) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return active_user_dict.has(person.user_id);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-10-26 18:45:08 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function is_valid_bulk_emails_for_compose(emails) {
|
2018-05-28 14:10:33 +02:00
|
|
|
// Returns false if at least one of the emails is invalid.
|
2020-07-02 01:39:34 +02:00
|
|
|
return emails.every((email) => {
|
2020-08-20 21:33:00 +02:00
|
|
|
if (!is_valid_email_for_compose(email)) {
|
2018-05-28 14:10:33 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
});
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-05-28 14:10:33 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function is_active_user_for_popover(user_id) {
|
2017-10-26 21:00:30 +02:00
|
|
|
// For popover menus, we include cross-realm bots as active
|
|
|
|
// users.
|
|
|
|
|
|
|
|
if (cross_realm_dict.get(user_id)) {
|
2017-10-25 00:59:51 +02:00
|
|
|
return true;
|
|
|
|
}
|
2017-10-26 21:00:30 +02:00
|
|
|
if (active_user_dict.has(user_id)) {
|
|
|
|
return true;
|
2017-10-25 00:59:51 +02:00
|
|
|
}
|
2017-10-26 21:00:30 +02:00
|
|
|
|
|
|
|
// TODO: We can report errors here once we start loading
|
|
|
|
// deactivated users at page-load time. For now just warn.
|
|
|
|
if (!people_by_user_id_dict.has(user_id)) {
|
|
|
|
blueslip.warn("Unexpectedly invalid user_id in user popover query: " + user_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-10-14 00:33:36 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function filter_all_persons(pred) {
|
2020-02-03 09:35:14 +01:00
|
|
|
const ret = [];
|
|
|
|
for (const person of people_by_user_id_dict.values()) {
|
|
|
|
if (pred(person)) {
|
|
|
|
ret.push(person);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-11-01 19:36:50 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function filter_all_users(pred) {
|
2020-03-21 15:22:40 +01:00
|
|
|
const ret = [];
|
|
|
|
for (const person of active_user_dict.values()) {
|
|
|
|
if (pred(person)) {
|
|
|
|
ret.push(person);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-03-21 15:22:40 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_realm_users() {
|
2020-03-21 19:53:16 +01:00
|
|
|
// includes humans and bots from your realm
|
2020-02-04 23:46:56 +01:00
|
|
|
return Array.from(active_user_dict.values());
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-11-01 19:45:53 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_active_human_ids() {
|
2020-05-30 04:21:15 +02:00
|
|
|
const human_ids = [];
|
2020-03-21 17:58:31 +01:00
|
|
|
|
|
|
|
for (const user of active_user_dict.values()) {
|
|
|
|
if (!user.is_bot) {
|
2020-05-30 04:21:15 +02:00
|
|
|
human_ids.push(user.user_id);
|
2020-03-21 17:58:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-30 04:21:15 +02:00
|
|
|
return human_ids;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-07-28 20:13:20 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_non_active_human_ids() {
|
2020-05-30 04:21:15 +02:00
|
|
|
const human_ids = [];
|
2020-05-26 19:14:41 +02:00
|
|
|
|
|
|
|
for (const user of non_active_user_dict.values()) {
|
|
|
|
if (!user.is_bot) {
|
2020-05-30 04:21:15 +02:00
|
|
|
human_ids.push(user.user_id);
|
2020-05-26 19:14:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-30 04:21:15 +02:00
|
|
|
return human_ids;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-05-26 19:14:41 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_active_human_count() {
|
2020-03-21 13:58:40 +01:00
|
|
|
let count = 0;
|
|
|
|
for (const person of active_user_dict.values()) {
|
|
|
|
if (!person.is_bot) {
|
|
|
|
count += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-03-21 13:58:40 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_active_user_ids() {
|
2017-10-26 18:17:43 +02:00
|
|
|
// This includes active users and active bots.
|
2020-02-04 23:46:56 +01:00
|
|
|
return Array.from(active_user_dict.keys());
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-10-07 20:10:10 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_non_active_realm_users() {
|
2020-05-26 19:14:41 +02:00
|
|
|
return Array.from(non_active_user_dict.values());
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-05-26 19:14:41 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function is_cross_realm_email(email) {
|
|
|
|
const person = get_by_email(email);
|
2017-01-31 21:35:45 +01:00
|
|
|
if (!person) {
|
2018-03-13 13:04:16 +01:00
|
|
|
return;
|
2017-01-31 21:35:45 +01:00
|
|
|
}
|
|
|
|
return cross_realm_dict.has(person.user_id);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-11-02 23:48:47 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_recipient_count(person) {
|
2016-11-03 21:59:18 +01:00
|
|
|
// We can have fake person objects like the "all"
|
|
|
|
// pseudo-person in at-mentions. They will have
|
|
|
|
// the pm_recipient_count on the object itself.
|
|
|
|
if (person.pm_recipient_count) {
|
|
|
|
return person.pm_recipient_count;
|
|
|
|
}
|
|
|
|
|
2020-01-01 14:47:19 +01:00
|
|
|
/*
|
|
|
|
For searching in the search bar, we will
|
|
|
|
have true `person` objects with `user_id`.
|
|
|
|
|
|
|
|
Likewise, we'll have user_id if we
|
|
|
|
are tab-completing a user to send a PM
|
|
|
|
to (but we only get called if we're not
|
|
|
|
currently in a stream view).
|
|
|
|
|
|
|
|
Finally, we'll have user_id if we are adding
|
|
|
|
people to a stream (w/typeahead).
|
|
|
|
|
|
|
|
*/
|
|
|
|
const count = pm_recipient_count_dict.get(person.user_id);
|
2016-11-03 21:59:18 +01:00
|
|
|
|
|
|
|
return count || 0;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-11-03 21:59:18 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function incr_recipient_count(user_id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const old_count = pm_recipient_count_dict.get(user_id) || 0;
|
2017-01-31 21:02:15 +01:00
|
|
|
pm_recipient_count_dict.set(user_id, old_count + 1);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-11-03 21:59:18 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_message_people() {
|
2020-01-10 15:22:25 +01:00
|
|
|
/*
|
|
|
|
message_people are roughly the people who have
|
|
|
|
actually sent messages that are currently
|
|
|
|
showing up on your feed. These people
|
|
|
|
are important--we give them preference
|
|
|
|
over other users in certain search
|
|
|
|
suggestions, since non-message-people are
|
|
|
|
presumably either not very active or
|
|
|
|
possibly subscribed to streams you don't
|
|
|
|
care about. message_people also includes
|
|
|
|
people whom you have sent PMs, but look
|
|
|
|
at the message_store code to see the precise
|
|
|
|
semantics
|
|
|
|
*/
|
2020-07-15 00:34:28 +02:00
|
|
|
const message_people = message_store
|
|
|
|
.user_ids()
|
|
|
|
.map((user_id) => people_by_user_id_dict.get(user_id))
|
|
|
|
.filter(Boolean);
|
2020-01-02 15:17:10 +01:00
|
|
|
|
2020-01-10 15:22:25 +01:00
|
|
|
return message_people;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-01-10 15:22:25 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_active_message_people() {
|
|
|
|
const message_people = get_message_people();
|
2020-07-02 01:45:54 +02:00
|
|
|
const active_message_people = message_people.filter((item) =>
|
2020-07-02 02:16:03 +02:00
|
|
|
active_user_dict.has(item.user_id),
|
2020-07-02 01:45:54 +02:00
|
|
|
);
|
2020-04-21 21:28:56 +02:00
|
|
|
return active_message_people;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-04-21 21:28:56 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_people_for_search_bar(query) {
|
|
|
|
const pred = build_person_matcher(query);
|
2020-01-10 15:22:25 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
const message_people = get_message_people();
|
2020-01-10 15:22:25 +01:00
|
|
|
|
2020-02-08 03:33:46 +01:00
|
|
|
const small_results = message_people.filter(pred);
|
2020-01-02 15:17:10 +01:00
|
|
|
|
|
|
|
if (small_results.length >= 5) {
|
|
|
|
return small_results;
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
return filter_all_persons(pred);
|
|
|
|
}
|
2016-10-18 19:21:38 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function build_termlet_matcher(termlet) {
|
2019-12-23 16:03:23 +01:00
|
|
|
termlet = termlet.trim();
|
|
|
|
|
|
|
|
const is_ascii = /^[a-z]+$/.test(termlet);
|
|
|
|
|
2019-12-23 16:18:32 +01:00
|
|
|
return function (user) {
|
|
|
|
let full_name = user.full_name;
|
|
|
|
if (is_ascii) {
|
|
|
|
// Only ignore diacritics if the query is plain ascii
|
2020-01-26 14:21:13 +01:00
|
|
|
full_name = typeahead.remove_diacritics(full_name);
|
2019-12-23 16:18:32 +01:00
|
|
|
}
|
2020-07-15 01:29:15 +02:00
|
|
|
const names = full_name.toLowerCase().split(" ");
|
2019-12-23 16:18:32 +01:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
return names.some((name) => name.startsWith(termlet));
|
2019-12-23 16:03:23 +01:00
|
|
|
};
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2019-12-23 16:03:23 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function build_person_matcher(query) {
|
2019-12-23 15:52:40 +01:00
|
|
|
query = query.trim();
|
|
|
|
|
2019-12-23 16:03:23 +01:00
|
|
|
const termlets = query.toLowerCase().split(/\s+/);
|
2020-08-20 21:33:00 +02:00
|
|
|
const termlet_matchers = termlets.map(build_termlet_matcher);
|
2016-11-14 22:34:46 +01:00
|
|
|
|
2019-12-23 15:39:44 +01:00
|
|
|
return function (user) {
|
|
|
|
const email = user.email.toLowerCase();
|
|
|
|
|
2020-01-28 15:26:02 +01:00
|
|
|
if (email.startsWith(query)) {
|
2019-12-23 15:39:44 +01:00
|
|
|
return true;
|
|
|
|
}
|
2019-12-23 15:52:40 +01:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
return termlet_matchers.every((matcher) => matcher(user));
|
2019-12-23 15:39:44 +01:00
|
|
|
};
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-11-14 22:22:02 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function filter_people_by_search_terms(users, search_terms) {
|
2020-02-03 09:42:50 +01:00
|
|
|
const filtered_users = new Map();
|
2018-04-06 20:43:34 +02:00
|
|
|
|
2019-12-23 15:49:41 +01:00
|
|
|
// Build our matchers outside the loop to avoid some
|
|
|
|
// search overhead that is not user-specific.
|
2020-08-20 21:33:00 +02:00
|
|
|
const matchers = search_terms.map((search_term) => build_person_matcher(search_term));
|
2019-12-23 15:49:41 +01:00
|
|
|
|
2018-04-06 20:43:34 +02:00
|
|
|
// Loop through users and populate filtered_users only
|
|
|
|
// if they include search_terms
|
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 of users) {
|
2020-08-20 21:33:00 +02:00
|
|
|
const person = get_by_email(user.email);
|
2018-04-06 20:43:34 +02:00
|
|
|
// Get person object (and ignore errors)
|
|
|
|
if (!person || !person.full_name) {
|
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
|
|
|
continue;
|
2018-04-06 20:43:34 +02:00
|
|
|
}
|
2016-11-14 21:37:36 +01:00
|
|
|
|
2018-04-06 20:43:34 +02:00
|
|
|
// Return user emails that include search terms
|
2020-07-02 01:39:34 +02:00
|
|
|
const match = matchers.some((matcher) => matcher(user));
|
2018-04-06 20:43:34 +02:00
|
|
|
|
|
|
|
if (match) {
|
|
|
|
filtered_users.set(person.user_id, true);
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2018-04-06 20:43:34 +02:00
|
|
|
return filtered_users;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-10-18 19:21:38 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export const is_valid_full_name_and_user_id = (full_name, user_id) => {
|
2020-02-16 14:16:46 +01:00
|
|
|
const person = people_by_user_id_dict.get(user_id);
|
|
|
|
|
|
|
|
if (!person) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return person.full_name === full_name;
|
|
|
|
};
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export const get_actual_name_from_user_id = (user_id) => {
|
2020-02-16 14:16:46 +01:00
|
|
|
/*
|
|
|
|
If you are dealing with user-entered data, you
|
|
|
|
should validate the user_id BEFORE calling
|
|
|
|
this function.
|
|
|
|
*/
|
|
|
|
const person = people_by_user_id_dict.get(user_id);
|
|
|
|
|
|
|
|
if (!person) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("Unknown user_id: " + user_id);
|
2020-02-16 14:16:46 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return person.full_name;
|
|
|
|
};
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_user_id_from_name(full_name) {
|
2020-02-16 14:16:46 +01:00
|
|
|
// get_user_id_from_name('Alice Smith') === 42
|
|
|
|
|
|
|
|
/*
|
|
|
|
This function is intended to be called
|
|
|
|
with a full name that is user-entered, such
|
|
|
|
a full name from a user mention.
|
|
|
|
|
|
|
|
We will only return a **unique** user_id
|
|
|
|
here. For duplicate names, our UI should
|
|
|
|
force users to disambiguate names with a
|
|
|
|
user_id and then call is_valid_full_name_and_user_id
|
|
|
|
to make sure the combo is valid. This is
|
|
|
|
exactly what we do with mentions.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const person = people_by_name_dict.get(full_name);
|
|
|
|
|
|
|
|
if (!person) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
if (is_duplicate_full_name(full_name)) {
|
2020-02-16 14:16:46 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return person.user_id;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2014-01-30 22:42:19 +01:00
|
|
|
|
|
|
|
function people_cmp(person1, person2) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const name_cmp = util.strcmp(person1.full_name, person2.full_name);
|
2014-01-30 22:42:19 +01:00
|
|
|
if (name_cmp < 0) {
|
|
|
|
return -1;
|
|
|
|
} else if (name_cmp > 0) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return util.strcmp(person1.email, person2.email);
|
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_people_for_stream_create() {
|
2020-01-01 23:20:49 +01:00
|
|
|
/*
|
|
|
|
If you are thinking of reusing this function,
|
|
|
|
a better option in most cases is to just
|
2020-03-21 19:53:16 +01:00
|
|
|
call `exports.get_realm_users()` and then
|
2020-01-01 23:20:49 +01:00
|
|
|
filter out the "me" user yourself as part of
|
|
|
|
any other filtering that you are doing.
|
|
|
|
|
|
|
|
In particular, this function does a sort
|
|
|
|
that is kinda expensive and may not apply
|
|
|
|
to your use case.
|
|
|
|
*/
|
2019-11-02 00:06:25 +01:00
|
|
|
const people_minus_you = [];
|
2020-02-03 08:51:09 +01:00
|
|
|
for (const person of active_user_dict.values()) {
|
2020-08-20 21:33:00 +02:00
|
|
|
if (!is_my_user_id(person.user_id)) {
|
2020-07-15 00:34:28 +02:00
|
|
|
people_minus_you.push({
|
|
|
|
email: person.email,
|
|
|
|
user_id: person.user_id,
|
|
|
|
full_name: person.full_name,
|
|
|
|
});
|
2014-01-30 22:42:19 +01:00
|
|
|
}
|
2020-02-03 08:51:09 +01:00
|
|
|
}
|
2014-01-30 22:42:19 +01:00
|
|
|
return people_minus_you.sort(people_cmp);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2014-01-30 22:42:19 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function track_duplicate_full_name(full_name, user_id, to_remove) {
|
2019-12-26 14:43:13 +01:00
|
|
|
let ids;
|
2018-08-08 21:56:07 +02:00
|
|
|
if (duplicate_full_name_data.has(full_name)) {
|
|
|
|
ids = duplicate_full_name_data.get(full_name);
|
2019-12-26 14:43:13 +01:00
|
|
|
} else {
|
|
|
|
ids = new Set();
|
2018-08-08 21:56:07 +02:00
|
|
|
}
|
|
|
|
if (!to_remove && user_id) {
|
2019-12-26 14:43:13 +01:00
|
|
|
ids.add(user_id);
|
2018-08-08 21:56:07 +02:00
|
|
|
}
|
2019-12-26 14:43:13 +01:00
|
|
|
if (to_remove && user_id) {
|
|
|
|
ids.delete(user_id);
|
2018-08-08 21:56:07 +02:00
|
|
|
}
|
2018-12-07 21:21:39 +01:00
|
|
|
duplicate_full_name_data.set(full_name, ids);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-08-08 21:56:07 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function is_duplicate_full_name(full_name) {
|
2019-12-26 14:43:13 +01:00
|
|
|
const ids = duplicate_full_name_data.get(full_name);
|
|
|
|
|
|
|
|
return ids && ids.size > 1;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-08-08 21:56:07 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_mention_syntax(full_name, user_id, silent) {
|
2020-07-15 01:29:15 +02:00
|
|
|
let mention = "";
|
2019-02-20 18:19:05 +01:00
|
|
|
if (silent) {
|
2020-07-15 01:29:15 +02:00
|
|
|
mention += "@_**";
|
2019-02-20 18:19:05 +01:00
|
|
|
} else {
|
2020-07-15 01:29:15 +02:00
|
|
|
mention += "@**";
|
2019-02-20 18:19:05 +01:00
|
|
|
}
|
|
|
|
mention += full_name;
|
2018-10-13 02:25:38 +02:00
|
|
|
if (!user_id) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.warn("get_mention_syntax called without user_id.");
|
2018-10-13 02:25:38 +02:00
|
|
|
}
|
2020-08-20 21:33:00 +02:00
|
|
|
if (is_duplicate_full_name(full_name) && user_id) {
|
2020-07-15 01:29:15 +02:00
|
|
|
mention += "|" + user_id;
|
2018-10-13 02:25:38 +02:00
|
|
|
}
|
2020-07-15 01:29:15 +02:00
|
|
|
mention += "**";
|
2018-10-13 02:25:38 +02:00
|
|
|
return mention;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-10-13 02:25:38 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function _add_user(person) {
|
2020-03-21 20:19:30 +01:00
|
|
|
/*
|
|
|
|
This is common code to add any user, even
|
|
|
|
users who may be deactivated or outside
|
|
|
|
our realm (like cross-realm bots).
|
|
|
|
*/
|
2016-10-31 15:56:57 +01:00
|
|
|
if (person.user_id) {
|
|
|
|
people_by_user_id_dict.set(person.user_id, person);
|
|
|
|
} else {
|
|
|
|
// We eventually want to lock this down completely
|
|
|
|
// and report an error and not update other the data
|
|
|
|
// structures here, but we have a lot of edge cases
|
|
|
|
// with cross-realm bots, zephyr users, etc., deactivated
|
|
|
|
// users, where we are probably fine for now not to
|
|
|
|
// find them via user_id lookups.
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.warn("No user_id provided for " + person.email);
|
2016-10-31 15:56:57 +01:00
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
track_duplicate_full_name(person.full_name, person.user_id);
|
2014-01-30 22:42:19 +01:00
|
|
|
people_dict.set(person.email, person);
|
|
|
|
people_by_name_dict.set(person.full_name, person);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2014-01-30 22:42:19 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function add_active_user(person) {
|
2017-10-26 18:26:28 +02:00
|
|
|
active_user_dict.set(person.user_id, person);
|
2020-08-20 21:33:00 +02:00
|
|
|
_add_user(person);
|
2020-05-26 19:14:41 +02:00
|
|
|
non_active_user_dict.delete(person.user_id);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-05-26 19:14:41 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export const is_person_active = (user_id) => {
|
2020-05-26 19:14:41 +02:00
|
|
|
if (!people_by_user_id_dict.has(user_id)) {
|
|
|
|
blueslip.error("No user found.", user_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return active_user_dict.has(user_id);
|
2014-01-30 22:42:19 +01:00
|
|
|
};
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function add_cross_realm_user(person) {
|
2020-05-27 03:09:17 +02:00
|
|
|
if (!people_dict.has(person.email)) {
|
2020-08-20 21:33:00 +02:00
|
|
|
_add_user(person);
|
2020-05-27 03:09:17 +02:00
|
|
|
}
|
|
|
|
cross_realm_dict.set(person.user_id, person);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2020-05-27 03:09:17 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function deactivate(person) {
|
2016-12-15 22:44:42 +01:00
|
|
|
// We don't fully remove a person from all of our data
|
|
|
|
// structures, because deactivated users can be part
|
|
|
|
// of somebody's PM list.
|
2020-02-03 07:41:38 +01:00
|
|
|
active_user_dict.delete(person.user_id);
|
2020-05-26 19:14:41 +02:00
|
|
|
non_active_user_dict.set(person.user_id, person);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2014-01-30 22:42:19 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function report_late_add(user_id, email) {
|
2017-11-06 17:03:01 +01:00
|
|
|
// This function is extracted to make unit testing easier,
|
|
|
|
// plus we may fine-tune our reporting here for different
|
|
|
|
// types of realms.
|
2020-07-15 01:29:15 +02:00
|
|
|
const msg = "Added user late: user_id=" + user_id + " email=" + email;
|
2017-11-06 17:03:01 +01:00
|
|
|
|
2018-08-04 15:40:25 +02:00
|
|
|
if (reload_state.is_in_progress()) {
|
2018-04-23 21:13:21 +02:00
|
|
|
blueslip.log(msg);
|
|
|
|
} else {
|
|
|
|
blueslip.error(msg);
|
|
|
|
}
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-11-06 17:03:01 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function extract_people_from_message(message) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let involved_people;
|
2016-12-15 23:33:36 +01:00
|
|
|
|
|
|
|
switch (message.type) {
|
2020-07-15 02:14:03 +02:00
|
|
|
case "stream":
|
2020-07-15 00:34:28 +02:00
|
|
|
involved_people = [
|
|
|
|
{
|
|
|
|
full_name: message.sender_full_name,
|
|
|
|
user_id: message.sender_id,
|
|
|
|
email: message.sender_email,
|
|
|
|
},
|
|
|
|
];
|
2020-07-15 02:14:03 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "private":
|
|
|
|
involved_people = message.display_recipient;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
involved_people = [];
|
2016-12-15 23:33:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add new people involved in this message to the people list
|
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 person of involved_people) {
|
2017-11-06 16:47:19 +01:00
|
|
|
if (person.unknown_local_echo_user) {
|
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
|
|
|
continue;
|
2017-11-06 16:47:19 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const user_id = person.user_id || person.id;
|
2017-11-06 16:47:19 +01:00
|
|
|
|
|
|
|
if (people_by_user_id_dict.has(user_id)) {
|
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
|
|
|
continue;
|
2017-11-06 15:48:44 +01:00
|
|
|
}
|
2017-11-06 16:47:19 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
report_late_add(user_id, person.email);
|
2017-11-06 17:03:01 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
_add_user({
|
2017-11-06 16:47:19 +01:00
|
|
|
email: person.email,
|
2020-07-20 22:18:43 +02:00
|
|
|
user_id,
|
2017-11-06 16:47:19 +01:00
|
|
|
full_name: person.full_name,
|
|
|
|
is_admin: person.is_realm_admin || false,
|
|
|
|
is_bot: person.is_bot || false,
|
|
|
|
});
|
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
|
|
|
}
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-12-15 23:33:36 +01:00
|
|
|
|
2019-12-28 17:10:17 +01:00
|
|
|
function safe_lower(s) {
|
2020-07-15 01:29:15 +02:00
|
|
|
return (s || "").toLowerCase();
|
2019-12-28 17:10:17 +01:00
|
|
|
}
|
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function matches_user_settings_search(person, value) {
|
2020-02-25 12:46:14 +01:00
|
|
|
const email = settings_data.email_for_user_settings(person);
|
2019-12-28 17:10:17 +01:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
return safe_lower(person.full_name).includes(value) || safe_lower(email).includes(value);
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2019-12-28 17:10:17 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function filter_for_user_settings_search(persons, query) {
|
2020-01-13 17:45:53 +01:00
|
|
|
/*
|
|
|
|
TODO: For large realms, we can optimize this a couple
|
|
|
|
different ways. For realms that don't show
|
|
|
|
emails, we can make a simpler filter predicate
|
|
|
|
that works solely with full names. And we can
|
|
|
|
also consider two-pass filters that try more
|
|
|
|
stingy criteria first, such as exact prefix
|
|
|
|
matches, before widening the search.
|
|
|
|
|
|
|
|
See #13554 for more context.
|
|
|
|
*/
|
2020-08-20 21:33:00 +02:00
|
|
|
return persons.filter((person) => matches_user_settings_search(person, query));
|
|
|
|
}
|
2020-01-13 17:45:53 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function maybe_incr_recipient_count(message) {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (message.type !== "private") {
|
2017-11-06 15:48:44 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!message.sent_by_me) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Track the number of PMs we've sent to this person to improve autocomplete
|
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 recip of message.display_recipient) {
|
2020-01-01 13:02:34 +01:00
|
|
|
if (recip.unknown_local_echo_user) {
|
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
|
|
|
continue;
|
2016-12-15 23:33:36 +01:00
|
|
|
}
|
2017-11-06 15:48:44 +01:00
|
|
|
|
2020-01-01 13:02:34 +01:00
|
|
|
const user_id = recip.id;
|
2020-08-20 21:33:00 +02:00
|
|
|
incr_recipient_count(user_id);
|
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
|
|
|
}
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2016-12-15 23:33:36 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function set_full_name(person_obj, new_full_name) {
|
2017-01-21 00:02:28 +01:00
|
|
|
if (people_by_name_dict.has(person_obj.full_name)) {
|
2020-02-03 07:41:38 +01:00
|
|
|
people_by_name_dict.delete(person_obj.full_name);
|
2017-01-21 00:02:28 +01:00
|
|
|
}
|
2018-08-08 21:56:07 +02:00
|
|
|
// Remove previous and add new full name to the duplicate full name tracker.
|
2020-08-20 21:33:00 +02:00
|
|
|
track_duplicate_full_name(person_obj.full_name, person_obj.user_id, true);
|
|
|
|
track_duplicate_full_name(new_full_name, person_obj.user_id);
|
2017-01-21 00:02:28 +01:00
|
|
|
people_by_name_dict.set(new_full_name, person_obj);
|
|
|
|
person_obj.full_name = new_full_name;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-21 00:02:28 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function set_custom_profile_field_data(user_id, field) {
|
2018-03-10 14:41:44 +01:00
|
|
|
if (field.id === undefined) {
|
2020-02-07 22:26:05 +01:00
|
|
|
blueslip.error("Trying to set undefined field id");
|
2018-03-10 14:41:44 +01:00
|
|
|
return;
|
|
|
|
}
|
2018-12-31 06:41:06 +01:00
|
|
|
people_by_user_id_dict.get(user_id).profile_data[field.id] = {
|
|
|
|
value: field.value,
|
2018-12-31 07:45:33 +01:00
|
|
|
rendered_value: field.rendered_value,
|
2018-12-31 06:41:06 +01:00
|
|
|
};
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-03-10 14:41:44 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function is_current_user(email) {
|
2017-01-19 20:18:03 +01:00
|
|
|
if (email === null || email === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-01-19 23:04:52 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
return email.toLowerCase() === my_current_email().toLowerCase();
|
|
|
|
}
|
2017-01-19 20:18:03 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function initialize_current_user(user_id) {
|
2017-01-20 23:16:28 +01:00
|
|
|
my_user_id = user_id;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-19 23:04:52 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function my_full_name() {
|
2017-01-20 23:49:20 +01:00
|
|
|
return people_by_user_id_dict.get(my_user_id).full_name;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-20 23:49:20 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function my_current_email() {
|
2017-01-19 23:04:52 +01:00
|
|
|
return people_by_user_id_dict.get(my_user_id).email;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-19 20:18:03 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function my_current_user_id() {
|
2017-01-19 23:52:09 +01:00
|
|
|
return my_user_id;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-19 23:52:09 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function my_custom_profile_data(field_id) {
|
2018-02-26 20:09:07 +01:00
|
|
|
if (field_id === undefined) {
|
|
|
|
blueslip.error("Undefined field id");
|
|
|
|
return;
|
|
|
|
}
|
2020-08-20 21:33:00 +02:00
|
|
|
return get_custom_profile_data(my_user_id, field_id);
|
|
|
|
}
|
2018-04-23 20:41:35 +02:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function get_custom_profile_data(user_id, field_id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const profile_data = people_by_user_id_dict.get(user_id).profile_data;
|
2019-04-07 17:29:53 +02:00
|
|
|
if (profile_data === undefined) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return profile_data[field_id];
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2018-02-26 20:09:07 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function is_my_user_id(user_id) {
|
2017-01-20 17:43:45 +01:00
|
|
|
if (!user_id) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-12-31 14:02:30 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
if (typeof user_id !== "number") {
|
|
|
|
blueslip.error("user_id is a string in my_user_id: " + user_id);
|
2019-12-31 14:02:30 +01:00
|
|
|
user_id = parseInt(user_id, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
return user_id === my_user_id;
|
2020-08-20 21:33:00 +02:00
|
|
|
}
|
2017-01-20 17:43:45 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
export function initialize(my_user_id, params) {
|
2020-02-25 12:16:26 +01:00
|
|
|
for (const person of params.realm_users) {
|
2020-08-20 21:33:00 +02:00
|
|
|
add_active_user(person);
|
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
|
|
|
}
|
2014-01-30 22:42:19 +01:00
|
|
|
|
2020-02-25 12:16:26 +01:00
|
|
|
for (const person of params.realm_non_active_users) {
|
2020-05-26 19:14:41 +02:00
|
|
|
non_active_user_dict.set(person.user_id, person);
|
2020-08-20 21:33:00 +02:00
|
|
|
_add_user(person);
|
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
|
|
|
}
|
2017-11-06 14:56:06 +01:00
|
|
|
|
2020-02-25 12:16:26 +01:00
|
|
|
for (const person of params.cross_realm_bots) {
|
2020-08-20 21:33:00 +02:00
|
|
|
add_cross_realm_user(person);
|
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
|
|
|
}
|
2016-11-01 20:55:18 +01:00
|
|
|
|
2020-08-20 21:33:00 +02:00
|
|
|
initialize_current_user(my_user_id);
|
|
|
|
}
|