2012-10-09 23:48:41 +02:00
|
|
|
var message_array = [];
|
|
|
|
var message_dict = {};
|
2012-10-12 19:14:59 +02:00
|
|
|
var subject_dict = {};
|
2012-10-13 00:17:48 +02:00
|
|
|
var people_hash = {};
|
2012-09-22 01:11:54 +02:00
|
|
|
|
2012-10-16 04:07:52 +02:00
|
|
|
var viewport = $(window);
|
2012-10-15 19:24:34 +02:00
|
|
|
|
2012-10-17 23:45:03 +02:00
|
|
|
var selected_message_id = -1; /* to be filled in on document.ready */
|
2012-10-18 20:55:41 +02:00
|
|
|
var selected_message; // = rows.get(selected_message_id)
|
2012-10-16 22:06:03 +02:00
|
|
|
var get_updates_params = {
|
2012-10-25 23:32:20 +02:00
|
|
|
last: -1,
|
2012-10-17 23:10:34 +02:00
|
|
|
pointer: -1,
|
2012-10-17 23:45:03 +02:00
|
|
|
failures: 0,
|
|
|
|
server_generation: -1, /* to be filled in on document.ready */
|
2012-10-26 00:07:31 +02:00
|
|
|
reload_pending: 0
|
2012-10-17 23:45:03 +02:00
|
|
|
};
|
|
|
|
|
2012-09-06 19:54:29 +02:00
|
|
|
$(function () {
|
2012-10-13 00:17:48 +02:00
|
|
|
$.each(people_list, function (idx, person) {
|
|
|
|
people_hash[person.email] = 1;
|
|
|
|
});
|
2012-09-21 19:32:01 +02:00
|
|
|
});
|
|
|
|
|
2012-10-01 22:41:53 +02:00
|
|
|
// The "message groups", i.e. blocks of messages collapsed by recipient.
|
|
|
|
// Each message table has a list of lists.
|
|
|
|
var message_groups = {
|
|
|
|
zhome: [],
|
|
|
|
zfilt: []
|
|
|
|
};
|
|
|
|
|
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.
|
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.
|
2012-10-10 16:17:58 +02:00
|
|
|
viewport.scrollTop(selected_message.offset().top - viewport.height() / 2);
|
2012-10-10 16:35:48 +02:00
|
|
|
} else if (below_view_threshold(message)) {
|
2012-10-10 16:17:58 +02:00
|
|
|
viewport.scrollTop(selected_message.offset().top - viewport.height() / 5);
|
2012-10-05 16:31:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function scroll_to_selected() {
|
2012-10-23 22:50:22 +02:00
|
|
|
if (selected_message && (selected_message.length !== 0))
|
|
|
|
recenter_view(selected_message);
|
2012-09-07 20:44:55 +02:00
|
|
|
}
|
|
|
|
|
2012-10-27 02:31:46 +02:00
|
|
|
function get_huddle_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) {
|
|
|
|
// huddle 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;
|
2012-10-10 16:35:48 +02:00
|
|
|
message = message_dict[selected_message_id];
|
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
|
|
|
|
|
|
|
var huddle_recipient = message.reply_to;
|
2012-10-10 16:35:48 +02:00
|
|
|
if (reply_type === "personal" && message.type === "huddle") {
|
2012-10-09 15:51:12 +02:00
|
|
|
// reply_to for huddle messages is the whole huddle, so for
|
|
|
|
// personals replies we need to set the the huddle recipient
|
|
|
|
// to just the sender
|
2012-10-18 20:17:55 +02:00
|
|
|
huddle_recipient = message.sender_email;
|
2012-10-09 15:51:12 +02:00
|
|
|
}
|
2012-10-18 20:17:55 +02:00
|
|
|
msg_type = reply_type;
|
|
|
|
if (msg_type === undefined) {
|
|
|
|
msg_type = message.type;
|
2012-09-12 16:23:55 +02:00
|
|
|
}
|
2012-10-18 20:17:55 +02:00
|
|
|
if (msg_type === "huddle") {
|
2012-10-09 15:51:12 +02:00
|
|
|
// Huddle messages use the personals compose box
|
2012-10-18 20:17:55 +02:00
|
|
|
msg_type = "personal";
|
2012-10-09 15:51:12 +02:00
|
|
|
}
|
2012-10-19 21:59:03 +02:00
|
|
|
compose.start(msg_type, {'stream': stream, 'subject': subject,
|
|
|
|
'huddle_recipient': huddle_recipient});
|
2012-09-12 16:23:55 +02:00
|
|
|
}
|
|
|
|
|
2012-09-21 23:29:30 +02:00
|
|
|
// Called by mouseover etc.
|
2012-10-22 19:05:06 +02:00
|
|
|
function select_message_by_id(message_id, opts) {
|
2012-10-23 22:48:20 +02:00
|
|
|
return select_message(rows.get(message_id), opts);
|
2012-09-13 16:58:29 +02:00
|
|
|
}
|
|
|
|
|
2012-10-22 18:28:40 +02:00
|
|
|
var last_message_id_sent = -1;
|
|
|
|
var message_id_to_send = -1;
|
|
|
|
// We only send pointer updates every second to avoid hammering the
|
|
|
|
// server
|
|
|
|
function send_pointer_update() {
|
|
|
|
if (message_id_to_send !== last_message_id_sent) {
|
|
|
|
$.post("json/update_pointer", {pointer: message_id_to_send});
|
|
|
|
last_message_id_sent = message_id_to_send;
|
|
|
|
}
|
|
|
|
setTimeout(send_pointer_update, 1000);
|
|
|
|
}
|
|
|
|
|
2012-10-27 04:48:21 +02:00
|
|
|
$(function () {
|
|
|
|
setTimeout(send_pointer_update, 1000);
|
|
|
|
});
|
2012-10-22 18:28:40 +02:00
|
|
|
|
2012-10-22 19:10:09 +02:00
|
|
|
function update_selected_message(message, opts) {
|
2012-10-27 02:58:21 +02:00
|
|
|
opts = $.extend({}, {
|
|
|
|
update_server: true,
|
|
|
|
for_narrow: narrow.active()
|
|
|
|
}, opts);
|
|
|
|
|
|
|
|
var cls = opts.for_narrow ? 'narrowed_selected_message' : 'selected_message';
|
|
|
|
$('.' + cls).removeClass(cls);
|
|
|
|
message.addClass(cls);
|
2012-09-24 19:50:09 +02:00
|
|
|
|
2012-10-18 20:55:41 +02:00
|
|
|
var new_selected_id = rows.id(message);
|
2012-10-22 19:10:09 +02:00
|
|
|
if (opts.update_server && !narrow.active()
|
|
|
|
&& new_selected_id !== message_id_to_send)
|
|
|
|
{
|
2012-09-24 19:50:09 +02:00
|
|
|
// Narrowing is a temporary view on top of the home view and
|
|
|
|
// doesn't permanently affect where you are.
|
|
|
|
//
|
|
|
|
// We also don't want to post if there's no effective change.
|
2012-10-22 18:28:40 +02:00
|
|
|
message_id_to_send = new_selected_id;
|
2012-09-24 19:50:09 +02:00
|
|
|
}
|
2012-10-10 00:09:25 +02:00
|
|
|
selected_message_id = new_selected_id;
|
2012-10-10 16:35:48 +02:00
|
|
|
selected_message = message;
|
2012-09-24 19:50:09 +02:00
|
|
|
}
|
|
|
|
|
2012-10-22 19:05:06 +02:00
|
|
|
function select_message(next_message, opts) {
|
2012-10-22 19:10:09 +02:00
|
|
|
opts = $.extend({}, {then_scroll: false, update_server: true}, opts);
|
2012-09-06 19:32:02 +02:00
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
/* If the message exists but is hidden, try to find the next visible one. */
|
2012-10-23 22:17:57 +02:00
|
|
|
if (next_message.is(':hidden')) {
|
2012-10-18 20:55:41 +02:00
|
|
|
next_message = rows.next_visible(next_message);
|
2012-09-06 19:32:02 +02:00
|
|
|
}
|
|
|
|
|
2012-10-23 22:19:28 +02:00
|
|
|
/* Fall back to the last visible message. */
|
2012-10-09 23:45:41 +02:00
|
|
|
if (next_message.length === 0) {
|
2012-10-23 22:19:28 +02:00
|
|
|
next_message = rows.last_visible();
|
2012-10-23 22:17:57 +02:00
|
|
|
if (next_message.length === 0) {
|
|
|
|
// There are no messages!
|
|
|
|
return false;
|
|
|
|
}
|
2012-09-17 16:42:45 +02:00
|
|
|
}
|
2012-09-06 19:32:02 +02:00
|
|
|
|
2012-10-26 17:44:22 +02:00
|
|
|
if (next_message.id !== selected_message_id) {
|
|
|
|
update_selected_message(next_message, opts);
|
|
|
|
}
|
2012-09-05 16:56:10 +02:00
|
|
|
|
2012-10-22 19:05:06 +02:00
|
|
|
if (opts.then_scroll) {
|
2012-10-09 23:45:41 +02:00
|
|
|
recenter_view(next_message);
|
2012-08-31 17:10:19 +02:00
|
|
|
}
|
2012-10-23 22:48:20 +02:00
|
|
|
|
|
|
|
return true;
|
2012-08-31 17:10:19 +02:00
|
|
|
}
|
|
|
|
|
2012-09-24 20:29:35 +02:00
|
|
|
function same_recipient(a, b) {
|
|
|
|
if ((a === undefined) || (b === undefined))
|
|
|
|
return false;
|
|
|
|
if (a.type !== b.type)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
switch (a.type) {
|
|
|
|
case 'huddle':
|
|
|
|
return a.recipient_id === b.recipient_id;
|
|
|
|
case 'personal':
|
|
|
|
return a.reply_to === b.reply_to;
|
2012-10-10 22:57:21 +02:00
|
|
|
case 'stream':
|
2012-09-24 20:29:35 +02:00
|
|
|
return (a.recipient_id === b.recipient_id) &&
|
2012-11-01 19:11:21 +01:00
|
|
|
(a.subject === b.subject);
|
2012-09-24 20:29:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// should never get here
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-09-24 21:26:50 +02:00
|
|
|
function same_sender(a, b) {
|
|
|
|
return ((a !== undefined) && (b !== undefined) &&
|
|
|
|
(a.sender_email === b.sender_email));
|
|
|
|
}
|
|
|
|
|
2012-10-01 22:41:53 +02:00
|
|
|
function clear_table(table_name) {
|
2012-10-27 03:03:06 +02:00
|
|
|
rows.get_table(table_name).empty();
|
2012-10-01 22:41:53 +02:00
|
|
|
message_groups[table_name] = [];
|
|
|
|
}
|
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
function add_display_time(message, prev) {
|
2012-10-05 20:44:29 +02:00
|
|
|
var two_digits = function (x) { return ('0' + x).slice(-2); };
|
2012-10-10 16:35:48 +02:00
|
|
|
var time = new XDate(message.timestamp * 1000);
|
|
|
|
var include_date = message.include_recipient;
|
2012-10-05 20:44:29 +02:00
|
|
|
|
|
|
|
if (prev !== undefined) {
|
|
|
|
var prev_time = new XDate(prev.timestamp * 1000);
|
|
|
|
if (time.toDateString() !== prev_time.toDateString()) {
|
|
|
|
include_date = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-01 19:24:51 +01:00
|
|
|
// NB: timestr is HTML, inserted into the document without escaping.
|
2012-10-05 20:44:29 +02:00
|
|
|
if (include_date) {
|
2012-10-10 16:35:48 +02:00
|
|
|
message.timestr = time.toString("MMM dd") + " " +
|
2012-10-05 20:44:29 +02:00
|
|
|
time.toString("HH:mm");
|
|
|
|
} else {
|
2012-10-10 16:35:48 +02:00
|
|
|
message.timestr = time.toString("HH:mm");
|
2012-10-05 20:44:29 +02:00
|
|
|
}
|
2012-11-01 19:07:03 +01:00
|
|
|
// Rather than using time.toLocaleString(), which varies by
|
|
|
|
// browser, just do our own hardcoded formatting.
|
|
|
|
message.full_date_str = time.toDateString() + " " + time.toTimeString();
|
2012-10-05 20:44:29 +02:00
|
|
|
}
|
|
|
|
|
2012-10-10 16:29:48 +02:00
|
|
|
function add_to_table(messages, table_name, filter_function, where) {
|
|
|
|
if (messages.length === 0)
|
2012-10-02 22:43:36 +02:00
|
|
|
return;
|
|
|
|
|
2012-10-27 03:03:06 +02:00
|
|
|
var table = rows.get_table(table_name);
|
2012-10-10 16:29:48 +02:00
|
|
|
var messages_to_render = [];
|
2012-09-24 22:36:09 +02:00
|
|
|
var ids_where_next_is_same_sender = [];
|
2012-09-28 19:48:04 +02:00
|
|
|
var prev;
|
|
|
|
|
2012-10-01 22:41:53 +02:00
|
|
|
var current_group = [];
|
|
|
|
var new_message_groups = [];
|
|
|
|
|
2012-10-03 00:17:24 +02:00
|
|
|
if (where === 'top') {
|
|
|
|
// Assumption: We never get a 'top' update as the first update.
|
|
|
|
|
|
|
|
// Delete the current top message group, and add it back in with these
|
|
|
|
// messages, in order to collapse properly.
|
|
|
|
//
|
|
|
|
// This means we redraw the entire view on each update when narrowed by
|
2012-10-11 00:01:39 +02:00
|
|
|
// subject, which could be a problem down the line. For now we hope
|
|
|
|
// that subject views will not be very big.
|
2012-10-03 00:17:24 +02:00
|
|
|
|
|
|
|
var top_group = message_groups[table_name][0];
|
|
|
|
var top_messages = [];
|
|
|
|
$.each(top_group, function (index, id) {
|
2012-10-18 20:55:41 +02:00
|
|
|
rows.get(id, table_name).remove();
|
2012-10-09 23:48:41 +02:00
|
|
|
top_messages.push(message_dict[id]);
|
2012-10-03 00:17:24 +02:00
|
|
|
});
|
2012-10-10 16:29:48 +02:00
|
|
|
messages = messages.concat(top_messages);
|
2012-10-03 00:17:24 +02:00
|
|
|
|
|
|
|
// Delete the leftover recipient label.
|
|
|
|
table.find('.recipient_row:first').remove();
|
|
|
|
} else {
|
2012-10-09 23:48:41 +02:00
|
|
|
prev = message_dict[table.find('tr:last-child').attr('zid')];
|
2012-10-03 00:17:24 +02:00
|
|
|
}
|
2012-09-20 20:33:57 +02:00
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
$.each(messages, function (index, message) {
|
|
|
|
if (! filter_function(message))
|
2012-09-24 21:21:23 +02:00
|
|
|
return;
|
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
message.include_recipient = false;
|
|
|
|
message.include_bookend = false;
|
|
|
|
if (same_recipient(prev, message)) {
|
|
|
|
current_group.push(message.id);
|
2012-10-01 22:41:53 +02:00
|
|
|
} else {
|
|
|
|
if (current_group.length > 0)
|
|
|
|
new_message_groups.push(current_group);
|
2012-10-10 16:35:48 +02:00
|
|
|
current_group = [message.id];
|
2012-10-01 22:41:53 +02:00
|
|
|
|
2012-09-24 22:36:09 +02:00
|
|
|
// Add a space to the table, but not for the first element.
|
2012-10-10 16:35:48 +02:00
|
|
|
message.include_recipient = true;
|
|
|
|
message.include_bookend = (prev !== undefined);
|
2012-09-19 19:15:12 +02:00
|
|
|
}
|
2012-09-13 22:00:11 +02:00
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
message.include_sender = true;
|
|
|
|
if (!message.include_recipient &&
|
|
|
|
same_sender(prev, message) &&
|
|
|
|
(Math.abs(message.timestamp - prev.timestamp) < 60*10)) {
|
|
|
|
message.include_sender = false;
|
2012-09-24 22:36:09 +02:00
|
|
|
ids_where_next_is_same_sender.push(prev.id);
|
2012-09-24 21:21:23 +02:00
|
|
|
}
|
2012-09-19 19:15:12 +02:00
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
add_display_time(message, prev);
|
2012-10-05 20:44:29 +02:00
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
message.dom_id = table_name + message.id;
|
2012-09-13 22:00:11 +02:00
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
messages_to_render.push(message);
|
|
|
|
prev = message;
|
2012-09-24 22:36:09 +02:00
|
|
|
});
|
|
|
|
|
2012-10-01 22:41:53 +02:00
|
|
|
if (current_group.length > 0)
|
|
|
|
new_message_groups.push(current_group);
|
|
|
|
|
|
|
|
if (where === 'top') {
|
|
|
|
message_groups[table_name] = new_message_groups.concat(message_groups[table_name]);
|
|
|
|
} else {
|
|
|
|
message_groups[table_name] = message_groups[table_name].concat(new_message_groups);
|
|
|
|
}
|
|
|
|
|
2012-10-10 16:32:59 +02:00
|
|
|
var rendered = templates.message({
|
2012-10-10 16:29:48 +02:00
|
|
|
messages: messages_to_render,
|
2012-10-03 23:22:51 +02:00
|
|
|
include_layout_row: (table.find('tr:first').length === 0)
|
2012-10-02 23:19:33 +02:00
|
|
|
});
|
2012-09-28 19:48:04 +02:00
|
|
|
|
2012-10-02 23:19:33 +02:00
|
|
|
if (where === 'top') {
|
|
|
|
table.find('.ztable_layout_row').after(rendered);
|
|
|
|
} else {
|
2012-09-28 19:48:04 +02:00
|
|
|
table.append(rendered);
|
2012-10-02 23:19:33 +02:00
|
|
|
}
|
2012-09-20 20:33:57 +02:00
|
|
|
|
2012-10-10 16:35:48 +02:00
|
|
|
$.each(messages_to_render, function (index, message) {
|
2012-10-18 20:55:41 +02:00
|
|
|
var row = rows.get(message.id, table_name);
|
2012-10-10 16:35:48 +02:00
|
|
|
register_onclick(row, message.id);
|
2012-10-04 22:46:28 +02:00
|
|
|
|
2012-10-10 00:02:28 +02:00
|
|
|
row.find('.message_content a').each(function (index, link) {
|
2012-10-04 22:46:28 +02:00
|
|
|
link = $(link);
|
|
|
|
link.attr('target', '_blank')
|
|
|
|
.attr('title', link.attr('href'))
|
|
|
|
.attr('onclick', 'event.cancelBubble = true;'); // would a closure work here?
|
|
|
|
});
|
2012-09-24 22:36:09 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$.each(ids_where_next_is_same_sender, function (index, id) {
|
2012-10-18 20:55:41 +02:00
|
|
|
rows.get(id, table_name).find('.messagebox').addClass("next_is_same_sender");
|
2012-09-24 21:21:23 +02:00
|
|
|
});
|
2012-09-13 22:00:11 +02:00
|
|
|
}
|
|
|
|
|
2012-10-10 16:34:42 +02:00
|
|
|
function add_message_metadata(dummy, message) {
|
2012-10-25 23:32:20 +02:00
|
|
|
get_updates_params.last = Math.max(get_updates_params.last, message.id);
|
|
|
|
|
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-01 17:03:45 +01:00
|
|
|
if ($.inArray(message.subject, subject_dict[message.display_recipient]) === -1) {
|
|
|
|
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;
|
|
|
|
|
|
|
|
case 'huddle':
|
2012-10-10 16:34:42 +02:00
|
|
|
message.is_huddle = true;
|
2012-10-27 02:31:46 +02:00
|
|
|
message.reply_to = get_huddle_recipient(message, 'email');
|
|
|
|
message.display_reply_to = get_huddle_recipient(message, 'full_name');
|
2012-10-12 17:26:04 +02:00
|
|
|
|
|
|
|
involved_people = message.display_recipient;
|
2012-09-26 19:57:19 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'personal':
|
2012-10-10 16:34:42 +02:00
|
|
|
message.is_personal = true;
|
2012-09-04 20:31:23 +02:00
|
|
|
|
2012-10-10 16:34:42 +02:00
|
|
|
if (message.sender_email === email) { // that is, we sent the original message
|
2012-10-12 16:47:01 +02:00
|
|
|
message.reply_to = message.display_recipient.email;
|
2012-10-13 00:33:52 +02:00
|
|
|
message.display_reply_to = message.display_recipient.full_name;
|
2012-10-09 15:42:30 +02:00
|
|
|
} else {
|
2012-10-10 16:34:42 +02:00
|
|
|
message.reply_to = message.sender_email;
|
2012-10-13 00:37:34 +02:00
|
|
|
message.display_reply_to = message.sender_full_name;
|
2012-09-04 20:31:23 +02:00
|
|
|
}
|
2012-09-20 21:33:45 +02:00
|
|
|
|
2012-10-12 17:26:04 +02:00
|
|
|
involved_people = [message.display_recipient,
|
|
|
|
{'email': message.sender_email,
|
2012-10-13 00:32:59 +02:00
|
|
|
'full_name': message.sender_full_name}];
|
2012-10-12 17:26:04 +02:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
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"
|
|
|
|
if (! Object.prototype.hasOwnProperty.call(people_hash, person.email)) {
|
|
|
|
people_hash[person.email] = 1;
|
|
|
|
people_list.push(person);
|
2012-11-01 18:01:33 +01:00
|
|
|
composebox_typeahead.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-10-10 16:34:42 +02:00
|
|
|
message_dict[message.id] = message;
|
2012-08-29 17:12:21 +02:00
|
|
|
}
|
|
|
|
|
2012-10-25 00:42:45 +02:00
|
|
|
function add_messages(messages, where) {
|
|
|
|
if (!messages)
|
2012-09-28 19:27:52 +02:00
|
|
|
return;
|
|
|
|
|
2012-09-26 22:44:38 +02:00
|
|
|
if (loading_spinner) {
|
|
|
|
loading_spinner.stop();
|
|
|
|
$('#loading_indicator').hide();
|
|
|
|
loading_spinner = undefined;
|
2012-10-19 21:59:12 +02:00
|
|
|
|
|
|
|
$('#load_more').show();
|
2012-09-26 22:44:38 +02:00
|
|
|
}
|
|
|
|
|
2012-10-25 00:42:45 +02:00
|
|
|
messages = $.grep(messages, function (elem, idx) {
|
|
|
|
return ! message_dict[elem.id];
|
|
|
|
});
|
|
|
|
$.each(messages, add_message_metadata);
|
2012-10-19 22:28:57 +02:00
|
|
|
|
2012-10-25 00:42:45 +02:00
|
|
|
if (where === 'top') {
|
|
|
|
message_array = messages.concat(message_array);
|
2012-10-01 21:02:39 +02:00
|
|
|
} else {
|
2012-10-25 00:42:45 +02:00
|
|
|
message_array = message_array.concat(messages);
|
2012-10-01 21:02:39 +02:00
|
|
|
}
|
|
|
|
|
2012-10-18 20:12:04 +02:00
|
|
|
if (narrow.active())
|
2012-10-25 00:42:45 +02:00
|
|
|
add_to_table(messages, 'zfilt', narrow.predicate(), where);
|
2012-09-25 00:42:04 +02:00
|
|
|
|
|
|
|
// Even when narrowed, add messages to the home view so they exist when we un-narrow.
|
2012-10-25 00:42:45 +02:00
|
|
|
add_to_table(messages, 'zhome', function () { return true; }, where);
|
2012-09-26 20:25:02 +02:00
|
|
|
|
2012-10-03 00:23:03 +02: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.
|
2012-10-10 00:09:25 +02:00
|
|
|
if ((selected_message_id === -1) && (message_dict.hasOwnProperty(initial_pointer))) {
|
2012-10-23 22:35:06 +02:00
|
|
|
select_message_by_id(initial_pointer,
|
|
|
|
{then_scroll: true, update_server: false});
|
2012-10-03 00:23:03 +02:00
|
|
|
}
|
2012-09-26 20:28:22 +02: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.
|
2012-10-25 00:42:45 +02:00
|
|
|
if ((where === 'top') && (selected_message_id >= 0)) {
|
2012-10-23 22:35:06 +02:00
|
|
|
select_message_by_id(selected_message_id,
|
|
|
|
{then_scroll: true, update_server: false});
|
2012-10-03 00:39:21 +02:00
|
|
|
}
|
2012-09-29 01:38:03 +02:00
|
|
|
|
2012-11-01 18:01:33 +01:00
|
|
|
if (composebox_typeahead.autocomplete_needs_update()) {
|
|
|
|
composebox_typeahead.update_autocomplete();
|
|
|
|
}
|
2012-09-24 21:21:23 +02:00
|
|
|
}
|
|
|
|
|
2012-10-10 20:21:22 +02:00
|
|
|
var get_updates_xhr;
|
|
|
|
var get_updates_timeout;
|
2012-09-27 21:44:54 +02:00
|
|
|
function get_updates() {
|
2012-10-17 23:10:34 +02:00
|
|
|
get_updates_params.pointer = selected_message_id;
|
2012-10-29 21:02:46 +01:00
|
|
|
get_updates_params.reload_pending = Number(reload.is_pending());
|
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-10-23 17:30:18 +02:00
|
|
|
timeout: 55*1000, // 55 seconds in ms -- needs to be under a
|
|
|
|
// minute to deal with crappy home wireless
|
|
|
|
// routers that kill "inactive" http connections.
|
2012-08-31 21:33:04 +02:00
|
|
|
success: function (data) {
|
2012-10-23 17:00:00 +02:00
|
|
|
if (! data) {
|
|
|
|
// The server occationally returns no data during a
|
|
|
|
// restart. Ignore those responses so the page keeps
|
|
|
|
// working
|
|
|
|
get_updates_timeout = setTimeout(get_updates, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-16 22:06:03 +02:00
|
|
|
get_updates_params.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) {
|
2012-10-25 21:30:42 +02:00
|
|
|
add_messages(data.messages, "bottom");
|
2012-10-17 23:10:34 +02:00
|
|
|
}
|
|
|
|
|
2012-10-29 19:13:26 +01:00
|
|
|
// Pointer sync is disabled for now
|
|
|
|
// if (data.new_pointer !== undefined
|
|
|
|
// && data.new_pointer !== selected_message_id)
|
|
|
|
// {
|
|
|
|
// select_message_by_id(data.new_pointer,
|
|
|
|
// {then_scroll: true, update_server: false});
|
|
|
|
// }
|
2012-10-17 23:10:34 +02:00
|
|
|
|
2012-10-10 20:21:22 +02: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) {
|
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-10-16 22:06:03 +02:00
|
|
|
get_updates_params.failures = 0;
|
2012-09-05 22:33:04 +02:00
|
|
|
$('#connection-error').hide();
|
2012-09-05 22:17:14 +02:00
|
|
|
} else {
|
2012-10-16 22:06:03 +02:00
|
|
|
get_updates_params.failures += 1;
|
2012-09-05 22:17:14 +02:00
|
|
|
}
|
|
|
|
|
2012-10-16 22:06:03 +02:00
|
|
|
if (get_updates_params.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-10-16 22:06:03 +02:00
|
|
|
var retry_sec = Math.min(90, Math.exp(get_updates_params.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
|
|
|
|
2012-10-25 00:42:45 +02:00
|
|
|
function load_old_messages(start, which, number, cont) {
|
|
|
|
$.ajax({
|
|
|
|
type: 'POST',
|
|
|
|
url: '/json/get_old_messages',
|
|
|
|
data: {start: start, which: which, number: number},
|
|
|
|
dataType: 'json',
|
|
|
|
success: function (data) {
|
|
|
|
if (! data) {
|
|
|
|
// The server occationally returns no data during a
|
|
|
|
// restart. Ignore those responses and try again
|
|
|
|
setTimeout(function () {
|
|
|
|
load_old_messages(start, which, number, cont);
|
|
|
|
}, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#connection-error').hide();
|
|
|
|
|
|
|
|
if (data.messages.length !== 0) {
|
|
|
|
var where;
|
|
|
|
if (which === "older") {
|
|
|
|
where = "top";
|
|
|
|
} else {
|
2012-11-05 20:22:35 +01:00
|
|
|
where = "bottom";
|
2012-10-25 00:42:45 +02:00
|
|
|
}
|
|
|
|
add_messages(data.messages, where);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cont !== undefined) {
|
|
|
|
cont(data.messages);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
error: function (xhr, error_type, exn) {
|
|
|
|
// We might want to be more clever here
|
|
|
|
$('#connection-error').show();
|
|
|
|
setTimeout(function () {
|
|
|
|
load_old_messages(start, which, number, cont);
|
|
|
|
}, 5000);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the initial message list
|
|
|
|
$(function () {
|
2012-10-26 19:30:42 +02:00
|
|
|
function load_more(messages) {
|
2012-10-25 00:42:45 +02:00
|
|
|
// catch the user up
|
|
|
|
if (messages.length !== 0) {
|
|
|
|
var latest_id = messages[messages.length-1].id;
|
2012-10-26 19:30:42 +02:00
|
|
|
load_old_messages(latest_id + 1, "newer", 400, 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();
|
2012-10-25 00:42:45 +02:00
|
|
|
|
2012-10-26 19:30:42 +02:00
|
|
|
// backfill more messages after the user is idle
|
|
|
|
$(document).idle({'idle': 1000*10,
|
|
|
|
'onIdle': function () {
|
|
|
|
var first_id = message_array[0].id;
|
|
|
|
load_old_messages(first_id - 1, "older", 1000);
|
|
|
|
}});
|
2012-10-25 00:42:45 +02:00
|
|
|
}
|
|
|
|
|
2012-10-26 19:30:42 +02:00
|
|
|
load_old_messages(initial_pointer, "around", 400, load_more);
|
2012-10-25 00:42:45 +02:00
|
|
|
});
|
|
|
|
|
2012-10-19 21:51:36 +02:00
|
|
|
function restart_get_updates() {
|
|
|
|
if (get_updates_xhr !== undefined)
|
|
|
|
get_updates_xhr.abort();
|
|
|
|
|
|
|
|
if (get_updates_timeout !== undefined)
|
|
|
|
clearTimeout(get_updates_timeout);
|
|
|
|
|
|
|
|
get_updates();
|
|
|
|
}
|
|
|
|
|
2012-10-19 22:28:57 +02:00
|
|
|
function load_more_messages() {
|
2012-10-25 21:45:06 +02:00
|
|
|
load_old_messages(message_array[0].id - 1, "older", 400,
|
|
|
|
function (messages) {
|
|
|
|
if (messages.length === 0) {
|
|
|
|
$('#load_more').hide();
|
|
|
|
}
|
|
|
|
});
|
2012-10-19 22:28:57 +02:00
|
|
|
}
|
|
|
|
|
2012-10-10 20:21:22 +02:00
|
|
|
var watchdog_time = $.now();
|
|
|
|
setInterval(function() {
|
|
|
|
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-10-16 22:06:03 +02:00
|
|
|
get_updates_params.failures = 0;
|
2012-10-19 21:51:36 +02:00
|
|
|
restart_get_updates();
|
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;
|
2012-10-18 20:55:41 +02:00
|
|
|
var next_message = rows.get(selected_message_id);
|
2012-10-23 22:50:22 +02:00
|
|
|
if (next_message.length === 0)
|
|
|
|
return;
|
2012-09-26 20:44:41 +02:00
|
|
|
|
2012-10-09 23:45:41 +02:00
|
|
|
if (above_view_threshold(next_message) && (!at_top_of_viewport())) {
|
|
|
|
while (above_view_threshold(next_message)) {
|
2012-10-18 20:55:41 +02:00
|
|
|
candidate = rows.next_visible(next_message);
|
2012-10-05 19:28:10 +02:00
|
|
|
if (candidate.length === 0) {
|
|
|
|
break;
|
|
|
|
} else {
|
2012-10-09 23:45:41 +02:00
|
|
|
next_message = candidate;
|
2012-10-05 19:28:10 +02:00
|
|
|
}
|
2012-09-26 20:44:41 +02:00
|
|
|
}
|
2012-10-09 23:45:41 +02:00
|
|
|
} else if (below_view_threshold(next_message) && (!at_bottom_of_viewport())) {
|
|
|
|
while (below_view_threshold(next_message)) {
|
2012-10-18 20:55:41 +02:00
|
|
|
candidate = rows.prev_visible(next_message);
|
2012-10-05 19:28:10 +02:00
|
|
|
if (candidate.length === 0) {
|
|
|
|
break;
|
|
|
|
} else {
|
2012-10-09 23:45:41 +02:00
|
|
|
next_message = candidate;
|
2012-10-05 19:28:10 +02:00
|
|
|
}
|
2012-09-26 20:44:41 +02:00
|
|
|
}
|
|
|
|
}
|
2012-10-23 16:30:53 +02:00
|
|
|
update_selected_message(next_message, {update_server: true});
|
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())) {
|
2012-10-18 20:55:41 +02:00
|
|
|
var next_message = rows.get(selected_message_id);
|
2012-10-16 18:16:26 +02:00
|
|
|
if (delta > 0) {
|
|
|
|
// Scrolling up (want older messages)
|
2012-10-18 20:55:41 +02:00
|
|
|
next_message = rows.prev_visible(next_message);
|
2012-10-16 18:16:26 +02:00
|
|
|
} else {
|
|
|
|
// We're scrolling down (we want more recent messages)
|
2012-10-18 20:55:41 +02:00
|
|
|
next_message = rows.next_visible(next_message);
|
2012-10-16 18:16:26 +02:00
|
|
|
}
|
|
|
|
if (next_message.length !== 0) {
|
|
|
|
update_selected_message(next_message);
|
|
|
|
}
|
2012-09-26 20:44:41 +02:00
|
|
|
}
|
|
|
|
}
|