zulip/static/js/unread.js

338 lines
11 KiB
JavaScript
Raw Normal View History

2016-08-17 01:19:14 +02:00
// See http://zulip.readthedocs.io/en/latest/pointer.html for notes on
// how this system is designed.
var unread = (function () {
var exports = {};
var unread_mentioned = new Dict();
var unread_topics = new Dict({fold_case: true});
var unread_privates = new Dict();
exports.suppress_unread_counts = true;
exports.messages_read_in_narrow = false;
exports.message_unread = function (message) {
if (message === undefined) {
return false;
}
return message.flags === undefined ||
message.flags.indexOf('read') === -1;
};
exports.update_unread_topics = function (msg, event) {
var canon_stream = stream_data.canonicalized_name(msg.stream);
var canon_subject = stream_data.canonicalized_name(msg.subject);
if (event.subject !== undefined &&
unread_topics.has(canon_stream) &&
unread_topics.get(canon_stream).has(canon_subject) &&
unread_topics.get(canon_stream).get(canon_subject).get(msg.id)) {
var new_canon_subject = stream_data.canonicalized_name(event.subject);
// Move the unread subject count to the new subject
unread_topics.get(canon_stream).get(canon_subject).del(msg.id);
if (unread_topics.get(canon_stream).get(canon_subject).num_items() === 0) {
unread_topics.get(canon_stream).del(canon_subject);
}
unread_topics.get(canon_stream).setdefault(new_canon_subject, new Dict());
unread_topics.get(canon_stream).get(new_canon_subject).set(msg.id, true);
}
};
exports.process_loaded_messages = function (messages) {
_.each(messages, function (message) {
var unread = exports.message_unread(message);
if (!unread) {
return;
}
if (message.type === 'private') {
unread_privates.setdefault(message.reply_to, new Dict());
unread_privates.get(message.reply_to).set(message.id, true);
}
if (message.type === 'stream') {
var canon_stream = stream_data.canonicalized_name(message.stream);
var canon_subject = stream_data.canonicalized_name(message.subject);
unread_topics.setdefault(canon_stream, new Dict());
unread_topics.get(canon_stream).setdefault(canon_subject, new Dict());
unread_topics.get(canon_stream).get(canon_subject).set(message.id, true);
}
if (message.mentioned) {
unread_mentioned.set(message.id, true);
}
});
};
exports.process_read_message = function (message) {
if (message.type === 'private') {
var dict = unread_privates.get(message.reply_to);
if (dict) {
dict.del(message.id);
}
}
if (message.type === 'stream') {
var canon_stream = stream_data.canonicalized_name(message.stream);
var canon_subject = stream_data.canonicalized_name(message.subject);
var stream_dict = unread_topics.get(canon_stream);
if (stream_dict) {
var subject_dict = stream_dict.get(canon_subject);
if (subject_dict) {
subject_dict.del(message.id);
}
}
}
unread_mentioned.del(message.id);
};
exports.declare_bankruptcy = function () {
unread_privates = new Dict();
unread_topics = new Dict({fold_case: true});
};
Clean up code for unread counts and notifications. The core simplification here is that zephyr.js no longer has: * the global home_unread_messages * the function unread_in_current_view() [which used the global] The logic that used to be in zephyr is now in its proper home of unread.js, which has these changes: * the structure returned from unread.get_counts() includes a new member called unread_in_current_view * there's a helper function unread.num_unread_current_messages() Deprecating zephyr.unread_in_current_view() affected two callers: * notifications.update_title_count() * notifications_bar.update() The above functions used to call back to zephyr to get counts, but there was no nice way to enforce that they were getting counts at the right time in the code flow, because they depended on functions like process_visible_unread_messages() to orchestrate updating internal unread counts before pushing out counts to the DOM. Now both of those function take a parameter with the unread count, and we then had to change all of their callers appropriately. This went hand in hand with another goal, which is that we want all the unread-counts logic to funnel though basically one place, which is zephyr.update_unread_counts(). So now that function always calls notifications_bar.update() [NEW] as well as calling into the modules unread.js, stream_list.js, and notifications.js [OLD]. Adding the call to notifications_bar.update() in update_unread_counts() made it so that some other places in the code no longer needed to call notifications_bar.update(), so you'll see some lines of code removed. There are also cases where notifications.update_title_count() was called redundantly, since the callers were already reaching update_unread_counts() via other calls. Finally, in ui.resizehandler, you'll see a simple case where the call to notifications_bar.update() is preceded by an explicit call to unread.get_counts(). (imported from commit ce84b9c8076c1f9bb20a61209913f0cb0dae098c)
2013-06-05 21:04:06 +02:00
exports.num_unread_current_messages = function () {
var num_unread = 0;
_.each(current_msg_list.all_messages(), function (msg) {
Clean up code for unread counts and notifications. The core simplification here is that zephyr.js no longer has: * the global home_unread_messages * the function unread_in_current_view() [which used the global] The logic that used to be in zephyr is now in its proper home of unread.js, which has these changes: * the structure returned from unread.get_counts() includes a new member called unread_in_current_view * there's a helper function unread.num_unread_current_messages() Deprecating zephyr.unread_in_current_view() affected two callers: * notifications.update_title_count() * notifications_bar.update() The above functions used to call back to zephyr to get counts, but there was no nice way to enforce that they were getting counts at the right time in the code flow, because they depended on functions like process_visible_unread_messages() to orchestrate updating internal unread counts before pushing out counts to the DOM. Now both of those function take a parameter with the unread count, and we then had to change all of their callers appropriately. This went hand in hand with another goal, which is that we want all the unread-counts logic to funnel though basically one place, which is zephyr.update_unread_counts(). So now that function always calls notifications_bar.update() [NEW] as well as calling into the modules unread.js, stream_list.js, and notifications.js [OLD]. Adding the call to notifications_bar.update() in update_unread_counts() made it so that some other places in the code no longer needed to call notifications_bar.update(), so you'll see some lines of code removed. There are also cases where notifications.update_title_count() was called redundantly, since the callers were already reaching update_unread_counts() via other calls. Finally, in ui.resizehandler, you'll see a simple case where the call to notifications_bar.update() is preceded by an explicit call to unread.get_counts(). (imported from commit ce84b9c8076c1f9bb20a61209913f0cb0dae098c)
2013-06-05 21:04:06 +02:00
if ((msg.id > current_msg_list.selected_id()) && exports.message_unread(msg)) {
num_unread += 1;
}
});
return num_unread;
};
exports.get_counts = function () {
var res = {};
// Return a data structure with various counts. This function should be
// pretty cheap, even if you don't care about all the counts, and you
// should strive to keep it free of side effects on globals or DOM.
res.private_message_count = 0;
res.home_unread_messages = 0;
res.mentioned_message_count = unread_mentioned.num_items();
res.stream_count = new Dict(); // hash by stream -> count
res.subject_count = new Dict(); // hash of hashes (stream, then subject -> count)
res.pm_count = new Dict(); // Hash by email -> count
unread_topics.each(function (_, stream) {
if (! stream_data.is_subscribed(stream)) {
return true;
}
if (unread_topics.has(stream)) {
res.subject_count.set(stream, new Dict());
var stream_count = 0;
unread_topics.get(stream).each(function (msgs, subject) {
var subject_count = msgs.num_items();
res.subject_count.get(stream).set(subject, subject_count);
if (!muting.is_topic_muted(stream, subject)) {
stream_count += subject_count;
}
});
res.stream_count.set(stream, stream_count);
if (stream_data.in_home_view(stream)) {
res.home_unread_messages += stream_count;
}
}
});
var pm_count = 0;
unread_privates.each(function (obj, index) {
var count = obj.num_items();
res.pm_count.set(index, count);
pm_count += count;
});
res.private_message_count = pm_count;
res.home_unread_messages += pm_count;
Clean up code for unread counts and notifications. The core simplification here is that zephyr.js no longer has: * the global home_unread_messages * the function unread_in_current_view() [which used the global] The logic that used to be in zephyr is now in its proper home of unread.js, which has these changes: * the structure returned from unread.get_counts() includes a new member called unread_in_current_view * there's a helper function unread.num_unread_current_messages() Deprecating zephyr.unread_in_current_view() affected two callers: * notifications.update_title_count() * notifications_bar.update() The above functions used to call back to zephyr to get counts, but there was no nice way to enforce that they were getting counts at the right time in the code flow, because they depended on functions like process_visible_unread_messages() to orchestrate updating internal unread counts before pushing out counts to the DOM. Now both of those function take a parameter with the unread count, and we then had to change all of their callers appropriately. This went hand in hand with another goal, which is that we want all the unread-counts logic to funnel though basically one place, which is zephyr.update_unread_counts(). So now that function always calls notifications_bar.update() [NEW] as well as calling into the modules unread.js, stream_list.js, and notifications.js [OLD]. Adding the call to notifications_bar.update() in update_unread_counts() made it so that some other places in the code no longer needed to call notifications_bar.update(), so you'll see some lines of code removed. There are also cases where notifications.update_title_count() was called redundantly, since the callers were already reaching update_unread_counts() via other calls. Finally, in ui.resizehandler, you'll see a simple case where the call to notifications_bar.update() is preceded by an explicit call to unread.get_counts(). (imported from commit ce84b9c8076c1f9bb20a61209913f0cb0dae098c)
2013-06-05 21:04:06 +02:00
if (narrow.active()) {
res.unread_in_current_view = exports.num_unread_current_messages();
} else {
Clean up code for unread counts and notifications. The core simplification here is that zephyr.js no longer has: * the global home_unread_messages * the function unread_in_current_view() [which used the global] The logic that used to be in zephyr is now in its proper home of unread.js, which has these changes: * the structure returned from unread.get_counts() includes a new member called unread_in_current_view * there's a helper function unread.num_unread_current_messages() Deprecating zephyr.unread_in_current_view() affected two callers: * notifications.update_title_count() * notifications_bar.update() The above functions used to call back to zephyr to get counts, but there was no nice way to enforce that they were getting counts at the right time in the code flow, because they depended on functions like process_visible_unread_messages() to orchestrate updating internal unread counts before pushing out counts to the DOM. Now both of those function take a parameter with the unread count, and we then had to change all of their callers appropriately. This went hand in hand with another goal, which is that we want all the unread-counts logic to funnel though basically one place, which is zephyr.update_unread_counts(). So now that function always calls notifications_bar.update() [NEW] as well as calling into the modules unread.js, stream_list.js, and notifications.js [OLD]. Adding the call to notifications_bar.update() in update_unread_counts() made it so that some other places in the code no longer needed to call notifications_bar.update(), so you'll see some lines of code removed. There are also cases where notifications.update_title_count() was called redundantly, since the callers were already reaching update_unread_counts() via other calls. Finally, in ui.resizehandler, you'll see a simple case where the call to notifications_bar.update() is preceded by an explicit call to unread.get_counts(). (imported from commit ce84b9c8076c1f9bb20a61209913f0cb0dae098c)
2013-06-05 21:04:06 +02:00
res.unread_in_current_view = res.home_unread_messages;
}
return res;
};
exports.num_unread_for_subject = function (stream, subject) {
var num_unread = 0;
if (unread_topics.has(stream) &&
unread_topics.get(stream).has(subject)) {
num_unread = unread_topics.get(stream).get(subject).num_items();
}
return num_unread;
};
exports.num_unread_for_person = function (email) {
if (!unread_privates.has(email)) {
return 0;
}
return unread_privates.get(email).num_items();
};
exports.update_unread_counts = function () {
if (exports.suppress_unread_counts) {
return;
}
// Pure computation:
var res = unread.get_counts();
// Side effects from here down:
// This updates some DOM elements directly, so try to
// avoid excessive calls to this.
stream_list.update_dom_with_unread_counts(res);
notifications.update_title_count(res.home_unread_messages);
notifications.update_pm_count(res.private_message_count);
};
exports.enable = function enable() {
exports.suppress_unread_counts = false;
exports.update_unread_counts();
};
exports.mark_all_as_read = function mark_all_as_read(cont) {
_.each(message_list.all.all_messages(), function (msg) {
msg.flags = msg.flags || [];
msg.flags.push('read');
});
unread.declare_bankruptcy();
exports.update_unread_counts();
channel.post({
url: '/json/messages/flags',
idempotent: true,
data: {messages: JSON.stringify([]),
all: true,
op: 'add',
flag: 'read'},
success: cont});
};
// Takes a list of messages and marks them as read
exports.mark_messages_as_read = function mark_messages_as_read (messages, options) {
options = options || {};
var processed = false;
_.each(messages, function (message) {
if (!unread.message_unread(message)) {
// Don't do anything if the message is already read.
return;
}
if (current_msg_list === message_list.narrowed) {
unread.messages_read_in_narrow = true;
}
if (options.from !== "server") {
message_flags.send_read(message);
}
message.flags = message.flags || [];
message.flags.push('read');
message.unread = false;
unread.process_read_message(message, options);
home_msg_list.show_message_as_read(message, options);
2016-04-21 22:49:23 +02:00
message_list.all.show_message_as_read(message, options);
if (message_list.narrowed) {
message_list.narrowed.show_message_as_read(message, options);
}
notifications.close_notification(message);
processed = true;
});
if (processed) {
exports.update_unread_counts();
}
};
exports.mark_message_as_read = function mark_message_as_read(message, options) {
exports.mark_messages_as_read([message], options);
};
// If we ever materially change the algorithm for this function, we
// may need to update notifications.received_messages as well.
exports.process_visible = function process_visible(update_cursor) {
if (! notifications.window_has_focus()) {
return;
}
if (feature_flags.mark_read_at_bottom) {
if (viewport.bottom_message_visible()) {
exports.mark_current_list_as_read();
}
} else {
exports.mark_messages_as_read(viewport.visible_messages(true));
}
};
exports.mark_current_list_as_read = function mark_current_list_as_read(options) {
exports.mark_messages_as_read(current_msg_list.all_messages(), options);
};
exports.mark_stream_as_read = function mark_stream_as_read(stream, cont) {
channel.post({
url: '/json/messages/flags',
idempotent: true,
data: {messages: JSON.stringify([]),
all: false,
op: 'add',
flag: 'read',
stream_name: stream
},
success: cont});
};
exports.mark_topic_as_read = function mark_topic_as_read(stream, topic, cont) {
channel.post({
url: '/json/messages/flags',
idempotent: true,
data: {messages: JSON.stringify([]),
all: false,
op: 'add',
flag: 'read',
topic_name: topic,
stream_name: stream
},
success: cont});
};
function consider_bankruptcy() {
// Until we've handled possibly declaring bankruptcy, don't show
// unread counts since they only consider messages that are loaded
// client side and may be different from the numbers reported by
// the server.
if (!page_params.furthest_read_time) {
// We've never read a message.
unread.enable();
return;
}
var now = new XDate(true).getTime() / 1000;
if ((page_params.unread_count > 500) &&
(now - page_params.furthest_read_time > 60 * 60 * 24 * 2)) { // 2 days.
var unread_info = templates.render('bankruptcy_modal',
{"unread_count": page_params.unread_count});
$('#bankruptcy-unread-count').html(unread_info);
$('#bankruptcy').modal('show');
} else {
unread.enable();
}
}
exports.initialize = function initialize() {
consider_bankruptcy();
};
return exports;
}());
if (typeof module !== 'undefined') {
module.exports = unread;
}