zulip/static/js/topic_list.js

291 lines
7.0 KiB
JavaScript
Raw Normal View History

const render_more_topics = require('../templates/more_topics.hbs');
const render_more_topics_spinner = require('../templates/more_topics_spinner.hbs');
const render_topic_list_item = require('../templates/topic_list_item.hbs');
const IntDict = require('./int_dict').IntDict;
const topic_list_data = require('./topic_list_data');
/*
Track all active widgets with an IntDict.
(We have at max one for now, but we may
eventually allow multiple streams to be
expanded.)
*/
const active_widgets = new IntDict();
// We know whether we're zoomed or not.
let zoomed = false;
exports.update = function () {
for (const widget of active_widgets.values()) {
widget.build();
}
};
exports.remove_expanded_topics = function () {
stream_popover.hide_topic_popover();
for (const widget of active_widgets.values()) {
widget.remove();
}
active_widgets.clear();
};
exports.close = function () {
zoomed = false;
exports.remove_expanded_topics();
};
exports.zoom_out = function () {
zoomed = false;
const stream_ids = Array.from(active_widgets.keys());
if (stream_ids.length !== 1) {
blueslip.error('Unexpected number of topic lists to zoom out.');
return;
}
const stream_id = stream_ids[0];
const widget = active_widgets.get(stream_id);
const parent_widget = widget.get_parent();
exports.rebuild(parent_widget, stream_id);
};
exports.keyed_topic_li = (convo) => {
const render = () => {
return render_topic_list_item(convo);
};
const eq = (other) => {
return _.isEqual(convo, other.convo);
};
const key = 't:' + convo.topic_name;
return {
key: key,
render: render,
convo: convo,
eq: eq,
};
};
exports.more_li = (more_topics_unreads) => {
const render = () => {
return render_more_topics({
more_topics_unreads: more_topics_unreads,
});
};
const eq = (other) => {
return other.more_items &&
more_topics_unreads === other.more_topics_unreads;
};
const key = 'more';
return {
key: key,
more_items: true,
more_topics_unreads: more_topics_unreads,
render: render,
eq: eq,
};
};
exports.spinner_li = () => {
const render = () => {
return render_more_topics_spinner();
};
const eq = (other) => {
return other.spinner;
};
const key = 'more';
return {
key: key,
spinner: true,
render: render,
eq: eq,
};
};
exports.widget = function (parent_elem, my_stream_id) {
const self = {};
self.prior_dom = undefined;
self.build_list = function (spinner) {
const list_info = topic_list_data.get_list_info(
my_stream_id, zoomed);
const num_possible_topics = list_info.num_possible_topics;
const more_topics_unreads = list_info.more_topics_unreads;
const is_showing_all_possible_topics =
list_info.items.length === num_possible_topics &&
topic_data.is_complete_for_stream_id(my_stream_id);
const attrs = [
['class', 'topic-list'],
];
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 nodes = list_info.items.map(exports.keyed_topic_li);
if (spinner) {
nodes.push(exports.spinner_li());
} else if (!is_showing_all_possible_topics) {
nodes.push(exports.more_li(more_topics_unreads));
}
const dom = vdom.ul({
attrs: attrs,
keyed_nodes: nodes,
});
return dom;
};
self.get_parent = function () {
return parent_elem;
};
self.get_stream_id = function () {
return my_stream_id;
};
self.remove = function () {
parent_elem.find('.topic-list').remove();
self.prior_dom = undefined;
};
self.build = function (spinner) {
const new_dom = self.build_list(spinner);
function replace_content(html) {
self.remove();
parent_elem.append(html);
}
function find() {
return parent_elem.find('.topic-list');
}
vdom.update(replace_content, find, new_dom, self.prior_dom);
self.prior_dom = new_dom;
};
return self;
};
2017-08-11 00:29:35 +02:00
exports.active_stream_id = function () {
const stream_ids = Array.from(active_widgets.keys());
if (stream_ids.length !== 1) {
2017-08-11 00:29:35 +02:00
return;
}
return stream_ids[0];
2017-08-11 00:29:35 +02:00
};
exports.get_stream_li = function () {
const widgets = Array.from(active_widgets.values());
if (widgets.length !== 1) {
return;
}
const stream_li = widgets[0].get_parent();
return stream_li;
};
exports.rebuild = function (stream_li, stream_id) {
const active_widget = active_widgets.get(stream_id);
if (active_widget) {
active_widget.build();
return;
}
exports.remove_expanded_topics();
const widget = exports.widget(stream_li, stream_id);
widget.build();
active_widgets.set(stream_id, widget);
};
// For zooming, we only do topic-list stuff here...let stream_list
// handle hiding/showing the non-narrowed streams
exports.zoom_in = function () {
zoomed = true;
const stream_id = exports.active_stream_id();
if (!stream_id) {
blueslip.error('Cannot find widget for topic history zooming.');
return;
}
const active_widget = active_widgets.get(stream_id);
function on_success() {
if (!active_widgets.has(stream_id)) {
blueslip.warn('User re-narrowed before topic history was returned.');
return;
}
if (!zoomed) {
blueslip.warn('User zoomed out before topic history was returned.');
// Note that we could attempt to re-draw the zoomed out topic list
// here, given that we have more history, but that might be more
// confusing than helpful to a user who is likely trying to browse
// other streams.
return;
}
active_widget.build();
}
ui.get_scroll_element($('#stream-filters-container')).scrollTop(0);
const spinner = true;
active_widget.build(spinner);
topic_data.get_server_history(stream_id, on_success);
};
exports.initialize = function () {
$('#stream_filters').on('click', '.topic-box', function (e) {
if (e.metaKey || e.ctrlKey) {
return;
}
if ($(e.target).closest('.show-more-topics').length > 0) {
return;
}
// In a more componentized world, we would delegate some
// of this stuff back up to our parents.
const stream_row = $(e.target).parents('.narrow-filter');
const stream_id = parseInt(stream_row.attr('data-stream-id'), 10);
const sub = stream_data.get_sub_by_id(stream_id);
const topic = $(e.target).parents('li').attr('data-topic-name');
narrow.activate([
{operator: 'stream', operand: sub.name},
{operator: 'topic', operand: topic}],
{trigger: 'sidebar'});
e.preventDefault();
});
};
window.topic_list = exports;