mirror of
https://github.com/zulip/zulip.git
synced 2025-11-10 17:07:07 +00:00
I moved code into MessageList to further encapsulate details of filtering. The MessageList instances should be their own gatekeepers for what messages they care about. (imported from commit ee6cd7f6eabf97962d724a05d7d0b0a3e6ab19e5)
416 lines
13 KiB
JavaScript
416 lines
13 KiB
JavaScript
/*jslint nomen: true */
|
|
function MessageList(table_name, filter, opts) {
|
|
_.extend(this, {
|
|
collapse_messages: true,
|
|
summarize_read: false
|
|
}, opts);
|
|
this.view = new MessageListView(this, table_name, this.collapse_messages);
|
|
|
|
this._items = [];
|
|
this._hash = {};
|
|
this.table_name = table_name;
|
|
this.filter = filter;
|
|
this._selected_id = -1;
|
|
|
|
if (this.filter === undefined) {
|
|
this.filter = new Filter();
|
|
}
|
|
this.narrowed = false;
|
|
if (this.table_name === "zfilt") {
|
|
this.narrowed = true;
|
|
}
|
|
|
|
this.num_appends = 0;
|
|
this.min_id_exempted_from_summaries = -1;
|
|
return this;
|
|
}
|
|
|
|
(function () {
|
|
|
|
MessageList.prototype = {
|
|
add_messages: function MessageList_add_messages(messages, messages_are_new) {
|
|
var self = this;
|
|
var predicate = self.filter.predicate();
|
|
var top_messages = [];
|
|
var bottom_messages = [];
|
|
var interior_messages = [];
|
|
|
|
// If we're initially populating the list, save the messages in
|
|
// bottom_messages regardless
|
|
if (self.selected_id() === -1 && self.empty()) {
|
|
bottom_messages = _.filter(messages, predicate);
|
|
} else {
|
|
_.each(messages, function (msg) {
|
|
// Filter out duplicates that are already in self, and all messages
|
|
// that fail our filter predicate
|
|
if (! (self.get(msg.id) === undefined && predicate(msg))) {
|
|
return;
|
|
}
|
|
|
|
// Put messages in correct order on either side of the message list
|
|
if (self.empty() || msg.id > self.last().id) {
|
|
bottom_messages.push(msg);
|
|
} else if (msg.id < self.first().id) {
|
|
top_messages.push(msg);
|
|
} else {
|
|
interior_messages.push(msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (interior_messages.length > 0) {
|
|
self.add_and_rerender(top_messages.concat(interior_messages).concat(bottom_messages));
|
|
return true;
|
|
}
|
|
if (top_messages.length > 0) {
|
|
self.prepend(top_messages);
|
|
}
|
|
if (bottom_messages.length > 0) {
|
|
self.append(bottom_messages, messages_are_new);
|
|
}
|
|
|
|
if ((self === narrowed_msg_list) && !self.empty() &&
|
|
(self.selected_id() === -1)) {
|
|
// If adding some new messages to the message tables caused
|
|
// our current narrow to no longer be empty, hide the empty
|
|
// feed placeholder text.
|
|
narrow.hide_empty_narrow_message();
|
|
// And also select the newly arrived message.
|
|
self.select_id(self.selected_id(), {then_scroll: true, use_closest: true});
|
|
}
|
|
},
|
|
|
|
get: function MessageList_get(id) {
|
|
id = parseInt(id, 10);
|
|
if (isNaN(id)) {
|
|
return undefined;
|
|
}
|
|
return this._hash[id];
|
|
},
|
|
|
|
get_messages: function MessageList_get_mesages() {
|
|
return this._items;
|
|
},
|
|
|
|
num_items: function MessageList_num_items() {
|
|
return this._items.length;
|
|
},
|
|
|
|
empty: function MessageList_empty() {
|
|
return this._items.length === 0;
|
|
},
|
|
|
|
first: function MessageList_first() {
|
|
return this._items[0];
|
|
},
|
|
|
|
last: function MessageList_last() {
|
|
return this._items[this._items.length - 1];
|
|
},
|
|
|
|
nth_most_recent_id: function MessageList_nth_most_recent_id(n) {
|
|
var i = this._items.length - n;
|
|
if (i < 0) {
|
|
return -1;
|
|
} else {
|
|
return this._items[i].id;
|
|
}
|
|
},
|
|
|
|
clear: function MessageList_clear(opts) {
|
|
opts = _.extend({clear_selected_id: true}, opts);
|
|
|
|
this._items = [];
|
|
this._hash = {};
|
|
this.view.clear_rendering_state(true);
|
|
|
|
if (opts.clear_selected_id) {
|
|
this._selected_id = -1;
|
|
}
|
|
},
|
|
|
|
selected_id: function MessageList_selected_id() {
|
|
return this._selected_id;
|
|
},
|
|
|
|
select_id: function MessageList_select_id(id, opts) {
|
|
opts = _.extend({
|
|
then_scroll: false,
|
|
use_closest: false,
|
|
mark_read: true
|
|
}, opts, {
|
|
id: id,
|
|
msg_list: this,
|
|
previously_selected: this._selected_id
|
|
});
|
|
|
|
id = parseInt(id, 10);
|
|
if (isNaN(id)) {
|
|
blueslip.fatal("Bad message id");
|
|
}
|
|
|
|
if (this.get(id) === undefined) {
|
|
if (!opts.use_closest) {
|
|
blueslip.error("Selected message id not in MessageList",
|
|
{table_name: this.table_name, id: id});
|
|
}
|
|
id = this.closest_id(id);
|
|
opts.id = id;
|
|
}
|
|
|
|
this._selected_id = id;
|
|
if (!opts.from_rendering) {
|
|
this.view.maybe_rerender();
|
|
}
|
|
|
|
$(document).trigger($.Event('message_selected.zulip', opts));
|
|
},
|
|
|
|
reselect_selected_id: function MessageList_select_closest_id() {
|
|
this.select_id(this._selected_id, {from_rendering: true});
|
|
},
|
|
|
|
selected_message: function MessageList_selected_message() {
|
|
return this.get(this._selected_id);
|
|
},
|
|
|
|
selected_row: function MessageList_selected_row() {
|
|
return this.get_row(this._selected_id);
|
|
},
|
|
|
|
on_expandable_row: function MessageList_on_expandable_row() {
|
|
return this.view.is_expandable_row(this.selected_row());
|
|
},
|
|
|
|
closest_id: function MessageList_closest_id(id) {
|
|
var items = this._items;
|
|
|
|
if (items.length === 0) {
|
|
return -1;
|
|
}
|
|
|
|
var closest = util.lower_bound(items, id,
|
|
function (a, b) {
|
|
return a.id < b;
|
|
});
|
|
|
|
if (closest === items.length
|
|
|| (closest !== 0
|
|
&& (id - items[closest - 1].id <
|
|
items[closest].id - id)))
|
|
{
|
|
closest = closest - 1;
|
|
}
|
|
return items[closest].id;
|
|
},
|
|
|
|
advance_past_messages: function MessageList_advance_past_messages(msg_ids) {
|
|
// Start with the current pointer, but then keep advancing the
|
|
// pointer while the next message's id is in msg_ids. See trac #1555
|
|
// for more context, but basically we are skipping over contiguous
|
|
// messages that we have recently visited.
|
|
var next_msg_id = 0;
|
|
|
|
var id_set = {};
|
|
|
|
_.each(msg_ids, function (msg_id) {
|
|
id_set[msg_id] = true;
|
|
});
|
|
|
|
var idx = this.selected_idx() + 1;
|
|
while (idx < this._items.length) {
|
|
var msg_id = this._items[idx].id;
|
|
if (!id_set[msg_id]) {
|
|
break;
|
|
}
|
|
next_msg_id = msg_id;
|
|
++idx;
|
|
}
|
|
|
|
if (next_msg_id > 0) {
|
|
this._selected_id = next_msg_id;
|
|
}
|
|
},
|
|
|
|
_add_to_hash: function MessageList__add_to_hash(messages) {
|
|
var self = this;
|
|
messages.forEach(function (elem) {
|
|
var id = parseInt(elem.id, 10);
|
|
if (isNaN(id)) {
|
|
blueslip.fatal("Bad message id");
|
|
}
|
|
if (self._hash[id] !== undefined) {
|
|
blueslip.error("Duplicate message added to MessageList");
|
|
return;
|
|
}
|
|
self._hash[id] = elem;
|
|
});
|
|
},
|
|
|
|
is_summarized_message: function (message) {
|
|
if (!feature_flags.summarize_read_while_narrowed ||
|
|
message === undefined || message.flags === undefined) {
|
|
return false;
|
|
}
|
|
if (message.id >= this.min_id_exempted_from_summaries) {
|
|
return false;
|
|
}
|
|
if (this.summarize_read === 'home') {
|
|
return message.flags.indexOf('summarize_in_home') !== -1;
|
|
} else if (this.summarize_read === 'stream' ) {
|
|
return message.flags.indexOf('summarize_in_stream') !== -1;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
selected_idx: function MessageList_selected_idx() {
|
|
return util.lower_bound(this._items, this._selected_id,
|
|
function (a, b) { return a.id < b; });
|
|
},
|
|
|
|
// Maintains a trailing bookend element explaining any changes in
|
|
// your subscribed/unsubscribed status at the bottom of the
|
|
// message list.
|
|
update_trailing_bookend: function MessageList_update_trailing_bookend() {
|
|
this.view.clear_trailing_bookend();
|
|
if (!this.narrowed) {
|
|
return;
|
|
}
|
|
var stream = narrow.stream();
|
|
if (stream === undefined) {
|
|
return;
|
|
}
|
|
var trailing_bookend_content, subscribed = stream_data.is_subscribed(stream);
|
|
if (subscribed) {
|
|
if (this.last_message_historical) {
|
|
trailing_bookend_content = "--- Subscribed to stream " + stream + " ---";
|
|
}
|
|
} else {
|
|
if (!this.last_message_historical) {
|
|
trailing_bookend_content = "--- Unsubscribed from stream " + stream + " ---";
|
|
} else {
|
|
trailing_bookend_content = "--- Not subscribed to stream " + stream + " ---";
|
|
}
|
|
}
|
|
if (trailing_bookend_content !== undefined) {
|
|
this.view.render_trailing_bookend(trailing_bookend_content);
|
|
}
|
|
},
|
|
|
|
start_summary_exemption: function MessageList_start_summary_exemption() {
|
|
var num_exempt = 8;
|
|
this.min_id_exempted_from_summaries = this.nth_most_recent_id(num_exempt);
|
|
},
|
|
|
|
append: function MessageList_append(messages, messages_are_new) {
|
|
this._items = this._items.concat(messages);
|
|
|
|
if (this.num_appends === 0) {
|
|
// We can't figure out which messages need to be exempt from
|
|
// summarization until we get the first batch of messages.
|
|
this.start_summary_exemption();
|
|
}
|
|
this.num_appends += 1;
|
|
|
|
this._add_to_hash(messages);
|
|
|
|
this.view.append(messages, messages_are_new);
|
|
},
|
|
|
|
prepend: function MessageList_prepend(messages) {
|
|
this._items = messages.concat(this._items);
|
|
this._add_to_hash(messages);
|
|
this.view.prepend(messages);
|
|
},
|
|
|
|
add_and_rerender: function MessageList_add_and_rerender(messages) {
|
|
// To add messages that might be in the interior of our
|
|
// existing messages list, we just add the new messages and
|
|
// then rerender the whole thing.
|
|
this._items = messages.concat(this._items);
|
|
this._items.sort(function (a, b) {return a.id - b.id;});
|
|
this._add_to_hash(messages);
|
|
|
|
this.view.rerender_the_whole_thing();
|
|
},
|
|
|
|
show_edit_message: function MessageList_show_edit_message(row, edit_obj) {
|
|
row.find(".message_edit_form").empty().append(edit_obj.form);
|
|
row.find(".message_content").hide();
|
|
row.find(".message_edit").show();
|
|
row.find(".message_edit_content").autosize();
|
|
},
|
|
|
|
hide_edit_message: function MessageList_hide_edit_message(row) {
|
|
row.find(".message_content").show();
|
|
row.find(".message_edit").hide();
|
|
},
|
|
|
|
show_edit_topic: function MessageList_show_edit_topic(recipient_row, form) {
|
|
recipient_row.find(".topic_edit_form").empty().append(form);
|
|
recipient_row.find(".stream_topic").hide();
|
|
recipient_row.find(".topic_edit").show();
|
|
},
|
|
|
|
hide_edit_topic: function MessageList_hide_edit_topic(recipient_row) {
|
|
recipient_row.find(".stream_topic").show();
|
|
recipient_row.find(".topic_edit").hide();
|
|
},
|
|
|
|
show_message_as_read: function (message, options) {
|
|
var row = this.get_row(message.id);
|
|
if (options.from === 'pointer' && feature_flags.mark_read_at_bottom) {
|
|
row.find('.unread_marker').addClass('fast_fade');
|
|
} else {
|
|
row.find('.unread_marker').addClass('slow_fade');
|
|
}
|
|
row.removeClass('unread');
|
|
},
|
|
|
|
rerender: function MessageList_rerender() {
|
|
// We need to clear the rendering state, rather than just
|
|
// doing clear_table, since we want to potentially recollapse
|
|
// things.
|
|
this.view.clear_rendering_state(false);
|
|
this.view.update_render_window(this.selected_idx(), false);
|
|
this.view.rerender_preserving_scrolltop();
|
|
if (this._selected_id !== -1) {
|
|
this.select_id(this._selected_id);
|
|
}
|
|
},
|
|
|
|
all: function MessageList_all() {
|
|
return this._items;
|
|
},
|
|
|
|
get_row: function (id) {
|
|
return this.view.get_row(id);
|
|
},
|
|
|
|
change_display_recipient: function MessageList_change_display_recipient(old_recipient,
|
|
new_recipient) {
|
|
// This method only works for streams.
|
|
_.each(this._items, function (item) {
|
|
if (item.display_recipient === old_recipient) {
|
|
item.display_recipient = new_recipient;
|
|
item.stream = new_recipient;
|
|
}
|
|
});
|
|
this.view.rerender_the_whole_thing();
|
|
}
|
|
};
|
|
|
|
// We stop autoscrolling when the user is clearly in the middle of
|
|
// doing something. Be careful, though, if you try to capture
|
|
// mousemove, then you will have to contend with the autoscroll
|
|
// itself generating mousemove events.
|
|
$(document).on('message_selected.zulip hashchange.zulip mousewheel', function (event) {
|
|
viewport.stop_auto_scrolling();
|
|
});
|
|
}());
|
|
/*jslint nomen: false */
|
|
if (typeof module !== 'undefined') {
|
|
module.exports = MessageList;
|
|
}
|