zulip/static/js/activity.js

495 lines
14 KiB
JavaScript
Raw Normal View History

const render_group_pms = require('../templates/group_pms.hbs');
const Dict = require('./dict').Dict;
/*
Helpers for detecting user activity and managing user idle states
*/
/* Broadcast "idle" to server after 5 minutes of local inactivity */
const DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
/* Time between keep-alive pings */
const ACTIVE_PING_INTERVAL_MS = 50 * 1000;
/* Keep in sync with views.py:update_active_status_backend() */
exports.ACTIVE = "active";
exports.IDLE = "idle";
// When you open Zulip in a new browser window, client_is_active
// should be true. When a server-initiated reload happens, however,
// it should be initialized to false. We handle this with a check for
// whether the window is focused at initialization time.
exports.client_is_active = document.hasFocus && document.hasFocus();
// new_user_input is a more strict version of client_is_active used
// primarily for analytics. We initialize this to true, to count new
// page loads, but set it to false in the onload function in reload.js
// if this was a server-initiated-reload to avoid counting a
// server-initiated reload as user activity.
exports.new_user_input = true;
const huddle_timestamps = new Dict();
function update_pm_count_in_dom(count_span, value_span, count) {
const li = count_span.parents('li');
if (count === 0) {
count_span.hide();
li.removeClass("user-with-count");
value_span.text('');
return;
}
count_span.show();
li.addClass("user-with-count");
value_span.text(count);
}
function update_group_count_in_dom(count_span, value_span, count) {
const li = count_span.parent();
if (count === 0) {
count_span.hide();
li.removeClass("group-with-count");
value_span.text('');
return;
}
count_span.show();
li.addClass("group-with-count");
value_span.text(count);
}
function get_pm_list_item(user_id) {
2018-04-19 14:17:22 +02:00
return buddy_list.find_li({
key: user_id,
});
}
function get_group_list_item(user_ids_string) {
return $("li.group-pms-sidebar-entry[data-user-ids='" + user_ids_string + "']");
}
function set_pm_count(user_ids_string, count) {
const count_span = get_pm_list_item(user_ids_string).find('.count');
const value_span = count_span.find('.value');
update_pm_count_in_dom(count_span, value_span, count);
}
function set_group_count(user_ids_string, count) {
const count_span = get_group_list_item(user_ids_string).find('.count');
const value_span = count_span.find('.value');
update_group_count_in_dom(count_span, value_span, count);
}
exports.update_dom_with_unread_counts = function (counts) {
// counts is just a data object that gets calculated elsewhere
// Our job is to update some DOM elements.
for (const [user_ids_string, count] of counts.pm_count) {
// TODO: just use user_ids_string in our markup
js: Convert a.indexOf(…) !== -1 to a.includes(…). Babel polyfills this for us for Internet Explorer. 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, { visitBinaryExpression(path) { const { operator, left, right } = path.node; if ( n.CallExpression.check(left) && n.MemberExpression.check(left.callee) && !left.callee.computed && n.Identifier.check(left.callee.property) && left.callee.property.name === "indexOf" && left.arguments.length === 1 && checkExpression(left.arguments[0]) && ((["===", "!==", "==", "!=", ">", "<="].includes(operator) && n.UnaryExpression.check(right) && right.operator == "-" && n.Literal.check(right.argument) && right.argument.value === 1) || ([">=", "<"].includes(operator) && n.Literal.check(right) && right.value === 0)) ) { const test = b.callExpression( b.memberExpression(left.callee.object, b.identifier("includes")), [left.arguments[0]] ); path.replace( ["!==", "!=", ">", ">="].includes(operator) ? test : b.unaryExpression("!", test) ); 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 04:55:06 +01:00
const is_pm = !user_ids_string.includes(',');
if (is_pm) {
set_pm_count(user_ids_string, count);
} else {
set_group_count(user_ids_string, count);
}
}
};
exports.process_loaded_messages = function (messages) {
let need_resize = 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
for (const message of messages) {
const huddle_string = people.huddle_string(message);
if (huddle_string) {
const old_timestamp = huddle_timestamps.get(huddle_string);
if (!old_timestamp || old_timestamp < message.timestamp) {
huddle_timestamps.set(huddle_string, message.timestamp);
need_resize = 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
}
exports.update_huddles();
if (need_resize) {
resize.resize_page_components(); // big hammer
}
};
exports.get_huddles = function () {
let huddles = Array.from(huddle_timestamps.keys());
huddles = _.sortBy(huddles, function (huddle) {
return huddle_timestamps.get(huddle);
});
return huddles.reverse();
};
function huddle_split(huddle) {
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 huddle.split(',').map(s => parseInt(s, 10));
}
exports.full_huddle_name = function (huddle) {
const user_ids = huddle_split(huddle);
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
const names = user_ids.map(user_id => {
const person = people.get_by_user_id(user_id);
return person.full_name;
});
return names.join(', ');
};
exports.short_huddle_name = function (huddle) {
const user_ids = huddle_split(huddle);
const num_to_show = 3;
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
let names = user_ids.map(user_id => {
const person = people.get_by_user_id(user_id);
return person.full_name;
});
names = _.sortBy(names, function (name) { return name.toLowerCase(); });
names = names.slice(0, num_to_show);
const others = user_ids.length - num_to_show;
if (others === 1) {
names.push("+ 1 other");
} else if (others >= 2) {
names.push("+ " + others + " others");
}
return names.join(', ');
};
function mark_client_idle() {
// When we become idle, we don't immediately send anything to the
// server; instead, we wait for our next periodic update, since
// this data is fundamentally not timely.
exports.client_is_active = false;
}
exports.redraw_user = function (user_id) {
if (page_params.realm_presence_disabled) {
return;
}
const filter_text = exports.get_filter_text();
2018-04-19 15:46:56 +02:00
if (!buddy_data.matches_filter(filter_text, user_id)) {
return;
}
const info = buddy_data.get_item(user_id);
2018-04-19 15:46:56 +02:00
2018-04-19 14:17:22 +02:00
buddy_list.insert_or_move({
key: user_id,
item: info,
});
};
exports.searching = function () {
return exports.user_filter && exports.user_filter.searching();
};
exports.build_user_sidebar = function () {
if (page_params.realm_presence_disabled) {
return;
}
const filter_text = exports.get_filter_text();
const user_ids = buddy_data.get_filtered_and_sorted_user_ids(filter_text);
2018-04-19 14:17:22 +02:00
const finish = blueslip.start_timing('buddy_list.populate');
2018-04-19 14:17:22 +02:00
buddy_list.populate({
keys: user_ids,
2018-04-19 14:17:22 +02:00
});
finish();
resize.resize_page_components();
return user_ids; // for testing
};
function do_update_users_for_search() {
// Hide all the popovers but not userlist sidebar
// when the user is searching.
popovers.hide_all_except_sidebars();
exports.build_user_sidebar();
exports.user_cursor.reset();
}
const update_users_for_search = _.throttle(do_update_users_for_search, 50);
function show_huddles() {
$('#group-pm-list').addClass("show");
}
function hide_huddles() {
$('#group-pm-list').removeClass("show");
}
exports.update_huddles = function () {
if (page_params.realm_presence_disabled) {
return;
}
const huddles = exports.get_huddles().slice(0, 10);
if (huddles.length === 0) {
hide_huddles();
return;
}
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
const group_pms = huddles.map(huddle => ({
user_ids_string: huddle,
name: exports.full_huddle_name(huddle),
href: hash_util.huddle_with_uri(huddle),
fraction_present: buddy_data.huddle_fraction_present(huddle),
short_name: exports.short_huddle_name(huddle),
}));
const html = render_group_pms({group_pms: group_pms});
ui.get_content_element($('#group-pms')).html(html);
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_ids_string of huddles) {
const count = unread.num_unread_for_person(user_ids_string);
set_group_count(user_ids_string, count);
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
}
show_huddles();
};
exports.compute_active_status = function () {
// The overall algorithm intent for the `status` field is to send
// `ACTIVE` (aka green circle) if we know the user is at their
// computer, and IDLE (aka orange circle) if the user might not
// be:
//
// * For the webapp, we just know whether this window has focus.
// * For the electron desktop app, we also know whether the
// user is active or idle elsewhere on their system.
//
// The check for `idle_on_system === undefined` is feature
// detection; older desktop app releases never set that property.
if (window.electron_bridge !== undefined
&& window.electron_bridge.idle_on_system !== undefined) {
if (window.electron_bridge.idle_on_system) {
return exports.IDLE;
}
return exports.ACTIVE;
}
if (exports.client_is_active) {
return exports.ACTIVE;
}
return exports.IDLE;
};
function send_presence_to_server(want_redraw) {
if (reload_state.is_in_progress()) {
blueslip.log("Skipping querying presence because reload in progress");
return;
}
channel.post({
url: '/json/users/me/presence',
data: {
status: exports.compute_active_status(),
ping_only: !want_redraw,
new_user_input: exports.new_user_input,
slim_presence: true,
},
idempotent: true,
success: function (data) {
// Update Zephyr mirror activity warning
if (data.zephyr_mirror_active === false) {
$('#zephyr-mirror-error').addClass("show");
} else {
$('#zephyr-mirror-error').removeClass("show");
}
exports.new_user_input = false;
// Zulip has 2 data feeds coming from the server to the
// client: The server_events data, and this presence feed.
// Everything in server_events is nicely serialized, but
// if we've been offline and not running for a while
// (e.g. due to suspend), we can end up throwing
// exceptions due to users appearing in presence that we
// haven't learned about yet. We handle this in 2 stages.
// First, here, we make sure that we've confirmed whether
// we are indeed in the unsuspend case. Then, in
// `presence.set_info`, we only complain about unknown
// users if server_events does not suspect we're offline.
server_events.check_for_unsuspend();
if (want_redraw) {
presence.set_info(data.presences, data.server_timestamp);
exports.redraw();
}
},
});
}
function mark_client_active() {
if (!exports.client_is_active) {
exports.client_is_active = true;
send_presence_to_server(false);
}
}
exports.initialize = function () {
$("html").on("mousemove", function () {
exports.new_user_input = true;
});
$(window).focus(mark_client_active);
$(window).idle({idle: DEFAULT_IDLE_TIMEOUT_MS,
onIdle: mark_client_idle,
onActive: mark_client_active,
keepTracking: true});
presence.set_info(page_params.presences,
page_params.initial_servertime);
delete page_params.presences;
exports.set_cursor_and_filter();
exports.build_user_sidebar();
exports.update_huddles();
buddy_list.start_scroll_handler();
// Let the server know we're here, but pass "false" for
// want_redraw, since we just got all this info in page_params.
send_presence_to_server(false);
function get_full_presence_list_update() {
send_presence_to_server(true);
}
setInterval(get_full_presence_list_update, ACTIVE_PING_INTERVAL_MS);
};
exports.update_presence_info = function (user_id, info, server_time) {
presence.set_info_for_user(user_id, info, server_time);
exports.redraw_user(user_id);
exports.update_huddles();
pm_list.update_private_messages();
};
exports.on_set_away = function (user_id) {
user_status.set_away(user_id);
exports.redraw_user(user_id);
pm_list.update_private_messages();
};
exports.on_revoke_away = function (user_id) {
user_status.revoke_away(user_id);
exports.redraw_user(user_id);
pm_list.update_private_messages();
};
exports.redraw = function () {
exports.build_user_sidebar();
exports.user_cursor.redraw();
exports.update_huddles();
pm_list.update_private_messages();
};
exports.reset_users = function () {
// Call this when we're leaving the search widget.
exports.build_user_sidebar();
exports.user_cursor.clear();
};
exports.narrow_for_user = function (opts) {
const user_id = buddy_list.get_key_from_li({li: opts.li});
return exports.narrow_for_user_id({user_id: user_id});
};
exports.narrow_for_user_id = function (opts) {
const person = people.get_by_user_id(opts.user_id);
const email = person.email;
narrow.by('pm-with', email, {trigger: 'sidebar'});
exports.user_filter.clear_and_hide_search();
};
function keydown_enter_key() {
const user_id = exports.user_cursor.get_key();
if (user_id === undefined) {
return;
}
exports.narrow_for_user_id({user_id: user_id});
popovers.hide_all();
}
exports.set_cursor_and_filter = function () {
exports.user_cursor = list_cursor({
list: buddy_list,
highlight_class: 'highlighted_user',
});
exports.user_filter = user_search({
update_list: update_users_for_search,
reset_items: exports.reset_users,
on_focus: exports.user_cursor.reset,
});
const $input = exports.user_filter.input_field();
$input.on('blur', exports.user_cursor.clear);
keydown_util.handle({
elem: $input,
handlers: {
enter_key: function () {
keydown_enter_key();
return true;
},
up_arrow: function () {
exports.user_cursor.prev();
return true;
},
down_arrow: function () {
exports.user_cursor.next();
return true;
},
},
});
};
exports.initiate_search = function () {
if (exports.user_filter) {
exports.user_filter.initiate_search();
}
};
exports.escape_search = function () {
if (exports.user_filter) {
exports.user_filter.escape_search();
}
};
exports.get_filter_text = function () {
if (!exports.user_filter) {
// This may be overly defensive, but there may be
// situations where get called before everything is
// fully initialized. The empty string is a fine
// default here.
blueslip.warn('get_filter_text() is called before initialization');
return '';
}
return exports.user_filter.text();
};
window.activity = exports;