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.
|
|
|
|
|
2013-05-17 21:32:26 +02:00
|
|
|
var unread = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
2013-08-23 03:14:51 +02:00
|
|
|
var unread_mentioned = new Dict();
|
2016-11-16 00:09:09 +01:00
|
|
|
var unread_privates = new Dict(); // indexed by user_ids_string like 5,7,9
|
2014-01-31 18:06:38 +01:00
|
|
|
exports.suppress_unread_counts = true;
|
2016-04-03 16:45:07 +02:00
|
|
|
exports.messages_read_in_narrow = false;
|
2013-05-17 21:32:26 +02:00
|
|
|
|
2016-11-30 03:20:29 +01:00
|
|
|
exports.unread_topic_counter = (function () {
|
|
|
|
var self = {};
|
|
|
|
|
|
|
|
function str_dict() {
|
|
|
|
// Use this when keys are streams and topics.
|
|
|
|
return new Dict({fold_case: true});
|
|
|
|
}
|
|
|
|
|
|
|
|
function num_dict() {
|
|
|
|
// Use this for message ids.
|
|
|
|
return new Dict();
|
|
|
|
}
|
|
|
|
|
|
|
|
var unread_topics = str_dict(); // dict of stream -> topic -> msg id
|
|
|
|
|
|
|
|
self.clear = function () {
|
|
|
|
unread_topics = str_dict();
|
|
|
|
};
|
|
|
|
|
|
|
|
self.update = function (stream, subject, new_subject, msg_id) {
|
|
|
|
if (unread_topics.has(stream) &&
|
|
|
|
unread_topics.get(stream).has(subject) &&
|
|
|
|
unread_topics.get(stream).get(subject).get(msg_id)) {
|
|
|
|
// Move the unread subject count to the new subject
|
|
|
|
unread_topics.get(stream).get(subject).del(msg_id);
|
|
|
|
if (unread_topics.get(stream).get(subject).num_items() === 0) {
|
|
|
|
unread_topics.get(stream).del(subject);
|
|
|
|
}
|
|
|
|
unread_topics.get(stream).setdefault(new_subject, num_dict());
|
|
|
|
unread_topics.get(stream).get(new_subject).set(msg_id, true);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.add = function (stream, subject, msg_id) {
|
|
|
|
unread_topics.setdefault(stream, str_dict());
|
|
|
|
unread_topics.get(stream).setdefault(subject, num_dict());
|
|
|
|
unread_topics.get(stream).get(subject).set(msg_id, true);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
self.del = function (stream, subject, msg_id) {
|
|
|
|
var stream_dict = unread_topics.get(stream);
|
|
|
|
if (stream_dict) {
|
|
|
|
var subject_dict = stream_dict.get(subject);
|
|
|
|
if (subject_dict) {
|
|
|
|
subject_dict.del(msg_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-12-15 01:53:33 +01:00
|
|
|
self.get_counts = function () {
|
|
|
|
var res = {};
|
|
|
|
res.stream_unread_messages = 0;
|
2016-11-30 03:20:29 +01:00
|
|
|
res.stream_count = str_dict(); // hash by stream -> count
|
|
|
|
res.subject_count = str_dict(); // hash of hashes (stream, then subject -> count)
|
|
|
|
unread_topics.each(function (_, stream) {
|
|
|
|
|
2016-12-15 01:59:08 +01:00
|
|
|
// We track unread counts for streams that may be currently
|
|
|
|
// unsubscribed. Since users may re-subscribe, we don't
|
|
|
|
// completely throw away the data. But we do ignore it here,
|
|
|
|
// so that callers have a view of the **current** world.
|
2016-11-30 03:20:29 +01:00
|
|
|
if (! stream_data.is_subscribed(stream)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unread_topics.has(stream)) {
|
|
|
|
res.subject_count.set(stream, str_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);
|
2017-05-13 20:54:53 +02:00
|
|
|
|
|
|
|
var stream_id = stream_data.get_stream_id(stream);
|
|
|
|
if (stream_data.in_home_view(stream_id)) {
|
2016-12-15 01:53:33 +01:00
|
|
|
res.stream_unread_messages += stream_count;
|
2016-11-30 03:20:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
2016-12-15 01:53:33 +01:00
|
|
|
|
|
|
|
return res;
|
2016-11-30 03:20:29 +01:00
|
|
|
};
|
|
|
|
|
2017-01-15 16:44:33 +01:00
|
|
|
self.get_stream_count = function (stream) {
|
|
|
|
var stream_count = 0;
|
|
|
|
|
|
|
|
if (!unread_topics.has(stream)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
unread_topics.get(stream).each(function (msgs, subject) {
|
|
|
|
if (!muting.is_topic_muted(stream, subject)) {
|
|
|
|
stream_count += msgs.num_items();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return stream_count;
|
|
|
|
};
|
|
|
|
|
2016-11-30 03:20:29 +01:00
|
|
|
self.get = 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;
|
|
|
|
};
|
|
|
|
|
2017-04-21 18:00:19 +02:00
|
|
|
self.topic_has_any_unread = function (stream, topic) {
|
|
|
|
var stream_dct = unread_topics.get(stream);
|
|
|
|
|
|
|
|
if (!stream_dct) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
var topic_dct = stream_dct.get(topic);
|
|
|
|
if (!topic_dct) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !topic_dct.is_empty();
|
|
|
|
};
|
|
|
|
|
2016-11-30 03:20:29 +01:00
|
|
|
return self;
|
|
|
|
}());
|
|
|
|
|
2013-05-17 21:32:26 +02:00
|
|
|
exports.message_unread = function (message) {
|
|
|
|
if (message === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return message.flags === undefined ||
|
|
|
|
message.flags.indexOf('read') === -1;
|
|
|
|
};
|
|
|
|
|
2016-08-27 03:29:32 +02:00
|
|
|
exports.update_unread_topics = function (msg, event) {
|
2016-11-30 03:20:29 +01:00
|
|
|
if (event.subject !== undefined) {
|
|
|
|
exports.unread_topic_counter.update(
|
|
|
|
msg.stream,
|
|
|
|
msg.subject,
|
|
|
|
event.subject,
|
|
|
|
msg.id
|
|
|
|
);
|
2013-05-17 21:32:26 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.process_loaded_messages = function (messages) {
|
2013-07-27 00:41:19 +02:00
|
|
|
_.each(messages, function (message) {
|
2013-05-17 21:32:26 +02:00
|
|
|
var unread = exports.message_unread(message);
|
|
|
|
if (!unread) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-09-27 23:20:52 +02:00
|
|
|
if (message.type === 'private') {
|
2017-02-14 01:15:26 +01:00
|
|
|
var user_ids_string = people.pm_reply_user_string(message);
|
2016-11-16 00:09:09 +01:00
|
|
|
if (user_ids_string) {
|
|
|
|
unread_privates.setdefault(user_ids_string, new Dict());
|
|
|
|
unread_privates.get(user_ids_string).set(message.id, true);
|
|
|
|
}
|
2013-09-27 23:20:52 +02:00
|
|
|
}
|
2013-05-17 21:32:26 +02:00
|
|
|
|
|
|
|
if (message.type === 'stream') {
|
2016-11-30 03:20:29 +01:00
|
|
|
exports.unread_topic_counter.add(
|
|
|
|
message.stream,
|
|
|
|
message.subject,
|
|
|
|
message.id
|
|
|
|
);
|
2013-05-17 21:32:26 +02:00
|
|
|
}
|
2013-05-30 22:15:59 +02:00
|
|
|
|
|
|
|
if (message.mentioned) {
|
2013-08-23 03:14:51 +02:00
|
|
|
unread_mentioned.set(message.id, true);
|
2013-05-30 22:15:59 +02:00
|
|
|
}
|
2013-05-17 21:32:26 +02:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.process_read_message = function (message) {
|
2013-09-27 23:20:52 +02:00
|
|
|
|
|
|
|
if (message.type === 'private') {
|
2017-02-14 01:15:26 +01:00
|
|
|
var user_ids_string = people.pm_reply_user_string(message);
|
2016-11-16 00:09:09 +01:00
|
|
|
if (user_ids_string) {
|
|
|
|
var dict = unread_privates.get(user_ids_string);
|
|
|
|
if (dict) {
|
|
|
|
dict.del(message.id);
|
|
|
|
}
|
2013-09-27 23:39:12 +02:00
|
|
|
}
|
2013-09-27 23:20:52 +02:00
|
|
|
}
|
|
|
|
|
2013-05-17 21:32:26 +02:00
|
|
|
if (message.type === 'stream') {
|
2016-11-30 03:20:29 +01:00
|
|
|
exports.unread_topic_counter.del(
|
|
|
|
message.stream,
|
|
|
|
message.subject,
|
|
|
|
message.id
|
|
|
|
);
|
2013-05-17 21:32:26 +02:00
|
|
|
}
|
2013-08-23 03:14:51 +02:00
|
|
|
unread_mentioned.del(message.id);
|
2013-05-17 21:32:26 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
exports.declare_bankruptcy = function () {
|
2013-09-27 23:34:50 +02:00
|
|
|
unread_privates = new Dict();
|
2016-11-30 03:20:29 +01:00
|
|
|
exports.unread_topic_counter.clear();
|
2013-05-17 21:32:26 +02:00
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
|
2016-04-23 00:56:44 +02:00
|
|
|
_.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;
|
|
|
|
};
|
|
|
|
|
2013-05-17 21:32:26 +02:00
|
|
|
exports.get_counts = function () {
|
|
|
|
var res = {};
|
2013-05-22 18:06:40 +02:00
|
|
|
|
2013-05-17 21:32:26 +02:00
|
|
|
// 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;
|
2013-08-23 03:14:51 +02:00
|
|
|
res.mentioned_message_count = unread_mentioned.num_items();
|
2013-08-07 23:56:51 +02:00
|
|
|
res.pm_count = new Dict(); // Hash by email -> count
|
2013-05-17 21:32:26 +02:00
|
|
|
|
2016-11-30 03:20:29 +01:00
|
|
|
// This sets stream_count, subject_count, and home_unread_messages
|
2016-12-15 01:53:33 +01:00
|
|
|
var topic_res = exports.unread_topic_counter.get_counts(res);
|
|
|
|
res.home_unread_messages = topic_res.stream_unread_messages;
|
|
|
|
res.stream_count = topic_res.stream_count;
|
|
|
|
res.subject_count = topic_res.subject_count;
|
2013-05-17 21:32:26 +02:00
|
|
|
|
|
|
|
var pm_count = 0;
|
2016-11-16 00:09:09 +01:00
|
|
|
unread_privates.each(function (obj, user_ids_string) {
|
2013-08-23 03:14:51 +02:00
|
|
|
var count = obj.num_items();
|
2016-11-16 00:09:09 +01:00
|
|
|
res.pm_count.set(user_ids_string, count);
|
2013-06-05 20:29:31 +02:00
|
|
|
pm_count += count;
|
2013-05-17 21:32:26 +02:00
|
|
|
});
|
|
|
|
res.private_message_count = pm_count;
|
|
|
|
res.home_unread_messages += pm_count;
|
|
|
|
|
2017-04-25 15:25:31 +02:00
|
|
|
if (narrow_state.active()) {
|
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 = exports.num_unread_current_messages();
|
2016-06-09 23:02:49 +02:00
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
|
2013-05-17 21:32:26 +02:00
|
|
|
return res;
|
|
|
|
};
|
|
|
|
|
2017-01-15 16:44:33 +01:00
|
|
|
exports.num_unread_for_stream = function (stream) {
|
|
|
|
return exports.unread_topic_counter.get_stream_count(stream);
|
|
|
|
};
|
|
|
|
|
2013-05-17 21:32:26 +02:00
|
|
|
exports.num_unread_for_subject = function (stream, subject) {
|
2016-11-30 03:20:29 +01:00
|
|
|
return exports.unread_topic_counter.get(stream, subject);
|
2013-05-17 21:32:26 +02:00
|
|
|
};
|
|
|
|
|
2017-04-21 18:00:19 +02:00
|
|
|
exports.topic_has_any_unread = function (stream, topic) {
|
|
|
|
return exports.unread_topic_counter.topic_has_any_unread(stream, topic);
|
|
|
|
};
|
|
|
|
|
2016-11-18 17:02:06 +01:00
|
|
|
exports.num_unread_for_person = function (user_ids_string) {
|
2016-11-16 00:09:09 +01:00
|
|
|
if (!user_ids_string) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!unread_privates.has(user_ids_string)) {
|
2013-08-22 19:06:04 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2016-11-16 00:09:09 +01:00
|
|
|
return unread_privates.get(user_ids_string).num_items();
|
2013-08-22 19:06:04 +02:00
|
|
|
};
|
|
|
|
|
2016-11-05 18:49:34 +01:00
|
|
|
|
2013-05-17 21:32:26 +02:00
|
|
|
return exports;
|
|
|
|
}());
|
2013-07-28 18:40:50 +02:00
|
|
|
if (typeof module !== 'undefined') {
|
|
|
|
module.exports = unread;
|
|
|
|
}
|