2019-11-02 00:06:25 +01:00
|
|
|
const render_more_topics = require('../templates/more_topics.hbs');
|
|
|
|
const render_topic_list_item = require('../templates/topic_list_item.hbs');
|
|
|
|
const Dict = require('./dict').Dict;
|
2019-12-26 15:34:17 +01:00
|
|
|
const FoldDict = require('./fold_dict').FoldDict;
|
2019-02-08 11:56:33 +01:00
|
|
|
|
2019-03-09 16:32:41 +01:00
|
|
|
/*
|
|
|
|
Track all active widgets with a Dict.
|
|
|
|
|
|
|
|
(We have at max one for now, but we may
|
|
|
|
eventually allow multiple streams to be
|
|
|
|
expanded.)
|
|
|
|
*/
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const active_widgets = new Dict();
|
2016-10-27 01:36:20 +02:00
|
|
|
|
2016-11-10 16:15:12 +01:00
|
|
|
// We know whether we're zoomed or not.
|
2019-11-02 00:06:25 +01:00
|
|
|
let zoomed = false;
|
2016-11-10 16:15:12 +01:00
|
|
|
|
2016-10-27 01:53:47 +02:00
|
|
|
exports.remove_expanded_topics = function () {
|
2017-03-05 17:28:40 +01:00
|
|
|
stream_popover.hide_topic_popover();
|
2016-10-27 01:53:47 +02:00
|
|
|
|
2019-03-09 16:32:41 +01:00
|
|
|
_.each(active_widgets.values(), function (widget) {
|
|
|
|
widget.remove();
|
|
|
|
});
|
|
|
|
|
|
|
|
active_widgets.clear();
|
2016-11-10 15:35:14 +01:00
|
|
|
};
|
2016-10-27 01:36:20 +02:00
|
|
|
|
2017-08-11 00:30:23 +02:00
|
|
|
exports.close = function () {
|
|
|
|
zoomed = false;
|
|
|
|
exports.remove_expanded_topics();
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.zoom_out = function () {
|
|
|
|
zoomed = false;
|
2019-03-09 16:32:41 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_ids = active_widgets.keys();
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
if (stream_ids.length !== 1) {
|
|
|
|
blueslip.error('Unexpected number of topic lists to zoom out.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_id = stream_ids[0];
|
|
|
|
const widget = active_widgets.get(stream_id);
|
|
|
|
const parent_widget = widget.get_parent();
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
exports.rebuild(parent_widget, stream_id);
|
2017-08-11 00:30:23 +02:00
|
|
|
};
|
|
|
|
|
2016-11-12 01:01:20 +01:00
|
|
|
function update_unread_count(unread_count_elem, count) {
|
|
|
|
// unread_count_elem is a jquery element...we expect DOM
|
|
|
|
// to look like this:
|
2016-11-12 01:14:34 +01:00
|
|
|
// <div class="topic-unread-count {{#if is_zero}}zero_count{{/if}}">
|
2016-11-12 01:01:20 +01:00
|
|
|
// <div class="value">{{unread}}</div>
|
|
|
|
// </div>
|
2019-11-02 00:06:25 +01:00
|
|
|
const value_span = unread_count_elem.find('.value');
|
2016-11-12 01:01:20 +01:00
|
|
|
|
|
|
|
if (value_span.length === 0) {
|
|
|
|
blueslip.error('malformed dom for unread count');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-10-27 01:02:41 +02:00
|
|
|
if (count === 0) {
|
2019-08-29 01:41:23 +02:00
|
|
|
unread_count_elem.addClass("zero_count");
|
2016-10-27 01:02:41 +02:00
|
|
|
value_span.text('');
|
|
|
|
} else {
|
2016-11-12 01:01:20 +01:00
|
|
|
unread_count_elem.removeClass("zero_count");
|
|
|
|
unread_count_elem.show();
|
2016-10-27 01:02:41 +02:00
|
|
|
value_span.text(count);
|
|
|
|
}
|
2016-10-29 19:39:47 +02:00
|
|
|
}
|
|
|
|
|
2017-05-13 19:26:54 +02:00
|
|
|
exports.set_count = function (stream_id, topic, count) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const widget = active_widgets.get(stream_id);
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
if (widget === undefined) {
|
2019-08-29 01:41:23 +02:00
|
|
|
return false;
|
2016-10-27 01:36:20 +02:00
|
|
|
}
|
2019-03-09 16:32:41 +01:00
|
|
|
|
2019-08-29 01:41:23 +02:00
|
|
|
return widget.set_count(topic, count);
|
2016-10-27 01:36:20 +02:00
|
|
|
};
|
|
|
|
|
2017-09-25 16:51:16 +02:00
|
|
|
exports.widget = function (parent_elem, my_stream_id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const self = {};
|
2020-01-17 22:56:00 +01:00
|
|
|
const max_topics = 5;
|
2016-10-27 02:02:40 +02:00
|
|
|
|
2020-01-17 22:56:00 +01:00
|
|
|
self.get_list_info = function () {
|
2019-08-29 01:41:23 +02:00
|
|
|
let topics_selected = 0;
|
|
|
|
let more_topics_unreads = 0;
|
2017-05-13 19:26:54 +02:00
|
|
|
|
2020-01-17 22:41:13 +01:00
|
|
|
let active_topic = narrow_state.topic();
|
|
|
|
|
|
|
|
if (active_topic) {
|
|
|
|
active_topic = active_topic.toLowerCase();
|
|
|
|
}
|
|
|
|
|
2019-08-29 01:41:23 +02:00
|
|
|
const max_topics_with_unread = 8;
|
2019-11-02 00:06:25 +01:00
|
|
|
const topic_names = topic_data.get_recent_names(my_stream_id);
|
2016-10-27 02:02:40 +02:00
|
|
|
|
2020-01-17 22:56:00 +01:00
|
|
|
const items = [];
|
2016-10-29 20:19:25 +02:00
|
|
|
|
2017-07-24 15:15:28 +02:00
|
|
|
_.each(topic_names, function (topic_name, idx) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const num_unread = unread.num_unread_for_topic(my_stream_id, topic_name);
|
2020-01-17 22:41:13 +01:00
|
|
|
const is_active_topic = active_topic === topic_name.toLowerCase();
|
2020-01-17 01:23:05 +01:00
|
|
|
const is_topic_muted = muting.is_topic_muted(my_stream_id, topic_name);
|
2016-10-29 20:19:25 +02:00
|
|
|
|
2017-08-23 08:38:56 +02:00
|
|
|
if (!zoomed) {
|
2020-01-17 01:23:05 +01:00
|
|
|
function should_show_topic() {
|
|
|
|
// This function exists just for readability, to
|
|
|
|
// avoid long chained conditionals to determine
|
|
|
|
// which topics to include.
|
|
|
|
|
|
|
|
// We always show the active topic. Ideally, this
|
|
|
|
// logic would first check whether the active
|
|
|
|
// topic is in the set of those with unreads to
|
|
|
|
// avoid ending up with max_topics_with_unread + 1
|
|
|
|
// total topics if the active topic comes after
|
|
|
|
// the first several topics with unread messages.
|
|
|
|
if (is_active_topic) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We unconditionally skip showing muted topics
|
|
|
|
// when not zoomed, even if they have unread
|
|
|
|
// messages.
|
|
|
|
if (is_topic_muted) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We include the most recent max_topics topics,
|
|
|
|
// even if there are no unread messages.
|
|
|
|
if (idx < max_topics) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We include older topics with unread messages up
|
|
|
|
// until max_topics_with_unread total topics have
|
|
|
|
// been included.
|
|
|
|
if (num_unread > 0 && topics_selected < max_topics_with_unread) {
|
|
|
|
return true;
|
2019-08-29 01:41:23 +02:00
|
|
|
}
|
|
|
|
|
2020-01-17 01:23:05 +01:00
|
|
|
// Otherwise, we don't show the topic in the
|
|
|
|
// unzoomed view. We might display its unread
|
|
|
|
// count in in "more topics" if it is not muted.
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-10 20:46:01 +01:00
|
|
|
|
2020-01-17 01:23:05 +01:00
|
|
|
const show_topic = should_show_topic();
|
2016-11-10 20:46:01 +01:00
|
|
|
if (!show_topic) {
|
2020-01-17 01:23:05 +01:00
|
|
|
if (!is_topic_muted) {
|
|
|
|
// The "more topics" unread count, like
|
|
|
|
// stream-level counts, only counts messages
|
|
|
|
// on unmuted topics.
|
|
|
|
more_topics_unreads += num_unread;
|
|
|
|
}
|
2016-11-10 20:46:01 +01:00
|
|
|
return;
|
|
|
|
}
|
2020-01-17 01:23:05 +01:00
|
|
|
topics_selected += 1;
|
|
|
|
// We fall through to rendering the topic, using the
|
|
|
|
// same code we do when zoomed.
|
2016-10-29 20:19:25 +02:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const topic_info = {
|
2016-10-29 20:19:25 +02:00
|
|
|
topic_name: topic_name,
|
|
|
|
unread: num_unread,
|
|
|
|
is_zero: num_unread === 0,
|
2020-01-17 01:23:05 +01:00
|
|
|
is_muted: is_topic_muted,
|
2020-01-17 21:40:28 +01:00
|
|
|
is_active_topic: is_active_topic,
|
2018-12-14 19:02:26 +01:00
|
|
|
url: hash_util.by_stream_topic_uri(my_stream_id, topic_name),
|
2016-10-29 20:19:25 +02:00
|
|
|
};
|
2020-01-17 22:56:00 +01:00
|
|
|
|
|
|
|
items.push(topic_info);
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
items: items,
|
|
|
|
num_possible_topics: topic_names.length,
|
|
|
|
more_topics_unreads: more_topics_unreads,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
self.build_list = function () {
|
|
|
|
const list_info = self.get_list_info();
|
|
|
|
const num_possible_topics = list_info.num_possible_topics;
|
|
|
|
const more_topics_unreads = list_info.more_topics_unreads;
|
|
|
|
|
|
|
|
const ul = $('<ul class="topic-list">');
|
|
|
|
|
|
|
|
self.topic_items = new FoldDict();
|
|
|
|
|
|
|
|
// This is the main list of topics:
|
|
|
|
// topic1
|
|
|
|
// topic2
|
|
|
|
// topic3
|
|
|
|
_.each(list_info.items, (topic_info) => {
|
2019-11-02 00:06:25 +01:00
|
|
|
const li = $(render_topic_list_item(topic_info));
|
2020-01-17 22:56:00 +01:00
|
|
|
self.topic_items.set(topic_info.topic_name, li);
|
2016-10-29 20:19:25 +02:00
|
|
|
ul.append(li);
|
|
|
|
});
|
|
|
|
|
2019-03-13 13:47:57 +01:00
|
|
|
// Now, we decide whether we need to show the "more topics"
|
|
|
|
// widget. We need it if there are at least 5 topics in the
|
|
|
|
// frontend's cache, or if we (possibly) don't have all
|
|
|
|
// historical topics in the browser's cache.
|
2019-08-29 01:41:23 +02:00
|
|
|
const show_more = self.build_more_topics_section(more_topics_unreads);
|
2019-11-02 00:06:25 +01:00
|
|
|
const sub = stream_data.get_sub_by_id(my_stream_id);
|
2016-10-27 02:02:40 +02:00
|
|
|
|
2020-01-17 22:56:00 +01:00
|
|
|
if (num_possible_topics > max_topics || !stream_data.all_topics_in_cache(sub)) {
|
2019-03-13 13:47:57 +01:00
|
|
|
ul.append(show_more);
|
|
|
|
}
|
2016-10-29 20:19:25 +02:00
|
|
|
return ul;
|
2017-09-22 20:37:05 +02:00
|
|
|
};
|
2016-10-27 02:02:40 +02:00
|
|
|
|
2019-08-29 01:41:23 +02:00
|
|
|
self.build_more_topics_section = function (more_topics_unreads) {
|
|
|
|
const show_more_html = render_more_topics({
|
|
|
|
more_topics_unreads: more_topics_unreads,
|
|
|
|
});
|
2017-09-21 20:44:31 +02:00
|
|
|
return $(show_more_html);
|
|
|
|
};
|
|
|
|
|
2016-11-10 20:05:14 +01:00
|
|
|
self.get_parent = function () {
|
|
|
|
return parent_elem;
|
|
|
|
};
|
|
|
|
|
2017-05-14 18:06:57 +02:00
|
|
|
self.get_stream_id = function () {
|
|
|
|
return my_stream_id;
|
2016-11-10 20:19:22 +01:00
|
|
|
};
|
|
|
|
|
2016-10-29 20:19:25 +02:00
|
|
|
self.get_dom = function () {
|
|
|
|
return self.dom;
|
|
|
|
};
|
|
|
|
|
2016-11-10 15:35:14 +01:00
|
|
|
self.remove = function () {
|
|
|
|
self.dom.remove();
|
|
|
|
};
|
|
|
|
|
2017-09-22 03:26:10 +02:00
|
|
|
self.num_items = function () {
|
|
|
|
return self.topic_items.num_items();
|
|
|
|
};
|
|
|
|
|
2016-10-29 21:01:07 +02:00
|
|
|
self.set_count = function (topic, count) {
|
2019-08-29 01:41:23 +02:00
|
|
|
let unread_count_elem;
|
|
|
|
if (topic === null) {
|
|
|
|
// null is used for updating the "more topics" count.
|
|
|
|
if (zoomed) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const unread_count_parent = $(".show-more-topics");
|
|
|
|
if (unread_count_parent.length === 0) {
|
|
|
|
// If no show-more-topics element is present in the
|
|
|
|
// DOM, there are two possibilities. The most likely
|
|
|
|
// is that there are simply no unreads on that topic
|
|
|
|
// and there should continue to not be a "more topics"
|
|
|
|
// button; we can check this by looking at count.
|
|
|
|
if (count === 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The alternative is that there is these new messages
|
|
|
|
// create the need for a "more topics" widget with a
|
|
|
|
// nonzero unread count, and we need to create one and
|
|
|
|
// add it to the DOM.
|
|
|
|
//
|
|
|
|
// With our current implementation, this code path
|
|
|
|
// will always have its results overwritten shortly
|
|
|
|
// after, because (1) the can only happen when we just
|
|
|
|
// added unread counts, (not removing them), and (2)
|
|
|
|
// when learning about new (unread) messages,
|
|
|
|
// stream_list.update_dom_with_unread_count is always
|
|
|
|
// immediately followed by
|
|
|
|
// stream_list.update_streams_sidebar, which will
|
|
|
|
// rebuilds the topic list from scratch anyway.
|
|
|
|
//
|
|
|
|
// So this code mostly exists to document this corner
|
|
|
|
// case if in the future we adjust the model for
|
|
|
|
// managing unread counts. The code for updating this
|
|
|
|
// element would look something like the following:
|
|
|
|
//
|
|
|
|
// var show_more = self.build_more_topics_section(count);
|
|
|
|
// var topic_list_ul = exports.get_stream_li().find(".topic-list").expectOne();
|
|
|
|
// topic_list_ul.append(show_more);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
unread_count_elem = unread_count_parent.find(".topic-unread-count");
|
|
|
|
update_unread_count(unread_count_elem, count);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-10-29 21:01:07 +02:00
|
|
|
if (!self.topic_items.has(topic)) {
|
2019-08-29 01:41:23 +02:00
|
|
|
// `topic_li` may not exist if the topic is behind "more
|
|
|
|
// topics"; We need to update the "more topics" count
|
|
|
|
// instead in that case; we do this by returning true to
|
|
|
|
// notify the caller to accumulate these.
|
2020-01-15 22:36:43 +01:00
|
|
|
if (muting.is_topic_muted(my_stream_id, topic)) {
|
|
|
|
// But we don't count unreads in muted topics.
|
|
|
|
return false;
|
|
|
|
}
|
2019-08-29 01:41:23 +02:00
|
|
|
return true;
|
2016-10-29 21:01:07 +02:00
|
|
|
}
|
|
|
|
|
2019-08-29 01:41:23 +02:00
|
|
|
const topic_li = self.topic_items.get(topic);
|
|
|
|
unread_count_elem = topic_li.find('.topic-unread-count');
|
2016-11-12 01:01:20 +01:00
|
|
|
update_unread_count(unread_count_elem, count);
|
2019-08-29 01:41:23 +02:00
|
|
|
return false;
|
2016-10-29 21:01:07 +02:00
|
|
|
};
|
|
|
|
|
2017-09-22 00:12:55 +02:00
|
|
|
self.show_spinner = function () {
|
|
|
|
// The spinner will go away once we get results and redraw
|
|
|
|
// the whole list.
|
2019-11-02 00:06:25 +01:00
|
|
|
const spinner = self.dom.find('.searching-for-more-topics');
|
2017-09-22 00:12:55 +02:00
|
|
|
spinner.show();
|
|
|
|
};
|
|
|
|
|
2020-01-17 22:41:13 +01:00
|
|
|
self.build = function () {
|
2017-09-25 16:38:39 +02:00
|
|
|
self.dom = self.build_list();
|
2017-09-22 22:54:10 +02:00
|
|
|
parent_elem.append(self.dom);
|
|
|
|
};
|
2016-11-10 20:05:14 +01:00
|
|
|
|
2016-10-29 20:19:25 +02:00
|
|
|
return self;
|
2016-10-27 02:02:40 +02:00
|
|
|
};
|
|
|
|
|
2017-08-11 00:29:35 +02:00
|
|
|
exports.active_stream_id = function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_ids = active_widgets.keys();
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
if (stream_ids.length !== 1) {
|
2017-08-11 00:29:35 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-09 16:32:41 +01:00
|
|
|
return stream_ids[0];
|
2017-08-11 00:29:35 +02:00
|
|
|
};
|
|
|
|
|
2018-09-10 14:52:58 +02:00
|
|
|
exports.get_stream_li = function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const widgets = active_widgets.values();
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
if (widgets.length !== 1) {
|
2018-09-10 14:52:58 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_li = widgets[0].get_parent();
|
2018-09-10 14:52:58 +02:00
|
|
|
return stream_li;
|
|
|
|
};
|
|
|
|
|
2017-05-14 18:06:57 +02:00
|
|
|
exports.rebuild = function (stream_li, stream_id) {
|
2016-10-27 02:33:24 +02:00
|
|
|
exports.remove_expanded_topics();
|
2019-11-02 00:06:25 +01:00
|
|
|
const widget = exports.widget(stream_li, stream_id);
|
2020-01-17 22:41:13 +01:00
|
|
|
widget.build();
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
active_widgets.set(stream_id, widget);
|
2016-10-27 02:33:24 +02:00
|
|
|
};
|
|
|
|
|
2016-11-10 16:15:12 +01:00
|
|
|
// 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;
|
2016-11-10 20:46:01 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_id = exports.active_stream_id();
|
2019-03-09 16:32:41 +01:00
|
|
|
if (!stream_id) {
|
2016-11-10 20:46:01 +01:00
|
|
|
blueslip.error('Cannot find widget for topic history zooming.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const active_widget = active_widgets.get(stream_id);
|
2019-03-09 16:32:41 +01:00
|
|
|
|
2017-08-08 20:29:15 +02:00
|
|
|
function on_success() {
|
2019-03-09 16:32:41 +01:00
|
|
|
if (!active_widgets.has(stream_id)) {
|
2017-09-22 17:34:50 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const widget = active_widgets.get(stream_id);
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
exports.rebuild(widget.get_parent(), stream_id);
|
2017-08-08 20:29:15 +02:00
|
|
|
}
|
|
|
|
|
2019-03-01 01:40:05 +01:00
|
|
|
ui.get_scroll_element($('#stream-filters-container')).scrollTop(0);
|
2017-09-22 00:12:55 +02:00
|
|
|
active_widget.show_spinner();
|
2017-09-21 19:03:23 +02:00
|
|
|
topic_data.get_server_history(stream_id, on_success);
|
2016-11-10 16:15:12 +01:00
|
|
|
};
|
|
|
|
|
2018-09-10 14:52:58 +02:00
|
|
|
exports.initialize = function () {
|
2016-11-05 20:13:36 +01:00
|
|
|
$('#stream_filters').on('click', '.topic-box', function (e) {
|
2016-10-27 03:47:05 +02:00
|
|
|
if (e.metaKey || e.ctrlKey) {
|
|
|
|
return;
|
|
|
|
}
|
2019-08-29 01:41:23 +02:00
|
|
|
if ($(e.target).closest('.show-more-topics').length > 0) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-27 03:47:05 +02:00
|
|
|
|
|
|
|
// In a more componentized world, we would delegate some
|
|
|
|
// of this stuff back up to our parents.
|
|
|
|
|
2019-12-30 12:51:16 +01:00
|
|
|
const stream_row = $(e.target).parents('.narrow-filter');
|
|
|
|
const stream_id = parseInt(stream_row.attr('data-stream-id'), 10);
|
2019-11-02 00:06:25 +01:00
|
|
|
const sub = stream_data.get_sub_by_id(stream_id);
|
|
|
|
const topic = $(e.target).parents('li').attr('data-topic-name');
|
2016-10-27 03:47:05 +02:00
|
|
|
|
2018-05-06 21:43:17 +02:00
|
|
|
narrow.activate([
|
|
|
|
{operator: 'stream', operand: sub.name},
|
|
|
|
{operator: 'topic', operand: topic}],
|
2018-04-23 06:02:11 +02:00
|
|
|
{trigger: 'sidebar'});
|
2016-10-27 03:47:05 +02:00
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-10-27 01:36:20 +02:00
|
|
|
|
2019-10-25 09:45:13 +02:00
|
|
|
window.topic_list = exports;
|