2013-02-26 23:30:13 +01:00
|
|
|
var all_msg_list = new MessageList();
|
2013-02-22 20:48:31 +01:00
|
|
|
var home_msg_list = new MessageList('zhome');
|
2013-02-12 20:01:24 +01:00
|
|
|
var narrowed_msg_list;
|
2013-02-22 20:48:31 +01:00
|
|
|
var current_msg_list = home_msg_list;
|
2012-10-12 19:14:59 +02:00
|
|
|
var subject_dict = {};
|
2013-01-14 22:18:30 +01:00
|
|
|
var people_dict = {};
|
2012-09-22 01:11:54 +02:00
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
var queued_mark_as_read = [];
|
|
|
|
var queued_flag_timer;
|
|
|
|
|
2012-10-16 04:07:52 +02:00
|
|
|
var viewport = $(window);
|
2012-10-15 19:24:34 +02:00
|
|
|
|
2012-10-16 22:06:03 +02:00
|
|
|
var get_updates_params = {
|
2012-10-17 23:10:34 +02:00
|
|
|
pointer: -1,
|
2012-11-27 21:26:48 +01:00
|
|
|
server_generation: -1 /* to be filled in on document.ready */
|
2012-10-17 23:45:03 +02:00
|
|
|
};
|
2012-11-27 21:06:17 +01:00
|
|
|
var get_updates_failures = 0;
|
2012-10-17 23:45:03 +02:00
|
|
|
|
2012-11-27 23:17:30 +01:00
|
|
|
var load_more_enabled = true;
|
|
|
|
// If the browser hasn't scrolled away from the top of the page
|
|
|
|
// since the last time that we ran load_more_messages(), we do
|
|
|
|
// not load_more_messages().
|
|
|
|
var have_scrolled_away_from_top = true;
|
|
|
|
|
2012-11-30 03:49:18 +01:00
|
|
|
var disable_pointer_movement = false;
|
|
|
|
|
2013-02-12 22:32:14 +01:00
|
|
|
// Toggles re-centering the pointer in the window
|
|
|
|
// when Home is next clicked by the user
|
|
|
|
var recenter_pointer_on_display = false;
|
|
|
|
var suppress_scroll_pointer_update = false;
|
|
|
|
|
2013-02-20 20:49:49 +01:00
|
|
|
var furthest_read = -1;
|
|
|
|
var server_furthest_read = -1;
|
2013-03-13 21:53:36 +01:00
|
|
|
var pointer_update_in_flight = false;
|
2013-02-20 20:49:49 +01:00
|
|
|
|
2013-01-14 22:18:30 +01:00
|
|
|
function add_person(person) {
|
|
|
|
people_list.push(person);
|
|
|
|
people_dict[person.email] = person;
|
|
|
|
}
|
|
|
|
|
|
|
|
$(function () {
|
|
|
|
$.each(people_list, function (idx, person) {
|
|
|
|
people_dict[person.email] = person;
|
|
|
|
});
|
2013-02-23 18:49:23 +01:00
|
|
|
// The special account feedback@humbughq.com is used for in-app
|
|
|
|
// feedback and should always show up as an autocomplete option.
|
|
|
|
typeahead_helper.update_your_recipients([{"email": "feedback@humbughq.com",
|
|
|
|
"full_name": "Humbug Feedback Bot"}]);
|
2013-01-14 22:18:30 +01:00
|
|
|
});
|
|
|
|
|
2012-10-16 16:45:33 +02:00
|
|
|
// Why do we look at the 'bottom' in above_view_threshold and the top
|
|
|
|
// in below_view_threshold as opposed to vice versa? Mostly to handle
|
|
|
|
// the case of gigantic messages. Imagine the case of a selected
|
|
|
|
// message that's so big that it takes up an two entire screens. The
|
|
|
|
// selector shouldn't move away from it until after the *bottom* of
|
|
|
|
// the message has gone too high up on the screen. (Otherwise we'd
|
|
|
|
// move the pointer right after part of the first screenful.)
|
|
|
|
function above_view_threshold(message, useTop) {
|
|
|
|
// Barnowl-style thresholds: the bottom of the pointer is never
|
|
|
|
// above the 1/5 mark.
|
|
|
|
// (if useTop = true, we look at the top of the pointer instead)
|
|
|
|
var position = message.offset().top;
|
|
|
|
if (!useTop) {
|
2012-11-01 19:03:48 +01:00
|
|
|
// outerHeight(true): Include margin
|
2012-10-16 16:45:33 +02:00
|
|
|
position += message.outerHeight(true);
|
|
|
|
}
|
|
|
|
return position < viewport.scrollTop() + viewport.height() / 5;
|
2012-10-05 16:31:10 +02:00
|
|
|
}
|
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
function below_view_threshold(message) {
|
2012-10-16 16:45:33 +02:00
|
|
|
// Barnowl-style thresholds: the top of the pointer is never below
|
2012-10-16 17:30:07 +02:00
|
|
|
// the 2/3-mark.
|
|
|
|
return message.offset().top > viewport.scrollTop() + viewport.height() * 2 / 3;
|
2012-10-05 16:31:10 +02:00
|
|
|
}
|
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
function recenter_view(message) {
|
2012-10-05 16:31:10 +02:00
|
|
|
// Barnowl-style recentering: if the pointer is too high, center
|
|
|
|
// in the middle of the screen. If the pointer is too low, center
|
|
|
|
// on the 1/5-mark.
|
|
|
|
|
|
|
|
// If this logic changes, above_view_threshold andd
|
|
|
|
// below_view_threshold must also change.
|
2013-02-14 23:48:37 +01:00
|
|
|
var selected_row_top = current_msg_list.selected_row().offset().top;
|
2012-10-16 16:45:33 +02:00
|
|
|
if (above_view_threshold(message, true)) {
|
|
|
|
// We specifically say useTop=true here, because suppose you're using
|
|
|
|
// the arrow keys to arrow up and you've moved up to a huge message.
|
|
|
|
// The message is so big that the bottom part of makes it not
|
|
|
|
// "above the view threshold". But since we're using the arrow keys
|
|
|
|
// to get here, the reason we selected this message is because
|
|
|
|
// we want to read it; so here we care about the top part.
|
2013-02-14 23:48:37 +01:00
|
|
|
viewport.scrollTop(selected_row_top - viewport.height() / 2);
|
2012-10-10 16:35:48 +02:00
|
|
|
} else if (below_view_threshold(message)) {
|
2013-02-14 23:48:37 +01:00
|
|
|
viewport.scrollTop(selected_row_top - viewport.height() / 5);
|
2012-10-05 16:31:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function scroll_to_selected() {
|
2013-02-14 23:48:37 +01:00
|
|
|
var selected_row = current_msg_list.selected_row();
|
|
|
|
if (selected_row && (selected_row.length !== 0))
|
|
|
|
recenter_view(selected_row);
|
2012-09-07 20:44:55 +02:00
|
|
|
}
|
|
|
|
|
2013-02-12 22:32:14 +01:00
|
|
|
function maybe_scroll_to_selected() {
|
|
|
|
// If we have been previously instructed to re-center to the
|
|
|
|
// selected message, then do so
|
|
|
|
if (recenter_pointer_on_display) {
|
|
|
|
scroll_to_selected();
|
|
|
|
recenter_pointer_on_display = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-03 19:49:12 +01:00
|
|
|
function get_private_message_recipient(message, attr) {
|
2012-09-13 22:00:11 +02:00
|
|
|
var recipient, i;
|
2012-10-15 21:57:41 +02:00
|
|
|
var other_recipients = $.grep(message.display_recipient,
|
|
|
|
function (element, index) {
|
|
|
|
return element.email !== email;
|
|
|
|
});
|
2012-10-17 23:07:46 +02:00
|
|
|
if (other_recipients.length === 0) {
|
2012-12-03 19:49:12 +01:00
|
|
|
// private message with oneself
|
2012-10-27 02:31:46 +02:00
|
|
|
return message.display_recipient[0][attr];
|
2012-10-17 23:07:46 +02:00
|
|
|
}
|
2012-09-13 22:00:11 +02:00
|
|
|
|
2012-10-27 02:31:46 +02:00
|
|
|
recipient = other_recipients[0][attr];
|
2012-10-15 21:57:41 +02:00
|
|
|
for (i = 1; i < other_recipients.length; i++) {
|
2012-10-27 02:31:46 +02:00
|
|
|
recipient += ', ' + other_recipients[i][attr];
|
2012-09-26 21:25:49 +02:00
|
|
|
}
|
|
|
|
return recipient;
|
|
|
|
}
|
|
|
|
|
2012-10-10 16:18:51 +02:00
|
|
|
function respond_to_message(reply_type) {
|
2012-10-18 20:17:55 +02:00
|
|
|
var message, msg_type;
|
2013-02-14 23:48:37 +01:00
|
|
|
message = current_msg_list.selected_message();
|
2012-10-18 20:17:55 +02:00
|
|
|
|
2013-02-22 19:07:26 +01:00
|
|
|
if (message === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-18 20:17:55 +02:00
|
|
|
var stream = '';
|
|
|
|
var subject = '';
|
2012-10-10 23:09:16 +02:00
|
|
|
if (message.type === "stream") {
|
2012-10-18 20:17:55 +02:00
|
|
|
stream = message.display_recipient;
|
|
|
|
subject = message.subject;
|
2012-10-09 20:18:55 +02:00
|
|
|
}
|
2012-10-18 20:17:55 +02:00
|
|
|
|
2012-11-08 00:38:21 +01:00
|
|
|
var pm_recipient = message.reply_to;
|
2012-12-03 19:49:12 +01:00
|
|
|
if (reply_type === "personal" && message.type === "private") {
|
|
|
|
// reply_to for private messages is everyone involved, so for
|
|
|
|
// personals replies we need to set the the private message
|
|
|
|
// recipient to just the sender
|
2012-11-08 00:38:21 +01:00
|
|
|
pm_recipient = message.sender_email;
|
2012-10-09 15:51:12 +02:00
|
|
|
}
|
2012-12-03 19:49:12 +01:00
|
|
|
if (reply_type === 'personal' || message.type === 'private') {
|
2012-11-08 00:38:21 +01:00
|
|
|
msg_type = 'private';
|
|
|
|
} else {
|
2012-10-18 20:17:55 +02:00
|
|
|
msg_type = message.type;
|
2012-09-12 16:23:55 +02:00
|
|
|
}
|
2012-10-19 21:59:03 +02:00
|
|
|
compose.start(msg_type, {'stream': stream, 'subject': subject,
|
2013-02-13 21:30:17 +01:00
|
|
|
'private_message_recipient': pm_recipient,
|
|
|
|
'replying_to_message': message});
|
2012-09-12 16:23:55 +02:00
|
|
|
}
|
|
|
|
|
2013-02-12 20:01:24 +01:00
|
|
|
function message_range(msg_list, start, end) {
|
|
|
|
// Returns messages from the given message list in the specified range, inclusive
|
2013-02-12 20:41:01 +01:00
|
|
|
var result = [];
|
|
|
|
var i;
|
|
|
|
|
|
|
|
for (i = start; i <= end; i++) {
|
2013-02-12 20:01:24 +01:00
|
|
|
if (msg_list.get(i) !== undefined) {
|
|
|
|
result.push(msg_list.get(i));
|
2013-02-12 20:41:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
function send_queued_flags() {
|
|
|
|
if (queued_mark_as_read.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
2013-02-12 20:41:01 +01:00
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
$.ajax({
|
|
|
|
type: 'POST',
|
|
|
|
url: '/json/update_message_flags',
|
|
|
|
data: {messages: JSON.stringify(queued_mark_as_read),
|
|
|
|
op: 'add',
|
|
|
|
flag: 'read'},
|
|
|
|
dataType: 'json'});
|
2013-02-12 20:41:01 +01:00
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
queued_mark_as_read = [];
|
|
|
|
}
|
2013-02-12 22:32:14 +01:00
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
var unread_counts = {'stream': {}, 'private': {}};
|
|
|
|
var home_unread_messages = 0;
|
2013-02-12 22:32:14 +01:00
|
|
|
|
2013-03-19 21:53:49 +01:00
|
|
|
function unread_in_current_view() {
|
|
|
|
var unread = 0;
|
|
|
|
if (!narrow.active()) {
|
|
|
|
unread = home_unread_messages;
|
|
|
|
} else {
|
|
|
|
$.each(current_msg_list.all(), function (idx, msg) {
|
|
|
|
if (message_unread(msg) && msg.id > current_msg_list.selected_id()) {
|
|
|
|
unread += 1;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return unread;
|
|
|
|
}
|
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
function message_unread(message) {
|
|
|
|
if (message === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-03-14 19:55:44 +01:00
|
|
|
var sent_by_human = ['website', 'iphone', 'android']
|
|
|
|
.indexOf(message.client.toLowerCase()) !== -1;
|
|
|
|
|
|
|
|
if (message.sender_email === email && sent_by_human) {
|
2013-03-04 23:44:07 +01:00
|
|
|
return false;
|
|
|
|
}
|
2013-02-15 04:51:18 +01:00
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
return message.flags === undefined ||
|
|
|
|
message.flags.indexOf('read') === -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function update_unread_counts() {
|
2013-02-27 19:18:21 +01:00
|
|
|
home_unread_messages = 0;
|
2013-02-15 04:53:03 +01:00
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
$.each(unread_counts.stream, function(index, obj) {
|
2013-02-15 04:53:03 +01:00
|
|
|
var count = Object.keys(obj).length;
|
|
|
|
ui.set_count("stream", index, count);
|
2013-02-27 19:18:21 +01:00
|
|
|
if (narrow.stream_in_home(index)) {
|
|
|
|
home_unread_messages += count;
|
|
|
|
}
|
2013-02-12 20:41:01 +01:00
|
|
|
});
|
2013-02-14 20:13:28 +01:00
|
|
|
|
2013-02-15 04:51:18 +01:00
|
|
|
var pm_count = 0;
|
2013-03-04 23:44:07 +01:00
|
|
|
$.each(unread_counts["private"], function(index, obj) {
|
2013-02-14 20:13:28 +01:00
|
|
|
pm_count += Object.keys(obj).length;
|
|
|
|
});
|
|
|
|
ui.set_count("global", "private", pm_count);
|
2013-02-27 19:18:21 +01:00
|
|
|
home_unread_messages += pm_count;
|
2013-03-04 23:44:07 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-03-15 20:07:38 +01:00
|
|
|
function mark_all_as_read(cont) {
|
2013-03-04 23:44:07 +01:00
|
|
|
$.each(all_msg_list.all(), function (idx, msg) {
|
|
|
|
msg.flags = msg.flags || [];
|
|
|
|
msg.flags.push('read');
|
|
|
|
});
|
|
|
|
unread_counts = {'stream': {}, 'private': {}};
|
|
|
|
update_unread_counts();
|
|
|
|
|
|
|
|
$.ajax({
|
|
|
|
type: 'POST',
|
|
|
|
url: '/json/update_message_flags',
|
|
|
|
data: {messages: JSON.stringify([]),
|
|
|
|
all: true,
|
|
|
|
op: 'add',
|
|
|
|
flag: 'read'},
|
2013-03-15 20:07:38 +01:00
|
|
|
dataType: 'json',
|
|
|
|
success: cont});
|
2013-03-04 23:44:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function unread_hashkey(message) {
|
|
|
|
var hashkey;
|
|
|
|
if (message.type === 'stream') {
|
|
|
|
hashkey = message.display_recipient;
|
|
|
|
} else {
|
|
|
|
hashkey = message.display_reply_to;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unread_counts[message.type][hashkey] === undefined) {
|
|
|
|
unread_counts[message.type][hashkey] = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
return hashkey;
|
|
|
|
}
|
|
|
|
|
|
|
|
function process_loaded_for_unread(messages) {
|
|
|
|
$.each(messages, function (idx, message) {
|
|
|
|
var unread = message_unread(message);
|
|
|
|
if (!unread) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var hashkey = unread_hashkey(message);
|
|
|
|
unread_counts[message.type][hashkey][message.id] = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
update_unread_counts();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Takes a list of messages and marks them as read
|
|
|
|
function process_read_messages(messages) {
|
|
|
|
var processed = [];
|
|
|
|
$.each(messages, function (idx, message) {
|
|
|
|
var hashkey = unread_hashkey(message);
|
|
|
|
|
|
|
|
message.flags = message.flags || [];
|
|
|
|
message.flags.push('read');
|
|
|
|
processed.push(message.id);
|
|
|
|
|
|
|
|
delete unread_counts[message.type][hashkey][message.id];
|
|
|
|
});
|
|
|
|
|
|
|
|
if (processed.length > 0) {
|
|
|
|
queued_mark_as_read = queued_mark_as_read.concat(processed);
|
|
|
|
|
|
|
|
if (queued_flag_timer !== undefined) {
|
|
|
|
clearTimeout(queued_flag_timer);
|
|
|
|
}
|
|
|
|
|
|
|
|
queued_flag_timer = setTimeout(send_queued_flags, 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
update_unread_counts();
|
2013-02-12 20:41:01 +01:00
|
|
|
}
|
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
function process_visible_unread_messages() {
|
|
|
|
// For any messages visible on the screen, make sure they have been marked
|
|
|
|
// as unread.
|
|
|
|
if (! notifications.window_has_focus()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var selected = current_msg_list.selected_message();
|
2013-03-20 21:20:31 +01:00
|
|
|
var top = viewport.scrollTop();
|
|
|
|
var height = viewport.height();
|
|
|
|
var bottom = top + height;
|
|
|
|
var middle = top + (height / 2);
|
2013-03-04 23:44:07 +01:00
|
|
|
|
|
|
|
// Being simplistic about this, the smallest message is 30 px high.
|
|
|
|
var selected_row = rows.get(current_msg_list.selected_id(), current_msg_list.table_name);
|
2013-03-20 21:20:31 +01:00
|
|
|
var num_neighbors = Math.floor(height / 30);
|
2013-03-04 23:44:07 +01:00
|
|
|
var candidates = $.merge(selected_row.prevAll("tr.message_row[zid]:lt(" + num_neighbors + ")"),
|
|
|
|
selected_row.nextAll("tr.message_row[zid]:lt(" + num_neighbors + ")"));
|
|
|
|
|
|
|
|
var visible_messages = candidates.filter(function (idx, message) {
|
|
|
|
var row = $(message);
|
2013-03-20 21:20:31 +01:00
|
|
|
var row_top = row.offset().top;
|
|
|
|
var row_height = row.height();
|
|
|
|
var row_bottom = (row_top + row_height);
|
|
|
|
var entirely_within_view = row_top > top && row_bottom < bottom;
|
|
|
|
|
|
|
|
// Mark very tall messages as read once we've gotten past them
|
|
|
|
if (row_height > height && row_top > top) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
return entirely_within_view;
|
|
|
|
});
|
|
|
|
|
|
|
|
var mark_as_read = $.map(visible_messages, function(msg) {
|
|
|
|
var message = current_msg_list.get(rows.id($(msg)));
|
|
|
|
if (! message_unread(message)) {
|
|
|
|
return undefined;
|
|
|
|
} else {
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (message_unread(selected)) {
|
|
|
|
mark_as_read.push(selected);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mark_as_read.length > 0) {
|
|
|
|
process_read_messages(mark_as_read);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-20 04:19:18 +01:00
|
|
|
function send_pointer_update() {
|
2013-03-13 21:53:36 +01:00
|
|
|
if (!pointer_update_in_flight &&
|
|
|
|
furthest_read > server_furthest_read) {
|
|
|
|
pointer_update_in_flight = true;
|
|
|
|
$.ajax({
|
|
|
|
type: 'POST',
|
|
|
|
url: '/json/update_pointer',
|
|
|
|
data: {pointer: furthest_read},
|
|
|
|
dataType: 'json',
|
|
|
|
success: function () {
|
|
|
|
server_furthest_read = furthest_read;
|
|
|
|
pointer_update_in_flight = false;
|
|
|
|
},
|
|
|
|
error: function () {
|
|
|
|
pointer_update_in_flight = false;
|
|
|
|
}
|
|
|
|
});
|
2013-02-20 04:19:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-20 20:49:49 +01:00
|
|
|
$(function () {
|
|
|
|
furthest_read = initial_pointer;
|
|
|
|
server_furthest_read = initial_pointer;
|
2012-09-06 19:32:02 +02:00
|
|
|
|
2013-02-20 20:49:49 +01:00
|
|
|
// We only send pointer updates when the user has been idle for a
|
|
|
|
// short while to avoid hammering the server
|
|
|
|
$(document).idle({idle: 1000,
|
|
|
|
onIdle: send_pointer_update,
|
|
|
|
keepTracking: true});
|
2012-10-23 22:48:20 +02:00
|
|
|
|
2013-02-20 20:49:49 +01:00
|
|
|
$(document).on('message_selected.zephyr', function (event) {
|
|
|
|
// Narrowing is a temporary view on top of the home view and
|
|
|
|
// doesn't affect your pointer in the home view.
|
2013-02-22 20:48:31 +01:00
|
|
|
if (event.msg_list === home_msg_list
|
2013-02-20 20:49:49 +01:00
|
|
|
&& event.id > furthest_read)
|
|
|
|
{
|
|
|
|
furthest_read = event.id;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2012-08-31 17:10:19 +02:00
|
|
|
|
2012-11-07 20:31:45 +01:00
|
|
|
function case_insensitive_find(term, array) {
|
|
|
|
var lowered_term = term.toLowerCase();
|
|
|
|
return $.grep(array, function (elt) {
|
|
|
|
return elt.toLowerCase() === lowered_term;
|
|
|
|
}).length !== 0;
|
|
|
|
}
|
|
|
|
|
2012-11-20 19:50:28 +01:00
|
|
|
function add_message_metadata(message, dummy) {
|
2013-02-12 20:01:24 +01:00
|
|
|
if (all_msg_list.get(message.id)) {
|
|
|
|
return all_msg_list.get(message.id);
|
2012-11-20 19:50:28 +01:00
|
|
|
}
|
2012-12-14 18:12:54 +01:00
|
|
|
get_updates_params.last = Math.max(get_updates_params.last || 0, message.id);
|
2012-10-25 23:32:20 +02:00
|
|
|
|
2012-10-12 17:26:04 +02:00
|
|
|
var involved_people;
|
|
|
|
|
2012-10-10 16:34:42 +02:00
|
|
|
switch (message.type) {
|
2012-10-10 22:57:21 +02:00
|
|
|
case 'stream':
|
2012-10-10 23:31:26 +02:00
|
|
|
message.is_stream = true;
|
2012-10-12 19:14:59 +02:00
|
|
|
if (! subject_dict.hasOwnProperty(message.display_recipient)) {
|
|
|
|
subject_dict[message.display_recipient] = [];
|
|
|
|
}
|
2012-11-07 20:31:45 +01:00
|
|
|
if (! case_insensitive_find(message.subject, subject_dict[message.display_recipient])) {
|
2012-11-01 17:03:45 +01:00
|
|
|
subject_dict[message.display_recipient].push(message.subject);
|
2012-10-12 19:14:59 +02:00
|
|
|
subject_dict[message.display_recipient].sort();
|
|
|
|
// We don't need to update the autocomplete after this because
|
|
|
|
// the subject box's source is a function
|
2012-09-04 20:31:23 +02:00
|
|
|
}
|
2012-10-10 16:34:42 +02:00
|
|
|
message.reply_to = message.sender_email;
|
2012-10-12 17:26:04 +02:00
|
|
|
|
2012-10-13 00:32:59 +02:00
|
|
|
involved_people = [{'full_name': message.sender_full_name,
|
2012-10-12 17:26:04 +02:00
|
|
|
'email': message.sender_email}];
|
2012-09-26 19:57:19 +02:00
|
|
|
break;
|
|
|
|
|
2012-12-03 19:49:12 +01:00
|
|
|
case 'private':
|
|
|
|
message.is_private = true;
|
2013-03-04 23:42:30 +01:00
|
|
|
message.reply_to = get_private_message_recipient(message, 'email').replace(/ /g, "");
|
2012-12-03 19:49:12 +01:00
|
|
|
message.display_reply_to = get_private_message_recipient(message, 'full_name');
|
2012-10-12 17:26:04 +02:00
|
|
|
|
|
|
|
involved_people = message.display_recipient;
|
2012-11-19 18:36:53 +01:00
|
|
|
|
|
|
|
if (message.sender_email === email) {
|
2012-11-26 19:24:34 +01:00
|
|
|
typeahead_helper.update_your_recipients(involved_people);
|
2012-11-19 18:36:53 +01:00
|
|
|
} else {
|
2012-11-26 19:24:34 +01:00
|
|
|
typeahead_helper.update_all_recipients(involved_people);
|
2012-11-19 18:36:53 +01:00
|
|
|
}
|
2012-09-26 19:57:19 +02:00
|
|
|
break;
|
2012-10-12 17:26:04 +02:00
|
|
|
}
|
|
|
|
|
2012-10-13 00:17:48 +02:00
|
|
|
// Add new people involved in this message to the people list
|
|
|
|
$.each(involved_people, function (idx, person) {
|
|
|
|
// Do the hasOwnProperty() call via the prototype to avoid problems
|
|
|
|
// with keys like "hasOwnProperty"
|
2013-02-11 23:50:47 +01:00
|
|
|
if (!typeahead_helper.known_to_typeahead(person) && people_dict[person.email] === undefined) {
|
2013-01-14 22:18:30 +01:00
|
|
|
add_person(person);
|
2012-11-19 01:09:40 +01:00
|
|
|
typeahead_helper.autocomplete_needs_update(true);
|
2012-09-04 20:31:23 +02:00
|
|
|
}
|
2012-10-13 00:17:48 +02:00
|
|
|
});
|
2012-08-30 20:24:29 +02:00
|
|
|
|
2012-11-20 19:50:28 +01:00
|
|
|
return message;
|
2012-08-29 17:12:21 +02:00
|
|
|
}
|
|
|
|
|
2013-02-28 23:00:03 +01:00
|
|
|
function add_messages_helper(messages, msg_list, predicate) {
|
2013-02-27 22:51:37 +01:00
|
|
|
var top_messages = [];
|
|
|
|
var bottom_messages = [];
|
|
|
|
|
|
|
|
// If we're initially populating the list, save the messages in
|
|
|
|
// bottom_messages regardless
|
|
|
|
if (msg_list.selected_id() === -1 && msg_list.empty()) {
|
|
|
|
bottom_messages = $.grep(messages, predicate);
|
|
|
|
} else {
|
|
|
|
$.each(messages, function (idx, msg) {
|
|
|
|
// Filter out duplicates that are already in msg_list, and all messages
|
|
|
|
// that fail our filter predicate
|
|
|
|
if (! (msg_list.get(msg.id) === undefined && predicate(msg))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put messages in correct order on either side of the message list
|
|
|
|
if (msg_list.empty() || (msg.id < msg_list.first().id)) {
|
|
|
|
top_messages.push(msg);
|
|
|
|
} else if (msg.id > msg_list.last().id) {
|
|
|
|
bottom_messages.push(msg);
|
|
|
|
} else {
|
2013-03-11 17:32:28 +01:00
|
|
|
blueslip.error("Attempting to insert a message in the middle of the msg_list");
|
|
|
|
bottom_messages.push(msg);
|
2013-02-27 22:51:37 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2013-02-12 20:01:24 +01:00
|
|
|
|
2013-02-28 23:00:03 +01:00
|
|
|
msg_list.prepend(top_messages);
|
|
|
|
msg_list.append(bottom_messages);
|
2013-02-08 19:57:16 +01:00
|
|
|
return top_messages.length > 0;
|
|
|
|
}
|
|
|
|
|
2013-02-12 20:01:24 +01:00
|
|
|
function add_messages(messages, msg_list, opts) {
|
2012-11-20 19:50:28 +01:00
|
|
|
var prepended = false;
|
2012-10-25 00:42:45 +02:00
|
|
|
if (!messages)
|
2012-09-28 19:27:52 +02:00
|
|
|
return;
|
|
|
|
|
2013-01-16 19:50:18 +01:00
|
|
|
util.destroy_loading_indicator($('#page_loading_indicator'));
|
2013-02-12 19:28:21 +01:00
|
|
|
util.destroy_first_run_message();
|
2012-11-20 19:50:28 +01:00
|
|
|
messages = $.map(messages, add_message_metadata);
|
2012-11-20 17:35:30 +01:00
|
|
|
|
2013-02-28 23:00:03 +01:00
|
|
|
var predicate;
|
2013-02-22 20:48:31 +01:00
|
|
|
if (msg_list === home_msg_list) {
|
2013-02-27 19:18:21 +01:00
|
|
|
predicate = narrow.message_in_home;
|
2013-02-22 20:48:31 +01:00
|
|
|
} else if (msg_list === narrowed_msg_list) {
|
2013-02-12 20:01:24 +01:00
|
|
|
predicate = narrow.predicate();
|
2013-02-22 20:48:31 +01:00
|
|
|
} else if (msg_list === all_msg_list) {
|
|
|
|
predicate = function () { return true; };
|
|
|
|
} else {
|
2013-03-11 17:32:28 +01:00
|
|
|
blueslip.fatal("Adding message to a list that is not known");
|
2013-02-12 20:01:24 +01:00
|
|
|
}
|
2013-02-12 22:32:14 +01:00
|
|
|
|
2013-02-28 23:00:03 +01:00
|
|
|
if (add_messages_helper(messages, msg_list, predicate)) {
|
2013-02-12 20:01:24 +01:00
|
|
|
prepended = true;
|
2012-10-01 21:02:39 +02:00
|
|
|
}
|
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
process_loaded_for_unread(messages);
|
|
|
|
|
2013-03-07 17:30:42 +01:00
|
|
|
if ((msg_list === narrowed_msg_list) && !msg_list.empty() &&
|
|
|
|
(msg_list.selected_id() === -1)) {
|
2013-02-23 19:38:25 +01:00
|
|
|
// If adding some new messages to the message tables caused
|
|
|
|
// our current narrow to no longer be empty, hide the empty
|
|
|
|
// feed placeholder text.
|
|
|
|
util.hide_empty_narrow_message();
|
2013-03-07 17:30:42 +01:00
|
|
|
// And also select the newly arrived message.
|
|
|
|
msg_list.select_id(msg_list.selected_id(), {then_scroll: true, use_closest: true});
|
2013-02-23 19:38:25 +01:00
|
|
|
}
|
|
|
|
|
2012-09-29 01:38:03 +02:00
|
|
|
// If we prepended messages, then we need to scroll back to the pointer.
|
|
|
|
// This will mess with the user's scrollwheel use; possibly we should be
|
|
|
|
// more clever here. However (for now) we only prepend on page load,
|
|
|
|
// so maybe it's okay.
|
2012-10-03 00:39:21 +02:00
|
|
|
//
|
|
|
|
// We also need to re-select the message by ID, because we might have
|
|
|
|
// removed and re-added the row as part of prepend collapsing.
|
2013-02-22 20:48:31 +01:00
|
|
|
//
|
|
|
|
// We select the closest id as a fallback in case the previously selected
|
|
|
|
// message is no longer in the list
|
2013-02-20 18:26:50 +01:00
|
|
|
if (prepended && (msg_list.selected_id() >= 0)) {
|
2013-02-22 20:48:31 +01:00
|
|
|
msg_list.select_id(msg_list.selected_id(), {then_scroll: true, use_closest: true});
|
2012-10-03 00:39:21 +02:00
|
|
|
}
|
2012-09-29 01:38:03 +02:00
|
|
|
|
2012-11-19 01:09:40 +01:00
|
|
|
if (typeahead_helper.autocomplete_needs_update()) {
|
|
|
|
typeahead_helper.update_autocomplete();
|
2012-11-01 18:01:33 +01:00
|
|
|
}
|
2013-01-30 21:49:56 +01:00
|
|
|
|
|
|
|
// If the new messages are off the screen, show a notification
|
|
|
|
notifications_bar.update();
|
2012-09-24 21:21:23 +02:00
|
|
|
}
|
|
|
|
|
2013-03-07 22:31:21 +01:00
|
|
|
function deduplicate_messages(messages) {
|
|
|
|
var new_message_ids = {};
|
|
|
|
return $.grep(messages, function (msg, idx) {
|
|
|
|
if (new_message_ids[msg.id] === undefined
|
|
|
|
&& all_msg_list.get(msg.id) === undefined)
|
|
|
|
{
|
|
|
|
new_message_ids[msg.id] = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2012-10-10 20:21:22 +02:00
|
|
|
var get_updates_xhr;
|
|
|
|
var get_updates_timeout;
|
2012-11-20 23:15:50 +01:00
|
|
|
function get_updates(options) {
|
|
|
|
var defaults = {dont_block: false};
|
|
|
|
options = $.extend({}, defaults, options);
|
|
|
|
|
2012-10-29 22:02:10 +01:00
|
|
|
get_updates_params.pointer = furthest_read;
|
2012-11-27 21:06:17 +01:00
|
|
|
get_updates_params.dont_block = options.dont_block || get_updates_failures > 0;
|
2012-11-27 21:26:48 +01:00
|
|
|
if (reload.is_pending()) {
|
|
|
|
// We only send a server_generation to the server if we're
|
|
|
|
// interested in an immediate reply to tell us if we need to
|
|
|
|
// reload. Once we're already reloading, we need to not
|
|
|
|
// submit the parameter, so that the server will process our
|
|
|
|
// future requests in longpolling mode.
|
|
|
|
delete get_updates_params.server_generation;
|
|
|
|
}
|
2012-10-17 23:10:34 +02:00
|
|
|
|
2012-10-10 20:21:22 +02:00
|
|
|
get_updates_xhr = $.ajax({
|
2012-08-31 21:33:04 +02:00
|
|
|
type: 'POST',
|
2012-10-16 21:42:40 +02:00
|
|
|
url: '/json/get_updates',
|
2012-10-16 22:06:03 +02:00
|
|
|
data: get_updates_params,
|
2012-08-31 21:33:04 +02:00
|
|
|
dataType: 'json',
|
2012-12-11 18:08:18 +01:00
|
|
|
timeout: poll_timeout,
|
2012-08-31 21:33:04 +02:00
|
|
|
success: function (data) {
|
2012-10-23 17:00:00 +02:00
|
|
|
if (! data) {
|
2013-02-14 22:03:10 +01:00
|
|
|
// The server occasionally returns no data during a
|
2012-10-23 17:00:00 +02:00
|
|
|
// restart. Ignore those responses so the page keeps
|
|
|
|
// working
|
|
|
|
get_updates_timeout = setTimeout(get_updates, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-11-27 21:06:17 +01:00
|
|
|
get_updates_failures = 0;
|
2012-09-05 22:33:04 +02:00
|
|
|
$('#connection-error').hide();
|
|
|
|
|
2012-10-17 00:24:29 +02:00
|
|
|
if (get_updates_params.server_generation === -1) {
|
|
|
|
get_updates_params.server_generation = data.server_generation;
|
|
|
|
} else if (data.server_generation !== get_updates_params.server_generation) {
|
2012-10-29 21:02:46 +01:00
|
|
|
reload.initiate();
|
Make the client reload the page when it detects a server restart
If the client is not composing a message, we can just force a page
reload. However, if he is composing a message, we must preserve that
message while still reloading as soon as possible.
We take the following approach: if the client has not completed the
composition after 5 minutes, do a compose-preserving reload
(described below). If he sends the message before the timeout
expires, reload the page after a successful send. If the send fails
(not due to server timeout), however, we do a compose-perserving
reload in case the error was due to the data format changing. If the
send failed due to server timeout, we don't reload because the reload
will probably also fail.
In a compose-preserving reload, we redirect to an URI that has a
fragment indicating we are doing a reload and containing all the
necessary information for restoring the compose window to its
previous state. On page load, we check the fragment to see if we
just did a compose-preserving reload, and, if we did, we restore the
compose window (or just try the send again in the case of send
failure). The URI fragment looks like:
(imported from commit af4eeb3930c24118e088057d4da456748fbd2229)
2012-10-16 21:16:06 +02:00
|
|
|
}
|
|
|
|
|
2012-10-25 21:48:35 +02:00
|
|
|
if (data.messages.length !== 0) {
|
2013-03-07 22:31:21 +01:00
|
|
|
// There is a known bug (#1062) in our backend
|
|
|
|
// whereby duplicate messages are delivered during a
|
|
|
|
// server update. Once that bug is fixed, this
|
|
|
|
// should no longer be needed
|
|
|
|
var messages = deduplicate_messages(data.messages);
|
|
|
|
|
2013-02-22 20:48:31 +01:00
|
|
|
if (narrow.active()) {
|
2013-03-07 22:31:21 +01:00
|
|
|
add_messages(messages, narrowed_msg_list);
|
2013-02-12 20:01:24 +01:00
|
|
|
}
|
2013-03-07 22:31:21 +01:00
|
|
|
add_messages(messages, all_msg_list);
|
|
|
|
add_messages(messages, home_msg_list);
|
2013-03-04 23:44:07 +01:00
|
|
|
process_visible_unread_messages();
|
2013-03-07 22:31:21 +01:00
|
|
|
notifications.received_messages(messages);
|
2013-03-04 16:59:09 +01:00
|
|
|
compose.update_faded_messages();
|
2012-10-17 23:10:34 +02:00
|
|
|
}
|
|
|
|
|
2012-10-29 22:02:10 +01:00
|
|
|
if (data.new_pointer !== undefined
|
2012-10-29 22:02:10 +01:00
|
|
|
&& data.new_pointer > furthest_read)
|
2012-10-29 22:02:10 +01:00
|
|
|
{
|
2012-10-29 22:02:10 +01:00
|
|
|
furthest_read = data.new_pointer;
|
|
|
|
server_furthest_read = data.new_pointer;
|
2013-03-11 15:38:54 +01:00
|
|
|
home_msg_list.select_id(data.new_pointer, {then_scroll: true, use_closest: true});
|
2013-02-14 23:48:37 +01:00
|
|
|
}
|
|
|
|
|
2013-02-22 20:48:31 +01:00
|
|
|
if ((home_msg_list.selected_id() === -1) && !home_msg_list.empty()) {
|
|
|
|
home_msg_list.select_id(home_msg_list.first().id, {then_scroll: false});
|
2012-10-29 22:02:10 +01:00
|
|
|
}
|
2012-10-17 23:10:34 +02:00
|
|
|
|
2012-12-11 18:17:56 +01:00
|
|
|
get_updates_timeout = setTimeout(get_updates, 0);
|
2012-08-31 21:33:04 +02:00
|
|
|
},
|
2012-09-05 22:17:14 +02:00
|
|
|
error: function (xhr, error_type, exn) {
|
2013-01-23 17:33:07 +01:00
|
|
|
// If we are old enough to have messages outside of the Tornado
|
|
|
|
// cache, immediately reload.
|
|
|
|
if ((xhr.status === 400) &&
|
|
|
|
($.parseJSON(xhr.responseText).msg.indexOf("too old") !== -1)) {
|
|
|
|
reload.initiate({immediate: true});
|
|
|
|
}
|
|
|
|
|
2012-09-07 20:35:15 +02:00
|
|
|
if (error_type === 'timeout') {
|
2012-09-05 22:17:14 +02:00
|
|
|
// Retry indefinitely on timeout.
|
2012-11-27 21:06:17 +01:00
|
|
|
get_updates_failures = 0;
|
2012-09-05 22:33:04 +02:00
|
|
|
$('#connection-error').hide();
|
2012-09-05 22:17:14 +02:00
|
|
|
} else {
|
2012-11-27 21:06:17 +01:00
|
|
|
get_updates_failures += 1;
|
2012-09-05 22:17:14 +02:00
|
|
|
}
|
|
|
|
|
2012-11-27 21:06:17 +01:00
|
|
|
if (get_updates_failures >= 5) {
|
2012-08-31 21:33:04 +02:00
|
|
|
$('#connection-error').show();
|
|
|
|
} else {
|
2012-09-05 22:33:04 +02:00
|
|
|
$('#connection-error').hide();
|
2012-08-31 21:33:04 +02:00
|
|
|
}
|
2012-09-05 22:33:04 +02:00
|
|
|
|
2012-11-27 21:06:17 +01:00
|
|
|
var retry_sec = Math.min(90, Math.exp(get_updates_failures/2));
|
2012-10-10 20:21:22 +02:00
|
|
|
get_updates_timeout = setTimeout(get_updates, retry_sec*1000);
|
2012-08-31 21:33:04 +02:00
|
|
|
}
|
|
|
|
});
|
2012-08-29 17:12:21 +02:00
|
|
|
}
|
2012-09-04 20:31:23 +02:00
|
|
|
|
2013-03-14 19:42:37 +01:00
|
|
|
function load_old_messages(opts) {
|
|
|
|
opts = $.extend({}, {
|
|
|
|
for_narrow: false,
|
|
|
|
cont_will_add_messages: false
|
|
|
|
}, opts);
|
2012-11-09 01:06:07 +01:00
|
|
|
|
2013-03-14 19:42:37 +01:00
|
|
|
var data = {anchor: opts.anchor,
|
|
|
|
num_before: opts.num_before,
|
|
|
|
num_after: opts.num_after};
|
2012-12-19 23:58:02 +01:00
|
|
|
|
2013-03-14 19:42:37 +01:00
|
|
|
if (opts.for_narrow && narrow.active())
|
2013-02-05 19:25:30 +01:00
|
|
|
data.narrow = JSON.stringify(narrow.public_operators());
|
2012-12-19 23:58:02 +01:00
|
|
|
|
2013-01-07 23:35:00 +01:00
|
|
|
function process_result(messages) {
|
|
|
|
$('#connection-error').hide();
|
|
|
|
|
2013-02-23 19:38:25 +01:00
|
|
|
if ((messages.length === 0) && (current_msg_list === narrowed_msg_list) &&
|
|
|
|
narrowed_msg_list.empty()) {
|
|
|
|
// Even after trying to load more messages, we have no
|
|
|
|
// messages to display in this narrow.
|
|
|
|
util.show_empty_narrow_message();
|
|
|
|
}
|
|
|
|
|
2013-02-22 20:48:31 +01:00
|
|
|
// If we're loading more messages into the home view, save them to
|
|
|
|
// the all_msg_list as well, as the home_msg_list is reconstructed
|
|
|
|
// from all_msg_list.
|
2013-03-14 19:42:37 +01:00
|
|
|
if (opts.msg_list === home_msg_list) {
|
2013-02-22 20:48:31 +01:00
|
|
|
add_messages(messages, all_msg_list, {append_to_table: false});
|
|
|
|
}
|
|
|
|
|
2013-03-14 19:42:37 +01:00
|
|
|
if (messages.length !== 0 && !opts.cont_will_add_messages) {
|
|
|
|
add_messages(messages, opts.msg_list);
|
2013-01-07 23:35:00 +01:00
|
|
|
}
|
|
|
|
|
2013-03-14 19:42:37 +01:00
|
|
|
if (opts.cont !== undefined) {
|
|
|
|
opts.cont(messages);
|
2013-01-07 23:35:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-25 00:42:45 +02:00
|
|
|
$.ajax({
|
|
|
|
type: 'POST',
|
|
|
|
url: '/json/get_old_messages',
|
2012-12-19 23:58:02 +01:00
|
|
|
data: data,
|
2012-10-25 00:42:45 +02:00
|
|
|
dataType: 'json',
|
|
|
|
success: function (data) {
|
2013-03-15 20:57:37 +01:00
|
|
|
if (opts.for_narrow && opts.msg_list !== current_msg_list) {
|
|
|
|
// We unnarrowed before receiving new messages so
|
|
|
|
// don't bother processing the newly arrived messages.
|
|
|
|
return;
|
|
|
|
}
|
2012-10-25 00:42:45 +02:00
|
|
|
if (! data) {
|
|
|
|
// The server occationally returns no data during a
|
|
|
|
// restart. Ignore those responses and try again
|
|
|
|
setTimeout(function () {
|
2013-03-14 19:42:37 +01:00
|
|
|
load_old_messages(opts);
|
2012-10-25 00:42:45 +02:00
|
|
|
}, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-07 23:35:00 +01:00
|
|
|
process_result(data.messages);
|
2012-10-25 00:42:45 +02:00
|
|
|
},
|
|
|
|
error: function (xhr, error_type, exn) {
|
2013-03-15 20:57:37 +01:00
|
|
|
if (opts.for_narrow && opts.msg_list !== current_msg_list) {
|
|
|
|
// We unnarrowed before getting an error so don't
|
|
|
|
// bother trying again or doing further processing.
|
|
|
|
return;
|
|
|
|
}
|
2013-01-07 23:35:00 +01:00
|
|
|
if (xhr.status === 400) {
|
|
|
|
// Bad request: We probably specified a narrow operator
|
|
|
|
// for a nonexistent stream or something. We shouldn't
|
|
|
|
// retry or display a connection error.
|
|
|
|
//
|
|
|
|
// FIXME: Warn the user when this has happened?
|
|
|
|
process_result([]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-25 00:42:45 +02:00
|
|
|
// We might want to be more clever here
|
|
|
|
$('#connection-error').show();
|
|
|
|
setTimeout(function () {
|
2013-03-14 19:42:37 +01:00
|
|
|
load_old_messages(opts);
|
2012-10-25 00:42:45 +02:00
|
|
|
}, 5000);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the initial message list
|
|
|
|
$(function () {
|
2012-10-26 19:30:42 +02:00
|
|
|
function load_more(messages) {
|
2013-02-14 23:48:37 +01:00
|
|
|
// If we received the initially selected message, select it on the client side,
|
|
|
|
// but not if the user has already selected another one during load.
|
2013-02-22 20:48:31 +01:00
|
|
|
//
|
|
|
|
// We fall back to the closest selected id, as the user may have removed
|
|
|
|
// a stream from the home before already
|
|
|
|
if (home_msg_list.selected_id() === -1) {
|
|
|
|
home_msg_list.select_id(initial_pointer, {then_scroll: true, use_closest: true});
|
2013-02-14 23:48:37 +01:00
|
|
|
}
|
|
|
|
|
2012-10-25 00:42:45 +02:00
|
|
|
// catch the user up
|
2012-11-01 21:31:31 +01:00
|
|
|
if (messages.length !== 1) {
|
2012-10-25 00:42:45 +02:00
|
|
|
var latest_id = messages[messages.length-1].id;
|
2013-03-14 19:42:37 +01:00
|
|
|
load_old_messages({
|
|
|
|
anchor: latest_id,
|
|
|
|
num_before: 0,
|
|
|
|
num_after: 400,
|
|
|
|
msg_list: home_msg_list,
|
|
|
|
cont: load_more
|
|
|
|
});
|
2012-10-26 00:06:40 +02:00
|
|
|
return;
|
2012-10-25 00:42:45 +02:00
|
|
|
}
|
2012-10-26 00:06:40 +02:00
|
|
|
// now start subscribing to updates
|
|
|
|
get_updates();
|
2013-03-12 22:16:37 +01:00
|
|
|
|
|
|
|
// backfill more messages after the user is idle
|
|
|
|
var backfill_batch_size = 1000;
|
|
|
|
$(document).idle({'idle': 1000*10,
|
|
|
|
'onIdle': function () {
|
|
|
|
var first_id = all_msg_list.first().id;
|
2013-03-14 19:42:37 +01:00
|
|
|
load_old_messages({
|
|
|
|
anchor: first_id,
|
|
|
|
num_before: backfill_batch_size,
|
|
|
|
num_after: 0,
|
|
|
|
msg_list: home_msg_list
|
|
|
|
});
|
2013-03-12 22:16:37 +01:00
|
|
|
}});
|
2012-10-25 00:42:45 +02:00
|
|
|
}
|
|
|
|
|
2012-11-05 23:56:27 +01:00
|
|
|
if (have_initial_messages) {
|
2013-03-14 19:42:37 +01:00
|
|
|
load_old_messages({
|
|
|
|
anchor: initial_pointer,
|
|
|
|
num_before: 200,
|
|
|
|
num_after: 200,
|
|
|
|
msg_list: home_msg_list,
|
|
|
|
cont: load_more
|
|
|
|
});
|
2012-11-05 23:56:27 +01:00
|
|
|
} else {
|
|
|
|
get_updates();
|
|
|
|
}
|
2012-10-25 00:42:45 +02:00
|
|
|
});
|
|
|
|
|
2012-11-20 23:15:50 +01:00
|
|
|
function restart_get_updates(options) {
|
2012-10-19 21:51:36 +02:00
|
|
|
if (get_updates_xhr !== undefined)
|
|
|
|
get_updates_xhr.abort();
|
|
|
|
|
|
|
|
if (get_updates_timeout !== undefined)
|
|
|
|
clearTimeout(get_updates_timeout);
|
|
|
|
|
2012-11-20 23:15:50 +01:00
|
|
|
get_updates(options);
|
2012-10-19 21:51:36 +02:00
|
|
|
}
|
|
|
|
|
2012-11-27 23:17:30 +01:00
|
|
|
function reset_load_more_status() {
|
|
|
|
load_more_enabled = true;
|
|
|
|
have_scrolled_away_from_top = true;
|
2013-03-15 20:57:37 +01:00
|
|
|
ui.hide_loading_more_messages_indicator();
|
2012-11-27 23:17:30 +01:00
|
|
|
}
|
|
|
|
|
2013-02-12 20:01:24 +01:00
|
|
|
function load_more_messages(msg_list) {
|
2012-11-06 19:17:38 +01:00
|
|
|
var batch_size = 400;
|
2013-02-12 20:01:24 +01:00
|
|
|
var oldest_message_id;
|
2012-11-27 23:17:30 +01:00
|
|
|
if (!load_more_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
2013-01-16 19:50:18 +01:00
|
|
|
ui.show_loading_more_messages_indicator();
|
2012-11-27 23:17:30 +01:00
|
|
|
load_more_enabled = false;
|
2013-02-12 20:01:24 +01:00
|
|
|
if (msg_list.first() === undefined) {
|
|
|
|
oldest_message_id = initial_pointer;
|
|
|
|
} else {
|
|
|
|
oldest_message_id = msg_list.first().id;
|
2012-12-04 20:04:36 +01:00
|
|
|
}
|
2013-03-14 19:42:37 +01:00
|
|
|
load_old_messages({
|
|
|
|
anchor: oldest_message_id,
|
|
|
|
num_before: batch_size,
|
|
|
|
num_after: 0,
|
|
|
|
msg_list: msg_list,
|
|
|
|
cont: function (messages) {
|
|
|
|
ui.hide_loading_more_messages_indicator();
|
|
|
|
if (messages.length === batch_size + 1) {
|
|
|
|
load_more_enabled = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
for_narrow: narrow.active()
|
|
|
|
});
|
2012-10-19 22:28:57 +02:00
|
|
|
}
|
|
|
|
|
2012-10-10 20:21:22 +02:00
|
|
|
var watchdog_time = $.now();
|
2012-11-20 21:37:31 +01:00
|
|
|
setInterval(function () {
|
2012-10-10 20:21:22 +02:00
|
|
|
var new_time = $.now();
|
|
|
|
if ((new_time - watchdog_time) > 20000) { // 20 seconds.
|
|
|
|
// Our app's JS wasn't running (the machine was probably
|
|
|
|
// asleep). Now that we're running again, immediately poll for
|
|
|
|
// new updates.
|
2012-11-27 21:06:17 +01:00
|
|
|
get_updates_failures = 0;
|
2012-11-27 21:13:32 +01:00
|
|
|
restart_get_updates({dont_block: true});
|
2012-10-10 20:21:22 +02:00
|
|
|
}
|
|
|
|
watchdog_time = new_time;
|
|
|
|
}, 5000);
|
|
|
|
|
2012-10-05 19:28:10 +02:00
|
|
|
function at_top_of_viewport() {
|
2012-10-16 04:07:52 +02:00
|
|
|
return (viewport.scrollTop() === 0);
|
2012-10-05 19:28:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function at_bottom_of_viewport() {
|
2012-11-01 19:03:48 +01:00
|
|
|
// outerHeight(true): Include margin
|
2012-10-05 19:28:10 +02:00
|
|
|
return (viewport.scrollTop() + viewport.height() >= $("#main_div").outerHeight(true));
|
|
|
|
}
|
|
|
|
|
2012-09-26 20:44:41 +02:00
|
|
|
function keep_pointer_in_view() {
|
2012-10-05 19:28:10 +02:00
|
|
|
var candidate;
|
2013-02-14 23:48:37 +01:00
|
|
|
var next_row = current_msg_list.selected_row();
|
2012-11-30 03:49:18 +01:00
|
|
|
|
|
|
|
if (disable_pointer_movement) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-14 23:48:37 +01:00
|
|
|
if (next_row.length === 0)
|
2012-10-23 22:50:22 +02:00
|
|
|
return;
|
2012-09-26 20:44:41 +02:00
|
|
|
|
2012-11-24 08:03:17 +01:00
|
|
|
function adjust(past_threshold, at_end, advance) {
|
2013-02-14 23:48:37 +01:00
|
|
|
if (!past_threshold(next_row) || at_end())
|
2012-11-24 08:03:17 +01:00
|
|
|
return false; // try other side
|
2013-02-14 23:48:37 +01:00
|
|
|
while (past_threshold(next_row)) {
|
|
|
|
candidate = advance(next_row);
|
2012-11-24 08:03:17 +01:00
|
|
|
if (candidate.length === 0)
|
2012-10-05 19:28:10 +02:00
|
|
|
break;
|
2013-02-14 23:48:37 +01:00
|
|
|
next_row = candidate;
|
2012-09-26 20:44:41 +02:00
|
|
|
}
|
2012-11-24 08:03:17 +01:00
|
|
|
return true;
|
2012-09-26 20:44:41 +02:00
|
|
|
}
|
2012-11-24 08:03:17 +01:00
|
|
|
|
|
|
|
if (! adjust(above_view_threshold, at_top_of_viewport, rows.next_visible))
|
|
|
|
adjust(below_view_threshold, at_bottom_of_viewport, rows.prev_visible);
|
|
|
|
|
2013-02-20 20:49:49 +01:00
|
|
|
current_msg_list.select_id(rows.id(next_row));
|
2012-10-10 20:20:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// The idea here is when you've scrolled to the very
|
|
|
|
// bottom of the page, e.g., the scroll handler isn't
|
|
|
|
// going to fire anymore. But if I continue to use
|
|
|
|
// the scrollwheel, the selection should advance until
|
|
|
|
// I'm at the very top or the very bottom of the page.
|
2012-10-16 18:16:26 +02:00
|
|
|
function move_pointer_at_page_top_and_bottom(delta) {
|
|
|
|
if (delta !== 0 && (at_top_of_viewport() || at_bottom_of_viewport())) {
|
2013-02-14 23:48:37 +01:00
|
|
|
var next_row = current_msg_list.selected_row();
|
2012-10-16 18:16:26 +02:00
|
|
|
if (delta > 0) {
|
|
|
|
// Scrolling up (want older messages)
|
2013-02-14 23:48:37 +01:00
|
|
|
next_row = rows.prev_visible(next_row);
|
2012-10-16 18:16:26 +02:00
|
|
|
} else {
|
|
|
|
// We're scrolling down (we want more recent messages)
|
2013-02-14 23:48:37 +01:00
|
|
|
next_row = rows.next_visible(next_row);
|
2012-10-16 18:16:26 +02:00
|
|
|
}
|
2013-02-14 23:48:37 +01:00
|
|
|
if (next_row.length !== 0) {
|
2013-02-20 20:49:49 +01:00
|
|
|
current_msg_list.select_id(rows.id(next_row));
|
2012-10-16 18:16:26 +02:00
|
|
|
}
|
2012-09-26 20:44:41 +02:00
|
|
|
}
|
|
|
|
}
|
2013-02-20 04:19:18 +01:00
|
|
|
|
2013-03-07 20:07:41 +01:00
|
|
|
function fast_forward_pointer(btn) {
|
|
|
|
btn = $(btn);
|
|
|
|
|
|
|
|
btn.attr('disabled', 'disabled');
|
|
|
|
|
|
|
|
btn.after($("<div>").addClass("alert alert-info settings_committed")
|
|
|
|
.text("Working…"));
|
|
|
|
|
2013-02-20 04:19:18 +01:00
|
|
|
$.ajax({
|
|
|
|
type: 'POST',
|
|
|
|
url: '/json/get_profile',
|
|
|
|
data: {email: email},
|
|
|
|
dataType: 'json',
|
|
|
|
success: function (data) {
|
2013-03-15 20:07:38 +01:00
|
|
|
mark_all_as_read(function () {
|
|
|
|
furthest_read = data.max_message_id;
|
|
|
|
send_pointer_update();
|
|
|
|
ui.change_tab_to('#home');
|
|
|
|
reload.initiate({immediate: true});
|
|
|
|
});
|
2013-02-20 04:19:18 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2013-03-05 23:45:02 +01:00
|
|
|
|
|
|
|
function logout() {
|
|
|
|
$('#logout_form').submit();
|
|
|
|
}
|