zulip/static/js/stream_data.js

860 lines
26 KiB
JavaScript
Raw Normal View History

const util = require("./util");
const FoldDict = require('./fold_dict').FoldDict;
const LazySet = require('./lazy_set').LazySet;
const settings_config = require("./settings_config");
const BinaryDict = function (pred) {
/*
A dictionary that keeps track of which objects had the predicate
return true or false for efficient lookups and iteration.
This class is an optimization for managing subscriptions.
Typically you only subscribe to a small minority of streams, and
most common operations want to efficiently iterate through only
streams where the current user is subscribed:
- search bar search
- build left sidebar
- autocomplete #stream_links
- autocomplete stream in compose
*/
const self = {};
self.trues = new FoldDict();
self.falses = new FoldDict();
self.true_values = function () {
return self.trues.values();
};
self.num_true_items = function () {
return self.trues.size;
};
self.false_values = function () {
return self.falses.values();
};
self.values = function* () {
for (const value of self.trues.values()) {
yield value;
}
for (const value of self.falses.values()) {
yield value;
}
};
self.get = function (k) {
const res = self.trues.get(k);
if (res !== undefined) {
return res;
}
return self.falses.get(k);
};
self.set = function (k, v) {
if (pred(v)) {
self.set_true(k, v);
} else {
self.set_false(k, v);
}
};
self.set_true = function (k, v) {
self.falses.delete(k);
self.trues.set(k, v);
};
self.set_false = function (k, v) {
self.trues.delete(k);
self.falses.set(k, v);
};
self.delete = function (k) {
self.trues.delete(k);
self.falses.delete(k);
};
return self;
};
// The stream_info variable maps stream names to stream properties objects
// Call clear_subscriptions() to initialize it.
let stream_info;
let subs_by_stream_id;
let filter_out_inactives = false;
const stream_ids_by_name = new FoldDict();
const default_stream_ids = new Set();
exports.stream_post_policy_values = {
everyone: {
code: 1,
description: i18n.t("All stream members can post"),
},
admins: {
code: 2,
description: i18n.t("Only organization administrators can post"),
},
non_new_members: {
code: 3,
description: i18n.t("Only organization full members can post"),
},
};
exports.clear_subscriptions = function () {
stream_info = new BinaryDict(function (sub) {
return sub.subscribed;
});
subs_by_stream_id = new Map();
};
2016-06-04 22:40:25 +02:00
exports.clear_subscriptions();
exports.set_filter_out_inactives = function () {
if (page_params.demote_inactive_streams ===
settings_config.demote_inactive_streams_values.automatic.code) {
filter_out_inactives = exports.num_subscribed_subs() >= 30;
} else if (page_params.demote_inactive_streams ===
settings_config.demote_inactive_streams_values.always.code) {
filter_out_inactives = true;
} else {
filter_out_inactives = false;
}
};
// for testing:
exports.is_filtering_inactives = function () {
return filter_out_inactives;
};
exports.is_active = function (sub) {
if (!filter_out_inactives || sub.pin_to_top) {
// If users don't want to filter inactive streams
// to the bottom, we respect that setting and don't
// treat any streams as dormant.
//
// Currently this setting is automatically determined
// by the number of streams. See the callers
// to set_filter_out_inactives.
return true;
}
return topic_data.stream_has_topics(sub.stream_id) || sub.newly_subscribed;
2016-10-28 17:18:56 +02:00
};
exports.rename_sub = function (sub, new_name) {
const old_name = sub.name;
stream_ids_by_name.set(old_name, sub.stream_id);
sub.name = new_name;
stream_info.delete(old_name);
stream_info.set(new_name, sub);
};
exports.subscribe_myself = function (sub) {
const user_id = people.my_current_user_id();
exports.add_subscriber(sub.name, user_id);
sub.subscribed = true;
sub.newly_subscribed = true;
stream_info.set_true(sub.name, sub);
};
exports.is_subscriber_subset = function (sub1, sub2) {
if (sub1.subscribers && sub2.subscribers) {
const sub2_set = sub2.subscribers;
return Array.from(sub1.subscribers.keys()).every(key => sub2_set.has(key));
}
return false;
};
exports.unsubscribe_myself = function (sub) {
// Remove user from subscriber's list
const user_id = people.my_current_user_id();
exports.remove_subscriber(sub.name, user_id);
sub.subscribed = false;
sub.newly_subscribed = false;
stream_info.set_false(sub.name, sub);
};
exports.add_sub = function (sub) {
if (!Object.prototype.hasOwnProperty.call(sub, 'subscribers')) {
sub.subscribers = new LazySet([]);
}
stream_info.set(sub.name, sub);
subs_by_stream_id.set(sub.stream_id, sub);
};
exports.get_sub = function (stream_name) {
return stream_info.get(stream_name);
};
exports.get_sub_by_id = function (stream_id) {
return subs_by_stream_id.get(stream_id);
};
exports.get_stream_id = function (name) {
// Note: Only use this function for situations where
// you are comfortable with a user dealing with an
// old name of a stream (from prior to a rename).
const sub = stream_info.get(name);
if (sub) {
return sub.stream_id;
}
const stream_id = stream_ids_by_name.get(name);
return stream_id;
};
exports.get_sub_by_name = function (name) {
// Note: Only use this function for situations where
// you are comfortable with a user dealing with an
// old name of a stream (from prior to a rename).
const sub = stream_info.get(name);
if (sub) {
return sub;
}
const stream_id = stream_ids_by_name.get(name);
if (!stream_id) {
return;
}
return subs_by_stream_id.get(stream_id);
};
exports.id_to_slug = function (stream_id) {
let name = exports.maybe_get_stream_name(stream_id) || 'unknown';
// The name part of the URL doesn't really matter, so we try to
// make it pretty.
name = name.replace(' ', '-');
return stream_id + '-' + name;
};
exports.name_to_slug = function (name) {
const stream_id = exports.get_stream_id(name);
if (!stream_id) {
return name;
}
// The name part of the URL doesn't really matter, so we try to
// make it pretty.
name = name.replace(' ', '-');
return stream_id + '-' + name;
};
exports.slug_to_name = function (slug) {
const m = /^([\d]+)-/.exec(slug);
if (m) {
const stream_id = parseInt(m[1], 10);
const sub = subs_by_stream_id.get(stream_id);
if (sub) {
return sub.name;
}
// if nothing was found above, we try to match on the stream
// name in the somewhat unlikely event they had a historical
// link to a stream like 4-horsemen
}
return slug;
};
exports.delete_sub = function (stream_id) {
const sub = subs_by_stream_id.get(stream_id);
if (!sub) {
blueslip.warn('Failed to delete stream ' + stream_id);
return;
}
subs_by_stream_id.delete(stream_id);
stream_info.delete(sub.name);
};
exports.get_non_default_stream_names = function () {
let subs = Array.from(stream_info.values());
subs = subs.filter(
sub => !exports.is_default_stream_id(sub.stream_id) && (sub.subscribed || !sub.invite_only)
);
const names = _.pluck(subs, 'name');
return names;
};
exports.get_unsorted_subs = function () {
return Array.from(stream_info.values());
};
exports.get_updated_unsorted_subs = function () {
// This function is expensive in terms of calculating
// some values (particularly stream counts) but avoids
// prematurely sorting subs.
let all_subs = Array.from(stream_info.values());
// Add in admin options and stream counts.
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 sub of all_subs) {
exports.update_calculated_fields(sub);
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
}
// We don't display unsubscribed streams to guest users.
if (page_params.is_guest) {
all_subs = all_subs.filter(sub => sub.subscribed);
}
return all_subs;
};
exports.num_subscribed_subs = function () {
return stream_info.num_true_items();
};
exports.subscribed_subs = function () {
return Array.from(stream_info.true_values());
};
exports.unsubscribed_subs = function () {
return Array.from(stream_info.false_values());
};
exports.subscribed_streams = function () {
return _.pluck(exports.subscribed_subs(), 'name');
};
exports.get_invite_stream_data = function () {
const filter_stream_data = function (sub) {
return {
name: sub.name,
stream_id: sub.stream_id,
invite_only: sub.invite_only,
default_stream: exports.get_default_status(sub.name),
};
};
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 invite_stream_data = exports.subscribed_subs().map(filter_stream_data);
const default_stream_data = page_params.realm_default_streams.map(filter_stream_data);
// Since, union doesn't work on array of objects we are using filter
const is_included = new Set();
const streams = default_stream_data.concat(invite_stream_data).filter(sub => {
if (is_included.has(sub.name)) {
return false;
}
is_included.add(sub.name);
return true;
});
return streams;
};
exports.invite_streams = function () {
const invite_list = exports.subscribed_streams();
const default_list = _.pluck(page_params.realm_default_streams, 'name');
return _.union(invite_list, default_list);
};
exports.get_colors = function () {
return _.pluck(exports.subscribed_subs(), 'color');
};
exports.update_subscribers_count = function (sub) {
const count = sub.subscribers.size;
sub.subscriber_count = count;
};
exports.update_stream_email_address = function (sub, email) {
sub.email_address = email;
};
exports.get_subscriber_count = function (stream_name) {
const sub = exports.get_sub_by_name(stream_name);
if (sub === undefined) {
blueslip.warn('We got a get_subscriber_count count call for a non-existent stream.');
return;
}
if (!sub.subscribers) {
return 0;
}
return sub.subscribers.size;
};
exports.update_stream_post_policy = function (sub, stream_post_policy) {
sub.stream_post_policy = stream_post_policy;
};
exports.update_stream_privacy = function (sub, values) {
sub.invite_only = values.invite_only;
sub.history_public_to_subscribers = values.history_public_to_subscribers;
};
exports.receives_notifications = function (stream_name, notification_name) {
const sub = exports.get_sub(stream_name);
if (sub === undefined) {
return false;
}
if (sub[notification_name] !== null) {
return sub[notification_name];
}
if (notification_name === 'wildcard_mentions_notify') {
return page_params[notification_name];
}
return page_params["enable_stream_" + notification_name];
};
const stream_notification_settings = [
"desktop_notifications",
"audible_notifications",
"push_notifications",
"email_notifications",
"wildcard_mentions_notify",
];
exports.update_calculated_fields = function (sub) {
sub.is_admin = page_params.is_admin;
// Admin can change any stream's name & description either stream is public or
// private, subscribed or unsubscribed.
sub.can_change_name_description = page_params.is_admin;
// If stream is public then any user can subscribe. If stream is private then only
// subscribed users can unsubscribe.
// Guest users can't subscribe themselves to any stream.
sub.should_display_subscription_button = sub.subscribed ||
!page_params.is_guest && !sub.invite_only;
sub.should_display_preview_button = sub.subscribed || !sub.invite_only ||
sub.previously_subscribed;
sub.can_change_stream_permissions = page_params.is_admin && (
!sub.invite_only || sub.subscribed);
// User can add other users to stream if stream is public or user is subscribed to stream.
// Guest users can't access subscribers of any(public or private) non-subscribed streams.
sub.can_access_subscribers = page_params.is_admin || sub.subscribed || !page_params.is_guest &&
!sub.invite_only;
sub.preview_url = hash_util.by_stream_uri(sub.stream_id);
sub.can_add_subscribers = !page_params.is_guest && (!sub.invite_only || sub.subscribed);
if (sub.rendered_description !== undefined) {
sub.rendered_description = sub.rendered_description.replace('<p>', '').replace('</p>', '');
}
exports.update_subscribers_count(sub);
// Apply the defaults for our notification settings for rendering.
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 setting of stream_notification_settings) {
sub[setting + "_display"] = exports.receives_notifications(sub.name, setting);
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.all_subscribed_streams_are_in_home_view = function () {
return exports.subscribed_subs().every(sub => !sub.is_muted);
};
exports.home_view_stream_names = function () {
const home_view_subs = exports.subscribed_subs().filter(sub => !sub.is_muted);
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 home_view_subs.map(sub => sub.name);
};
exports.canonicalized_name = function (stream_name) {
return stream_name.toString().toLowerCase();
};
exports.get_color = function (stream_name) {
const sub = exports.get_sub(stream_name);
if (sub === undefined) {
return stream_color.default_color;
}
return sub.color;
};
exports.is_muted = function (stream_id) {
const sub = exports.get_sub_by_id(stream_id);
// Return true for undefined streams
if (sub === undefined) {
return true;
}
return sub.is_muted;
};
exports.is_stream_muted_by_name = function (stream_name) {
const sub = exports.get_sub(stream_name);
// Return true for undefined streams
if (sub === undefined) {
return true;
}
return sub.is_muted;
};
exports.is_notifications_stream_muted = function () {
// TODO: add page_params.notifications_stream_id
return exports.is_stream_muted_by_name(page_params.notifications_stream);
};
exports.is_subscribed = function (stream_name) {
const sub = exports.get_sub(stream_name);
return sub !== undefined && sub.subscribed;
};
exports.id_is_subscribed = function (stream_id) {
const sub = subs_by_stream_id.get(stream_id);
return sub !== undefined && sub.subscribed;
};
exports.get_invite_only = function (stream_name) {
const sub = exports.get_sub(stream_name);
if (sub === undefined) {
return false;
}
return sub.invite_only;
};
exports.get_stream_post_policy = function (stream_name) {
const sub = exports.get_sub(stream_name);
if (sub === undefined) {
return false;
}
return sub.stream_post_policy;
};
exports.all_topics_in_cache = function (sub) {
// Checks whether this browser's cache of contiguous messages
// (used to locally render narrows) in message_list.all has all
// messages from a given stream, and thus all historical topics
// for it. Because message_list.all is a range, we just need to
// compare it to the range of history on the stream.
// If the cache isn't initialized, it's a clear false.
if (message_list.all === undefined || message_list.all.empty()) {
return false;
}
// If the cache doesn't have the latest messages, we can't be sure
// we have all topics.
if (!message_list.all.fetch_status.has_found_newest()) {
return false;
}
if (sub.first_message_id === null) {
// If the stream has no message history, we have it all
// vacuously. This should be a very rare condition, since
// stream creation sends a message.
return true;
}
// Now, we can just compare the first cached message to the first
// message ID in the stream; if it's older, we're good, otherwise,
// we might be missing the oldest topics in this stream in our
// cache.
const first_cached_message = message_list.all.first();
return first_cached_message.id <= sub.first_message_id;
};
exports.set_realm_default_streams = function (realm_default_streams) {
page_params.realm_default_streams = realm_default_streams;
default_stream_ids.clear();
realm_default_streams.forEach(function (stream) {
default_stream_ids.add(stream.stream_id);
});
};
exports.get_default_stream_names = function () {
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 streams = Array.from(default_stream_ids).map(exports.get_sub_by_id);
const default_stream_names = _.pluck(streams, 'name');
return default_stream_names;
};
exports.get_default_status = function (stream_name) {
const stream_id = exports.get_stream_id(stream_name);
if (!stream_id) {
return false;
}
return default_stream_ids.has(stream_id);
};
exports.is_default_stream_id = function (stream_id) {
return default_stream_ids.has(stream_id);
};
exports.get_name = function (stream_name) {
// This returns the actual name of a stream if we are subscribed to
// it (i.e "Denmark" vs. "denmark"), while falling thru to
// stream_name if we don't have a subscription. (Stream names
// are case-insensitive, but we try to display the actual name
// when we know it.)
//
// This function will also do the right thing if we have
// an old stream name in memory for a recently renamed stream.
const sub = exports.get_sub_by_name(stream_name);
if (sub === undefined) {
return stream_name;
}
return sub.name;
};
exports.maybe_get_stream_name = function (stream_id) {
if (!stream_id) {
return;
}
const stream = exports.get_sub_by_id(stream_id);
if (!stream) {
return;
}
return stream.name;
};
exports.set_subscribers = function (sub, user_ids) {
sub.subscribers = new LazySet(user_ids || []);
};
exports.add_subscriber = function (stream_name, user_id) {
const sub = exports.get_sub(stream_name);
if (typeof sub === 'undefined') {
blueslip.warn("We got an add_subscriber call for a non-existent stream.");
return false;
}
const person = people.get_by_user_id(user_id);
if (person === undefined) {
blueslip.error("We tried to add invalid subscriber: " + user_id);
return false;
}
sub.subscribers.add(user_id);
return true;
};
exports.remove_subscriber = function (stream_name, user_id) {
const sub = exports.get_sub(stream_name);
if (typeof sub === 'undefined') {
blueslip.warn("We got a remove_subscriber call for a non-existent stream " + stream_name);
return false;
}
if (!sub.subscribers.has(user_id)) {
blueslip.warn("We tried to remove invalid subscriber: " + user_id);
return false;
}
sub.subscribers.delete(user_id);
return true;
};
exports.is_user_subscribed = function (stream_name, user_id) {
const sub = exports.get_sub(stream_name);
if (typeof sub === 'undefined' || !sub.can_access_subscribers) {
// If we don't know about the stream, or we ourselves cannot access subscriber list,
// so we return undefined (treated as falsy if not explicitly handled).
blueslip.warn("We got a is_user_subscribed call for a non-existent or inaccessible stream.");
return;
}
if (typeof user_id === "undefined") {
blueslip.warn("Undefined user_id passed to function is_user_subscribed");
return;
}
return sub.subscribers.has(user_id);
};
2016-10-15 21:10:10 +02:00
exports.create_streams = function (streams) {
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 stream of streams) {
// We handle subscriber stuff in other events.
const attrs = {
subscribers: [],
subscribed: false,
...stream,
};
2016-10-15 21:10:10 +02:00
exports.create_sub_from_server_data(stream.name, attrs);
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-10-15 21:10:10 +02:00
};
exports.create_sub_from_server_data = function (stream_name, attrs) {
let sub = exports.get_sub(stream_name);
if (sub !== undefined) {
// We've already created this subscription, no need to continue.
return sub;
}
if (!attrs.stream_id) {
// fail fast (blueslip.fatal will throw an error on our behalf)
blueslip.fatal("We cannot create a sub without a stream_id");
return; // this line is never actually reached
}
// Our internal data structure for subscriptions is mostly plain dictionaries,
// so we just reuse the attrs that are passed in to us, but we encapsulate how
// we handle subscribers. We defensively remove the `subscribers` field from
// the original `attrs` object, which will get thrown away. (We used to make
// a copy of the object with `_.omit(attrs, 'subscribers')`, but `_.omit` is
// slow enough to show up in timings when you have 1000s of streams.
const subscriber_user_ids = attrs.subscribers;
delete attrs.subscribers;
sub = {
name: stream_name,
render_subscribers: !page_params.realm_is_zephyr_mirror_realm || attrs.invite_only === true,
subscribed: true,
newly_subscribed: false,
is_muted: false,
invite_only: false,
desktop_notifications: page_params.enable_stream_desktop_notifications,
audible_notifications: page_params.enable_stream_audible_notifications,
push_notifications: page_params.enable_stream_push_notifications,
email_notifications: page_params.enable_stream_email_notifications,
description: '',
rendered_description: '',
first_message_id: attrs.first_message_id,
...attrs,
};
exports.set_subscribers(sub, subscriber_user_ids);
if (!sub.color) {
sub.color = color_data.pick_color();
}
exports.update_calculated_fields(sub);
exports.add_sub(sub);
return sub;
};
exports.get_streams_for_settings_page = function () {
// TODO: This function is only used for copy-from-stream, so
// the current name is slightly misleading now, plus
// it's not entirely clear we need unsubscribed streams
// for that. Also we may be revisiting that UI.
// Build up our list of subscribed streams from the data we already have.
const subscribed_rows = exports.subscribed_subs();
const unsubscribed_rows = exports.unsubscribed_subs();
// Sort and combine all our streams.
function by_name(a, b) {
return util.strcmp(a.name, b.name);
}
subscribed_rows.sort(by_name);
unsubscribed_rows.sort(by_name);
const all_subs = unsubscribed_rows.concat(subscribed_rows);
// Add in admin options and stream counts.
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 sub of all_subs) {
exports.update_calculated_fields(sub);
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
}
return all_subs;
};
exports.sort_for_stream_settings = function (stream_ids) {
// TODO: We may want to simply use util.strcmp here,
// which uses Intl.Collator() when possible.
function name(stream_id) {
const sub = exports.get_sub_by_id(stream_id);
if (!sub) {
return '';
}
return sub.name.toLocaleLowerCase();
}
function by_stream_name(id_a, id_b) {
const stream_a_name = name(id_a);
const stream_b_name = name(id_b);
return String.prototype.localeCompare.call(stream_a_name, stream_b_name);
}
stream_ids.sort(by_stream_name);
};
exports.get_streams_for_admin = function () {
// Sort and combine all our streams.
function by_name(a, b) {
return util.strcmp(a.name, b.name);
}
const subs = Array.from(stream_info.values());
subs.sort(by_name);
return subs;
};
exports.initialize = function (params) {
/*
We get `params` data, which is data that we "own"
and which has already been removed from `page_params`.
We only use it in this function to populate other
data structures.
*/
const subscriptions = params.subscriptions;
const unsubscribed = params.unsubscribed;
const never_subscribed = params.never_subscribed;
/*
We also consume some data directly from `page_params`.
This data can be accessed by any other module,
and we consider the authoritative source to be
`page_params`. Some of this data should eventually
be fully handled by stream_data. In particular,
the way we handle `page_params.realm_default_streams`
is kinda janky, because we maintain our own data
structure, but then some legacy modules still
refer directly to `page_params`. We should fix that.
*/
color_data.claim_colors(subscriptions);
function populate_subscriptions(subs, subscribed, previously_subscribed) {
subs.forEach(function (sub) {
const stream_name = sub.name;
sub.subscribed = subscribed;
sub.previously_subscribed = previously_subscribed;
exports.create_sub_from_server_data(stream_name, sub);
});
}
exports.set_realm_default_streams(page_params.realm_default_streams);
populate_subscriptions(subscriptions, true, true);
populate_subscriptions(unsubscribed, false, true);
populate_subscriptions(never_subscribed, false, false);
// Migrate the notifications stream from the new API structure to
// what the frontend expects.
if (page_params.realm_notifications_stream_id !== -1) {
const notifications_stream_obj =
exports.get_sub_by_id(page_params.realm_notifications_stream_id);
if (notifications_stream_obj) {
// This happens when the notifications stream is a private
// stream the current user is not subscribed to.
page_params.notifications_stream = notifications_stream_obj.name;
} else {
page_params.notifications_stream = "";
}
} else {
page_params.notifications_stream = "";
}
exports.set_filter_out_inactives();
};
exports.remove_default_stream = function (stream_id) {
page_params.realm_default_streams = page_params.realm_default_streams.filter(
stream => stream.stream_id !== stream_id
);
default_stream_ids.delete(stream_id);
};
window.stream_data = exports;