2012-11-16 16:45:39 +01:00
|
|
|
var ui = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
2012-12-03 20:54:29 +01:00
|
|
|
// What, if anything, obscures the home tab?
|
|
|
|
exports.home_tab_obscured = function () {
|
|
|
|
if ($('.modal:visible').length > 0)
|
|
|
|
return 'modal';
|
|
|
|
if (! $('#home').hasClass('active'))
|
|
|
|
return 'other_tab';
|
|
|
|
return false;
|
2012-11-30 18:28:23 +01:00
|
|
|
};
|
|
|
|
|
2012-10-05 20:25:27 +02:00
|
|
|
// We want to remember how far we were scrolled on each 'tab'.
|
|
|
|
// To do so, we need to save away the old position of the
|
|
|
|
// scrollbar when we switch to a new tab (and restore it
|
|
|
|
// when we switch back.)
|
|
|
|
var scroll_positions = {};
|
2012-11-06 23:06:47 +01:00
|
|
|
var gravatar_stamp = 1;
|
2012-10-05 20:25:27 +02:00
|
|
|
|
2012-12-19 21:19:29 +01:00
|
|
|
exports.change_tab_to = function (tabname) {
|
|
|
|
$('#sidebar a[href="' + tabname + '"]').tab('show');
|
|
|
|
};
|
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
exports.focus_on = function (field_id) {
|
2012-10-09 17:41:34 +02:00
|
|
|
// Call after autocompleting on a field, to advance the focus to
|
|
|
|
// the next input field.
|
|
|
|
|
|
|
|
// Bootstrap's typeahead does not expose a callback for when an
|
|
|
|
// autocomplete selection has been made, so we have to do this
|
|
|
|
// manually.
|
|
|
|
$("#" + field_id).focus();
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
2012-10-09 17:41:34 +02:00
|
|
|
|
2012-10-03 21:44:07 +02:00
|
|
|
/* We use 'visibility' rather than 'display' and jQuery's show() / hide(),
|
|
|
|
because we want to reserve space for the email address. This avoids
|
|
|
|
things jumping around slightly when the email address is shown. */
|
|
|
|
|
2012-11-16 19:35:56 +01:00
|
|
|
var current_email_elem;
|
2012-10-03 21:44:07 +02:00
|
|
|
function hide_email() {
|
2012-11-16 19:35:56 +01:00
|
|
|
if (current_email_elem !== undefined) {
|
|
|
|
current_email_elem.addClass('invisible');
|
|
|
|
current_email_elem = undefined;
|
|
|
|
}
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
|
|
|
|
2012-11-14 23:33:13 +01:00
|
|
|
function show_email(message_row) {
|
2012-10-03 21:44:07 +02:00
|
|
|
hide_email();
|
2012-11-14 23:33:13 +01:00
|
|
|
while (!message_row.hasClass('include-sender')) {
|
|
|
|
message_row = message_row.prev();
|
2012-11-05 23:30:20 +01:00
|
|
|
}
|
2012-11-16 19:35:56 +01:00
|
|
|
var elem = message_row.find('.sender_email');
|
|
|
|
elem.removeClass('invisible');
|
|
|
|
current_email_elem = elem;
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
exports.report_message = function (response, status_box, cls) {
|
2012-10-31 23:38:34 +01:00
|
|
|
if (cls === undefined)
|
|
|
|
cls = 'alert';
|
|
|
|
|
|
|
|
status_box.removeClass(status_classes).addClass(cls)
|
|
|
|
.text(response).stop(true).fadeTo(0, 1);
|
|
|
|
status_box.show();
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
2012-10-31 23:38:34 +01:00
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
exports.report_error = function (response, xhr, status_box) {
|
2012-10-03 21:44:07 +02:00
|
|
|
if (xhr.status.toString().charAt(0) === "4") {
|
|
|
|
// Only display the error response for 4XX, where we've crafted
|
|
|
|
// a nice response.
|
|
|
|
response += ": " + $.parseJSON(xhr.responseText).msg;
|
|
|
|
}
|
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
ui.report_message(response, status_box, 'alert-error');
|
|
|
|
};
|
2012-10-03 21:44:07 +02:00
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
exports.report_success = function (response, status_box) {
|
|
|
|
ui.report_message(response, status_box, 'alert-success');
|
|
|
|
};
|
2012-10-17 20:43:20 +02:00
|
|
|
|
2012-10-03 21:44:07 +02:00
|
|
|
var clicking = false;
|
|
|
|
var mouse_moved = false;
|
|
|
|
|
2012-10-10 16:21:40 +02:00
|
|
|
function mousedown() {
|
2012-10-03 21:44:07 +02:00
|
|
|
mouse_moved = false;
|
|
|
|
clicking = true;
|
|
|
|
}
|
|
|
|
|
2012-10-10 16:21:40 +02:00
|
|
|
function mousemove() {
|
2012-10-03 21:44:07 +02:00
|
|
|
if (clicking) {
|
|
|
|
mouse_moved = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Fix "resizing window breaks in Chrome" issue.
Ironically, I think this might've bee introduced by
commit ca35321c02d5e79e4f9c439a662805c016a333ed,
'Fix "resizing window breaks in Firefox" issue'.
Basically, when the window is 776px wide according to
window.innerWidth, that's the width not including the
scrollbar. However, in Chrome, the media query seems to ignore the
width of the scrollbar, so from the CSS's perspective, the window is
actually ~766px wide, so it goes into condensed mode.
But the rest of our code doesn't, which causes the break.
A bit more on this browser-specific difference at:
http://www.456bereastreet.com/archive/201101/media_queries_viewport_width_scrollbars_and_webkit_browsers/
So the issue we have is, to match the CSS's behavior:
* In Firefox, we should be listening to window.innerWidth
* In Chrome, we should be listening to window.width
We fix this hopefully once and for all by using window.matchMedia --
aka the exact same query that the CSS itself uses. As discussed in my
last commit, this feature is unavailable in IE<10, so we provide a
potentially more fragile fallback, i.e. what we did before this
commit.
(imported from commit d8e6425b81c90c8e0fdda28e7273988c9bfd67ec)
2012-12-04 20:17:56 +01:00
|
|
|
function need_skinny_mode() {
|
|
|
|
if (window.matchMedia !== undefined) {
|
|
|
|
return window.matchMedia("(max-width: 767px)").matches;
|
|
|
|
} else {
|
|
|
|
// IE<10 doesn't support window.matchMedia, so do this
|
|
|
|
// as best we can without it.
|
|
|
|
return window.innerWidth <= 767;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-11 22:34:57 +02:00
|
|
|
function resizehandler(e) {
|
|
|
|
var sidebar = $("#sidebar");
|
|
|
|
var sidebar_nav = $(".sidebar-nav");
|
|
|
|
var composebox = $("#compose");
|
2012-10-15 21:11:16 +02:00
|
|
|
var top_statusbar = $("#top_statusbar");
|
Fix "resizing window breaks in Chrome" issue.
Ironically, I think this might've bee introduced by
commit ca35321c02d5e79e4f9c439a662805c016a333ed,
'Fix "resizing window breaks in Firefox" issue'.
Basically, when the window is 776px wide according to
window.innerWidth, that's the width not including the
scrollbar. However, in Chrome, the media query seems to ignore the
width of the scrollbar, so from the CSS's perspective, the window is
actually ~766px wide, so it goes into condensed mode.
But the rest of our code doesn't, which causes the break.
A bit more on this browser-specific difference at:
http://www.456bereastreet.com/archive/201101/media_queries_viewport_width_scrollbars_and_webkit_browsers/
So the issue we have is, to match the CSS's behavior:
* In Firefox, we should be listening to window.innerWidth
* In Chrome, we should be listening to window.width
We fix this hopefully once and for all by using window.matchMedia --
aka the exact same query that the CSS itself uses. As discussed in my
last commit, this feature is unavailable in IE<10, so we provide a
potentially more fragile fallback, i.e. what we did before this
commit.
(imported from commit d8e6425b81c90c8e0fdda28e7273988c9bfd67ec)
2012-12-04 20:17:56 +01:00
|
|
|
if (need_skinny_mode()) {
|
2012-10-11 22:34:57 +02:00
|
|
|
sidebar.removeClass('nav-stacked');
|
|
|
|
|
|
|
|
var space_taken_up_by_navbar = sidebar_nav.outerHeight(true);
|
2012-10-27 02:29:23 +02:00
|
|
|
|
|
|
|
// .visible-phone only, so doesn't need undoing
|
|
|
|
$("#nav_whitespace").height(space_taken_up_by_navbar);
|
|
|
|
|
2012-10-15 21:11:16 +02:00
|
|
|
top_statusbar.css('top', space_taken_up_by_navbar);
|
2012-10-11 22:34:57 +02:00
|
|
|
|
2012-12-05 04:55:08 +01:00
|
|
|
var desired_width;
|
|
|
|
if (exports.home_tab_obscured() === 'other_tab') {
|
|
|
|
desired_width = $("div.tab-pane.active").outerWidth();
|
|
|
|
} else {
|
|
|
|
desired_width = $("#main_div").outerWidth();
|
|
|
|
composebox.width(desired_width);
|
|
|
|
}
|
|
|
|
top_statusbar.width(desired_width);
|
|
|
|
sidebar_nav.width(desired_width);
|
2012-10-11 22:34:57 +02:00
|
|
|
} else {
|
|
|
|
sidebar.addClass('nav-stacked');
|
2012-10-15 21:11:16 +02:00
|
|
|
top_statusbar.css('top', 0);
|
|
|
|
top_statusbar.width('');
|
2012-10-12 04:49:18 +02:00
|
|
|
composebox.width('');
|
|
|
|
sidebar_nav.width('');
|
2012-10-11 22:34:57 +02:00
|
|
|
}
|
2012-10-12 06:12:46 +02:00
|
|
|
|
2013-01-09 21:29:44 +01:00
|
|
|
$("#bottom_whitespace").height(viewport.height() * 0.4);
|
2013-01-09 21:17:09 +01:00
|
|
|
$("#main_div").css('min-height', viewport.height());
|
2012-12-19 21:00:51 +01:00
|
|
|
|
2012-10-12 06:12:46 +02:00
|
|
|
// This function might run onReady (if we're in a narrow window),
|
|
|
|
// but before we've loaded in the messages; in that case, don't
|
|
|
|
// try to scroll to one.
|
|
|
|
if (selected_message_id !== -1) {
|
|
|
|
scroll_to_selected();
|
|
|
|
}
|
2012-10-11 22:34:57 +02:00
|
|
|
}
|
|
|
|
|
2012-10-16 01:29:03 +02:00
|
|
|
var old_label;
|
2012-10-16 03:33:43 +02:00
|
|
|
var is_floating_recipient_bar_showing = false;
|
|
|
|
function replace_floating_recipient_bar(desired_label) {
|
2012-11-21 20:41:57 +01:00
|
|
|
var new_label, other_label, header;
|
2012-10-16 01:29:03 +02:00
|
|
|
if (desired_label !== old_label) {
|
2012-10-17 22:03:00 +02:00
|
|
|
if (desired_label.children(".message_header_stream").length !== 0) {
|
2012-11-21 20:41:57 +01:00
|
|
|
new_label = $("#current_label_stream");
|
2012-12-03 19:49:12 +01:00
|
|
|
other_label = $("#current_label_private_message");
|
2012-11-21 20:41:57 +01:00
|
|
|
header = desired_label.children(".message_header_stream.right_part");
|
2012-12-01 04:37:52 +01:00
|
|
|
|
|
|
|
$("#current_label_stream td:first").css(
|
|
|
|
"background-color",
|
|
|
|
desired_label.children(".message_header_stream.right_part")
|
|
|
|
.css("background-color"));
|
2012-10-16 01:29:03 +02:00
|
|
|
} else {
|
2012-12-03 19:49:12 +01:00
|
|
|
new_label = $("#current_label_private_message");
|
2012-11-21 20:41:57 +01:00
|
|
|
other_label = $("#current_label_stream");
|
2012-12-03 19:49:12 +01:00
|
|
|
header = desired_label.children(".message_header_private_message.right_part");
|
2012-10-16 01:29:03 +02:00
|
|
|
}
|
2012-11-21 20:41:57 +01:00
|
|
|
new_label.find("td:last").replaceWith(header.clone());
|
|
|
|
other_label.css('display', 'none');
|
|
|
|
new_label.css('display', 'table-row');
|
|
|
|
new_label.attr("zid", desired_label.attr("zid"));
|
|
|
|
|
2012-10-16 01:29:03 +02:00
|
|
|
old_label = desired_label;
|
|
|
|
}
|
2012-10-16 03:33:43 +02:00
|
|
|
if (!is_floating_recipient_bar_showing) {
|
2012-10-16 01:29:03 +02:00
|
|
|
$(".floating_recipient_bar").css('visibility', 'visible');
|
2012-10-16 03:33:43 +02:00
|
|
|
is_floating_recipient_bar_showing = true;
|
2012-10-14 04:13:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-16 03:33:43 +02:00
|
|
|
function hide_floating_recipient_bar() {
|
|
|
|
if (is_floating_recipient_bar_showing) {
|
2012-10-16 01:29:03 +02:00
|
|
|
$(".floating_recipient_bar").css('visibility', 'hidden');
|
2012-10-16 03:33:43 +02:00
|
|
|
is_floating_recipient_bar_showing = false;
|
2012-10-16 01:29:03 +02:00
|
|
|
}
|
2012-10-14 04:13:27 +02:00
|
|
|
}
|
|
|
|
|
2012-12-07 20:52:39 +01:00
|
|
|
exports.update_floating_recipient_bar = function () {
|
2012-10-15 21:11:16 +02:00
|
|
|
var top_statusbar = $("#top_statusbar");
|
|
|
|
var top_statusbar_top = top_statusbar.offset().top;
|
2012-10-16 04:45:10 +02:00
|
|
|
var top_statusbar_bottom = top_statusbar_top + top_statusbar.outerHeight();
|
2012-10-14 04:13:27 +02:00
|
|
|
|
2012-10-15 18:21:05 +02:00
|
|
|
// Find the last message where the top of the recipient
|
2012-10-16 00:14:04 +02:00
|
|
|
// row is at least partially occluded by our box.
|
|
|
|
// Start with the pointer's current location.
|
|
|
|
var candidate = selected_message;
|
2012-10-26 21:02:04 +02:00
|
|
|
if (candidate === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
2012-10-16 00:14:04 +02:00
|
|
|
while (true) {
|
|
|
|
candidate = candidate.prev();
|
|
|
|
if (candidate.length === 0) {
|
|
|
|
// We're at the top of the page and no labels are above us.
|
2012-10-16 03:33:43 +02:00
|
|
|
hide_floating_recipient_bar();
|
2012-10-16 00:14:04 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (candidate.is(".focused_table .recipient_row")) {
|
|
|
|
if (candidate.offset().top < top_statusbar_bottom) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-10-15 18:21:05 +02:00
|
|
|
}
|
2012-10-16 00:14:04 +02:00
|
|
|
var current_label = candidate;
|
2012-10-15 18:21:05 +02:00
|
|
|
|
|
|
|
// We now know what the floating stream/subject bar should say.
|
|
|
|
// Do we show it?
|
2012-10-14 04:13:27 +02:00
|
|
|
|
2012-10-15 18:21:05 +02:00
|
|
|
// Hide if the bottom of our floating stream/subject label is not
|
|
|
|
// lower than the bottom of current_label (since that means we're
|
|
|
|
// covering up a label that already exists).
|
2012-10-15 21:11:16 +02:00
|
|
|
if (top_statusbar_bottom <=
|
2012-10-16 04:45:10 +02:00
|
|
|
(current_label.offset().top + current_label.outerHeight())) {
|
2012-10-16 03:33:43 +02:00
|
|
|
hide_floating_recipient_bar();
|
2012-10-14 04:13:27 +02:00
|
|
|
return;
|
|
|
|
}
|
2012-10-15 18:21:05 +02:00
|
|
|
|
|
|
|
// Hide if our bottom is in our bookend (or one bookend-height
|
|
|
|
// above it). This means we're not showing any useful part of the
|
2012-11-02 22:04:56 +01:00
|
|
|
// message above us, so why bother showing the label?
|
2012-10-27 03:38:31 +02:00
|
|
|
var current_bookend = current_label.nextUntil(".bookend_tr")
|
|
|
|
.andSelf()
|
|
|
|
.next(".bookend_tr:first");
|
2012-10-15 18:21:05 +02:00
|
|
|
// (The last message currently doesn't have a bookend, which is why this might be 0).
|
2012-10-27 03:38:31 +02:00
|
|
|
if (current_bookend.length > 0) {
|
2012-10-15 21:11:16 +02:00
|
|
|
if (top_statusbar_bottom >
|
2012-10-27 03:38:31 +02:00
|
|
|
(current_bookend.offset().top - current_bookend.outerHeight())) {
|
2012-10-16 03:33:43 +02:00
|
|
|
hide_floating_recipient_bar();
|
2012-10-15 18:21:05 +02:00
|
|
|
return;
|
2012-10-14 04:13:27 +02:00
|
|
|
}
|
|
|
|
}
|
2012-10-15 18:21:05 +02:00
|
|
|
|
2012-10-16 03:33:43 +02:00
|
|
|
replace_floating_recipient_bar(current_label);
|
2012-12-07 20:52:39 +01:00
|
|
|
};
|
|
|
|
|
2012-10-15 21:57:30 +02:00
|
|
|
function hack_for_floating_recipient_bar() {
|
|
|
|
// So, as of this writing, Firefox respects visibility: collapse,
|
|
|
|
// but WebKit does not (at least, my Chrome doesn't.) Instead it
|
|
|
|
// renders it basically as visibility: hidden, which leaves a
|
|
|
|
// slight gap that our messages peek through as they scroll
|
|
|
|
// by. This hack fixes this by programmatically measuring how big
|
|
|
|
// the gap is, and then moving our table up to compensate.
|
|
|
|
var gap = $("#floating_recipient_layout_row").outerHeight(true);
|
|
|
|
var floating_recipient = $(".floating_recipient");
|
|
|
|
var offset = floating_recipient.offset();
|
|
|
|
offset.top = offset.top - gap;
|
|
|
|
floating_recipient.offset(offset);
|
|
|
|
}
|
2012-10-14 04:13:27 +02:00
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
exports.show_api_key_box = function () {
|
2012-10-17 22:26:59 +02:00
|
|
|
$("#get_api_key_box").show();
|
|
|
|
$("#api_key_button_box").hide();
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
2012-10-17 22:26:59 +02:00
|
|
|
|
2012-11-16 02:30:44 +01:00
|
|
|
var current_userinfo_popover_elem;
|
|
|
|
function show_userinfo_popover(element, id) {
|
2012-10-18 20:23:57 +02:00
|
|
|
select_message_by_id(id);
|
|
|
|
var elt = $(element);
|
|
|
|
if (elt.data('popover') === undefined) {
|
2012-11-27 22:30:47 +01:00
|
|
|
var content, message = message_dict[id];
|
|
|
|
if (elt.hasClass("message_sender") || elt.hasClass("profile_picture")) {
|
|
|
|
elt.popover({placement: "bottom",
|
|
|
|
title: templates.userinfo_popover_title(message),
|
|
|
|
content: templates.userinfo_popover_content(message),
|
|
|
|
trigger: "manual"
|
|
|
|
});
|
|
|
|
} else if (elt.hasClass("message_time")) {
|
|
|
|
if (!narrow.active()) {
|
|
|
|
// Only show the time-travel popover when narrowed
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
elt.popover({placement: "bottom",
|
|
|
|
title: templates.userinfo_popover_title(message),
|
|
|
|
content: templates.timeinfo_popover_content(message),
|
|
|
|
trigger: "manual"
|
|
|
|
});
|
|
|
|
}
|
2012-10-18 20:23:57 +02:00
|
|
|
elt.popover("show");
|
2012-11-16 02:30:44 +01:00
|
|
|
current_userinfo_popover_elem = elt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
exports.hide_userinfo_popover = function () {
|
|
|
|
if (ui.userinfo_currently_popped()) {
|
2012-11-16 02:30:44 +01:00
|
|
|
current_userinfo_popover_elem.popover("destroy");
|
|
|
|
current_userinfo_popover_elem = undefined;
|
2012-10-18 20:23:57 +02:00
|
|
|
}
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
2012-10-18 20:23:57 +02:00
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
exports.userinfo_currently_popped = function () {
|
2012-11-16 02:30:44 +01:00
|
|
|
return current_userinfo_popover_elem !== undefined;
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
2012-11-16 02:30:44 +01:00
|
|
|
|
2012-11-26 16:54:25 +01:00
|
|
|
exports.safari_composebox_workaround = function () {
|
2012-10-31 20:01:21 +01:00
|
|
|
// OK, so the situation here is basically a lot of work so that
|
|
|
|
// Tab-Enter is a valid hotkey for sending a message in Safari.
|
|
|
|
// By default, Safari uses Tab only to cycle through textboxes,
|
|
|
|
// basically. Even if the tabindex is set on it, a button (like
|
|
|
|
// our submit button) will not get focus.
|
|
|
|
|
|
|
|
// HOWEVER, if you set the tabindex on a div, Safari/WebKit will
|
2012-11-26 16:54:25 +01:00
|
|
|
// respect that, and will focus it. So we make a div right
|
|
|
|
// *after* the send button -- and that's the one that gets focus
|
|
|
|
// when you press tab in Safari, in the composebox. When that div
|
|
|
|
// is selected, we instead shift focus to the Send button.
|
2012-10-31 20:01:21 +01:00
|
|
|
|
|
|
|
// This behavior is configurable in Safari, but is not on by
|
|
|
|
// default. (It is on by default in Chrome.)
|
2012-11-26 16:54:25 +01:00
|
|
|
|
|
|
|
// One unfortunate consequence of this behavior is that you can't
|
|
|
|
// get anywhere else by pressing Tab when the "Send" button has
|
|
|
|
// focus -- you're sent to the div, which immediately bounces you
|
|
|
|
// back. I think this is harmless enough, since you can always
|
|
|
|
// close the composebox.
|
|
|
|
$('#compose-send-button').focus();
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
function update_gravatars() {
|
2012-11-30 18:50:16 +01:00
|
|
|
$.each($(".gravatar-profile"), function (index, profile) {
|
2012-11-16 16:45:39 +01:00
|
|
|
$(this).attr('src', $(this).attr('src') + '?stamp=' + gravatar_stamp);
|
|
|
|
});
|
|
|
|
gravatar_stamp += 1;
|
2012-10-31 20:01:21 +01:00
|
|
|
}
|
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
function poll_for_gravatar_update(start_time, url) {
|
|
|
|
var updated = false;
|
|
|
|
|
|
|
|
$.ajax({
|
|
|
|
type: "HEAD",
|
|
|
|
url: url,
|
|
|
|
async: false,
|
|
|
|
cache: false,
|
|
|
|
success: function (resp, statusText, xhr) {
|
|
|
|
if (new Date(xhr.getResponseHeader('Last-Modified')) > start_time) {
|
|
|
|
update_gravatars();
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Give users 5 minutes to update their picture on gravatar.com,
|
|
|
|
// during which we try to auto-update their image on our site. If
|
|
|
|
// they take longer than that, we'll update when they press the
|
|
|
|
// save button.
|
|
|
|
if (!updated && (($.now() - start_time) < 1000 * 60 * 5)) {
|
2012-11-30 18:50:16 +01:00
|
|
|
setTimeout(function () {
|
2012-11-16 16:45:39 +01:00
|
|
|
poll_for_gravatar_update(start_time, url);
|
|
|
|
}, 1500);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.get_gravatar_stamp = function () {
|
|
|
|
return gravatar_stamp;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.wait_for_gravatar = function () {
|
|
|
|
poll_for_gravatar_update($.now(), $(".gravatar-profile").attr("src"));
|
|
|
|
};
|
|
|
|
|
2012-11-28 22:17:57 +01:00
|
|
|
var load_more_messages_spinner;
|
|
|
|
exports.show_load_more_messages_spinner = function () {
|
|
|
|
if (load_more_messages_spinner === undefined) {
|
|
|
|
load_more_messages_spinner = new Spinner({
|
|
|
|
lines: 8,
|
|
|
|
length: 0,
|
|
|
|
width: 9,
|
|
|
|
radius: 9,
|
|
|
|
speed: 1.25,
|
|
|
|
shadow: false
|
|
|
|
}).spin($('#load_more_messages_spinner')[0]);
|
|
|
|
$("#load_more_messages_indicator").show();
|
|
|
|
hide_floating_recipient_bar();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-11-30 18:50:16 +01:00
|
|
|
exports.hide_load_more_messages_spinner = function () {
|
2012-11-28 22:17:57 +01:00
|
|
|
if (load_more_messages_spinner) {
|
|
|
|
load_more_messages_spinner.stop();
|
|
|
|
$("#load_more_messages_indicator").hide();
|
|
|
|
load_more_messages_spinner = undefined;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-10-03 21:44:07 +02:00
|
|
|
$(function () {
|
|
|
|
// NB: This just binds to current elements, and won't bind to elements
|
|
|
|
// created after ready() is called.
|
2012-10-09 15:51:12 +02:00
|
|
|
|
2012-10-10 23:37:14 +02:00
|
|
|
// Prepare the click handler for subbing to a new stream to which
|
2012-10-10 16:20:48 +02:00
|
|
|
// you have composed a message.
|
2012-10-03 21:44:07 +02:00
|
|
|
$('#create-it').click(function () {
|
2012-10-31 23:15:27 +01:00
|
|
|
subs.subscribe_for_send(compose.stream_name(), $('#stream-dne'));
|
2012-10-03 21:44:07 +02:00
|
|
|
});
|
|
|
|
|
2012-10-10 23:37:14 +02:00
|
|
|
// Prepare the click handler for subbing to an existing stream.
|
2012-10-03 21:44:07 +02:00
|
|
|
$('#sub-it').click(function () {
|
2012-10-31 23:15:27 +01:00
|
|
|
subs.subscribe_for_send(compose.stream_name(), $('#stream-nosub'));
|
2012-10-03 21:44:07 +02:00
|
|
|
});
|
|
|
|
|
2012-11-30 18:49:42 +01:00
|
|
|
$(window).scroll($.throttle(50, function (e) {
|
2012-10-05 19:22:43 +02:00
|
|
|
if ($('#home').hasClass('active')) {
|
2012-10-10 20:20:31 +02:00
|
|
|
keep_pointer_in_view();
|
2012-12-07 20:52:39 +01:00
|
|
|
exports.update_floating_recipient_bar();
|
2012-11-27 23:17:30 +01:00
|
|
|
if (viewport.scrollTop() === 0 &&
|
|
|
|
have_scrolled_away_from_top) {
|
|
|
|
have_scrolled_away_from_top = false;
|
|
|
|
load_more_messages();
|
|
|
|
} else if (!have_scrolled_away_from_top) {
|
|
|
|
have_scrolled_away_from_top = true;
|
|
|
|
}
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
2012-11-30 18:49:42 +01:00
|
|
|
}));
|
2012-10-03 21:44:07 +02:00
|
|
|
|
2012-11-30 18:50:16 +01:00
|
|
|
var throttled_mousewheelhandler = $.throttle(50, function (e, delta) {
|
2012-10-16 03:49:51 +02:00
|
|
|
// Most of the mouse wheel's work will be handled by the
|
|
|
|
// scroll handler, but when we're at the top or bottom of the
|
|
|
|
// page, the pointer may still need to move.
|
2012-10-16 18:16:26 +02:00
|
|
|
move_pointer_at_page_top_and_bottom(delta);
|
2012-10-16 03:49:51 +02:00
|
|
|
});
|
2012-11-30 18:28:23 +01:00
|
|
|
|
|
|
|
$(window).mousewheel(function (e, delta) {
|
|
|
|
// Ignore mousewheel events if a modal is visible. It's weird if the
|
|
|
|
// user can scroll the main view by wheeling over the greyed-out area.
|
|
|
|
// Similarly, ignore events on settings page etc.
|
|
|
|
//
|
|
|
|
// We don't handle the compose box here, because it *should* work to
|
|
|
|
// select the compose box and then wheel over the message stream.
|
2012-12-03 20:55:17 +01:00
|
|
|
var obscured = exports.home_tab_obscured();
|
|
|
|
if (!obscured) {
|
2012-11-30 18:28:23 +01:00
|
|
|
throttled_mousewheelhandler(e, delta);
|
2012-12-03 20:55:17 +01:00
|
|
|
} else if (obscured === 'modal') {
|
|
|
|
// The modal itself has a handler invoked before this one (see below).
|
|
|
|
// preventDefault here so that the tab behind the modal doesn't scroll.
|
|
|
|
//
|
|
|
|
// This needs to include the events that would be ignored by throttling.
|
|
|
|
// That's why this code can't be moved into throttled_mousewheelhandler.
|
2012-11-30 18:28:23 +01:00
|
|
|
e.preventDefault();
|
|
|
|
}
|
2012-12-03 20:55:17 +01:00
|
|
|
// If on another tab, we neither handle the event nor preventDefault, allowing
|
|
|
|
// the tab to scroll normally.
|
2012-11-30 18:28:23 +01:00
|
|
|
});
|
2012-10-16 03:49:51 +02:00
|
|
|
|
2012-11-30 18:49:42 +01:00
|
|
|
$(window).resize($.throttle(50, resizehandler));
|
2012-10-11 22:34:57 +02:00
|
|
|
|
2012-11-30 18:28:23 +01:00
|
|
|
// Scrolling in modals and input boxes should not scroll the main view.
|
|
|
|
// Stop propagation in all cases. Also, ignore the event if the element
|
|
|
|
// is already at the top or bottom. Otherwise we get a new scroll event
|
|
|
|
// on the parent (?).
|
|
|
|
$('.modal-body, input, textarea').mousewheel(function (e, delta) {
|
|
|
|
var self = $(this);
|
|
|
|
var scroll = self.scrollTop();
|
|
|
|
e.stopPropagation();
|
|
|
|
if ( ((delta > 0) && (scroll <= 0))
|
|
|
|
|| ((delta < 0) && (scroll >= (this.scrollHeight - self.innerHeight())))) {
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Ignore wheel events in the compose area which weren't already handled above.
|
|
|
|
$('#compose').mousewheel(function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
|
2012-11-02 22:02:40 +01:00
|
|
|
function clear_password_change() {
|
|
|
|
// Clear the password boxes so that passwords don't linger in the DOM
|
|
|
|
// for an XSS attacker to find.
|
|
|
|
$('#old_password, #new_password, #confirm_password').val('');
|
|
|
|
}
|
|
|
|
|
2012-10-05 20:25:27 +02:00
|
|
|
$('#sidebar a[data-toggle="pill"]').on('show', function (e) {
|
|
|
|
// Save the position of our old tab away, before we switch
|
|
|
|
var old_tab = $(e.relatedTarget).attr('href');
|
|
|
|
scroll_positions[old_tab] = viewport.scrollTop();
|
|
|
|
});
|
|
|
|
$('#sidebar a[data-toggle="pill"]').on('shown', function (e) {
|
|
|
|
// Right after we show the new tab, restore its old scroll position
|
|
|
|
var target_tab = $(e.target).attr('href');
|
|
|
|
if (scroll_positions.hasOwnProperty(target_tab)) {
|
|
|
|
viewport.scrollTop(scroll_positions[target_tab]);
|
|
|
|
} else {
|
|
|
|
viewport.scrollTop(0);
|
|
|
|
}
|
2012-10-09 21:44:29 +02:00
|
|
|
|
|
|
|
// Hide all our error messages when switching tabs
|
|
|
|
$('.alert-error').hide();
|
|
|
|
$('.alert-success').hide();
|
|
|
|
$('.alert-info').hide();
|
|
|
|
$('.alert').hide();
|
2012-10-10 20:06:59 +02:00
|
|
|
|
2012-10-17 22:26:59 +02:00
|
|
|
$("#api_key_value").text("");
|
|
|
|
$("#get_api_key_box").hide();
|
|
|
|
$("#show_api_key_box").hide();
|
|
|
|
$("#api_key_button_box").show();
|
|
|
|
|
2012-11-02 22:02:40 +01:00
|
|
|
clear_password_change();
|
|
|
|
|
2012-10-10 20:06:59 +02:00
|
|
|
// Set the URL bar title to show the sub-page you're currently on.
|
2012-10-27 03:45:05 +02:00
|
|
|
var browser_url = target_tab;
|
2012-10-10 20:06:59 +02:00
|
|
|
if (browser_url === "#home") {
|
2012-12-19 21:19:29 +01:00
|
|
|
browser_url = "";
|
2012-10-10 20:06:59 +02:00
|
|
|
}
|
2012-12-19 21:19:29 +01:00
|
|
|
hashchange.changehash(browser_url);
|
2012-10-05 20:25:27 +02:00
|
|
|
});
|
|
|
|
|
2013-01-07 19:46:52 +01:00
|
|
|
// TODO: We really want to show a spinner while we're fetching
|
|
|
|
// the subs
|
2013-01-07 19:43:36 +01:00
|
|
|
$('#sidebar a[href="#subscriptions"]').on('show', subs.fetch);
|
2012-10-03 22:35:35 +02:00
|
|
|
|
|
|
|
var settings_status = $('#settings-status');
|
2012-10-17 22:26:59 +02:00
|
|
|
$("#settings-change-box form").ajaxForm({
|
2012-10-03 22:35:35 +02:00
|
|
|
dataType: 'json', // This seems to be ignored. We still get back an xhr.
|
|
|
|
success: function (resp, statusText, xhr, form) {
|
|
|
|
var message = "Updated settings!";
|
|
|
|
var result = $.parseJSON(xhr.responseText);
|
2012-11-05 23:51:27 +01:00
|
|
|
|
|
|
|
if (result.full_name !== undefined) {
|
|
|
|
$(".my_fullname").text(result.full_name);
|
2012-10-03 22:35:35 +02:00
|
|
|
}
|
2012-11-05 23:51:27 +01:00
|
|
|
update_gravatars();
|
|
|
|
|
2012-11-23 23:53:38 +01:00
|
|
|
if (result.enable_desktop_notifications !== undefined) {
|
|
|
|
desktop_notifications_enabled = result.enable_desktop_notifications;
|
|
|
|
}
|
|
|
|
|
2012-10-03 22:35:35 +02:00
|
|
|
settings_status.removeClass(status_classes)
|
|
|
|
.addClass('alert-success')
|
|
|
|
.text(message).stop(true).fadeTo(0,1);
|
|
|
|
// TODO: In theory we should auto-reload or something if
|
|
|
|
// you changed the email address or other fields that show
|
|
|
|
// up on all screens
|
|
|
|
},
|
|
|
|
error: function (xhr, error_type, xhn) {
|
|
|
|
var response = "Error changing settings";
|
|
|
|
if (xhr.status.toString().charAt(0) === "4") {
|
|
|
|
// Only display the error response for 4XX, where we've crafted
|
|
|
|
// a nice response.
|
|
|
|
response += ": " + $.parseJSON(xhr.responseText).msg;
|
|
|
|
}
|
|
|
|
settings_status.removeClass(status_classes)
|
|
|
|
.addClass('alert-error')
|
|
|
|
.text(response).stop(true).fadeTo(0,1);
|
2012-10-30 21:08:58 +01:00
|
|
|
},
|
|
|
|
complete: function (xhr, statusText) {
|
2012-11-02 22:02:40 +01:00
|
|
|
// Whether successful or not, clear the password boxes.
|
2012-10-30 21:08:58 +01:00
|
|
|
// TODO: Clear these earlier, while the request is still pending.
|
2012-11-02 22:02:40 +01:00
|
|
|
clear_password_change();
|
2012-10-03 23:22:51 +02:00
|
|
|
}
|
2012-10-03 22:35:35 +02:00
|
|
|
});
|
2012-10-12 00:48:37 +02:00
|
|
|
|
2012-10-17 22:26:59 +02:00
|
|
|
$("#get_api_key_box").hide();
|
|
|
|
$("#show_api_key_box").hide();
|
|
|
|
$("#get_api_key_box form").ajaxForm({
|
|
|
|
dataType: 'json', // This seems to be ignored. We still get back an xhr.
|
|
|
|
success: function (resp, statusText, xhr, form) {
|
|
|
|
var message = "Updated settings!";
|
|
|
|
var result = $.parseJSON(xhr.responseText);
|
|
|
|
$("#get_api_key_password").val("");
|
|
|
|
$("#api_key_value").text(result.api_key);
|
|
|
|
$("#show_api_key_box").show();
|
|
|
|
$("#get_api_key_box").hide();
|
|
|
|
settings_status.hide();
|
|
|
|
},
|
|
|
|
error: function (xhr, error_type, xhn) {
|
|
|
|
var response = "Error getting API key";
|
|
|
|
if (xhr.status.toString().charAt(0) === "4") {
|
|
|
|
// Only display the error response for 4XX, where we've crafted
|
|
|
|
// a nice response.
|
|
|
|
response += ": " + $.parseJSON(xhr.responseText).msg;
|
|
|
|
}
|
|
|
|
settings_status.removeClass(status_classes)
|
|
|
|
.addClass('alert-error')
|
|
|
|
.text(response).stop(true).fadeTo(0,1);
|
|
|
|
$("#show_api_key_box").hide();
|
|
|
|
$("#get_api_key_box").show();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2012-10-12 00:48:37 +02:00
|
|
|
// A little hackish, because it doesn't seem to totally get us
|
2012-10-16 03:33:43 +02:00
|
|
|
// the exact right width for the top_statusbar and compose box,
|
2012-10-12 00:48:37 +02:00
|
|
|
// but, close enough for now.
|
|
|
|
resizehandler();
|
2012-10-15 21:57:30 +02:00
|
|
|
hack_for_floating_recipient_bar();
|
2012-10-12 17:26:04 +02:00
|
|
|
|
2012-11-26 23:28:11 +01:00
|
|
|
typeahead_helper.update_all_recipients(people_list);
|
2012-11-01 18:01:33 +01:00
|
|
|
composebox_typeahead.initialize();
|
2012-11-14 22:12:21 +01:00
|
|
|
search.initialize();
|
2012-11-23 23:53:38 +01:00
|
|
|
notifications.initialize();
|
2012-12-07 20:52:39 +01:00
|
|
|
hashchange.initialize();
|
2012-12-05 22:48:15 +01:00
|
|
|
invite.initialize();
|
2012-10-18 20:23:57 +02:00
|
|
|
|
2012-11-30 18:50:16 +01:00
|
|
|
$("body").bind('click', function () {
|
2012-11-16 16:45:39 +01:00
|
|
|
ui.hide_userinfo_popover();
|
2012-10-18 20:23:57 +02:00
|
|
|
});
|
2012-11-14 23:33:13 +01:00
|
|
|
|
|
|
|
$("#main_div").on("click", ".messagebox", function (e) {
|
|
|
|
if ($(e.target).is("a")) {
|
|
|
|
// If this click came from a hyperlink, don't trigger the
|
|
|
|
// reply action. The simple way of doing this is simply
|
|
|
|
// to call e.stopPropagation() from within the link's
|
|
|
|
// click handler.
|
|
|
|
//
|
|
|
|
// Unfortunately, on Firefox, this breaks Ctrl-click and
|
|
|
|
// Shift-click, because those are (apparently) implemented
|
|
|
|
// by adding an event listener on link clicks, and
|
|
|
|
// stopPropagation prevents them from being called.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!(clicking && mouse_moved)) {
|
|
|
|
// Was a click (not a click-and-drag).
|
|
|
|
var row = $(this).closest(".message_row");
|
|
|
|
select_message_by_id(row.attr('zid'));
|
|
|
|
respond_to_message();
|
|
|
|
}
|
|
|
|
mouse_moved = false;
|
|
|
|
clicking = false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$("#main_div").on("mousedown", ".messagebox", mousedown);
|
|
|
|
$("#main_div").on("mousemove", ".messagebox", mousemove);
|
|
|
|
$("#main_div").on("mouseover", ".messagebox", function (e) {
|
|
|
|
var row = $(this).closest(".message_row");
|
|
|
|
show_email(row);
|
|
|
|
});
|
|
|
|
|
|
|
|
$("#main_div").on("mouseout", ".messagebox", function (e) {
|
|
|
|
hide_email();
|
|
|
|
});
|
|
|
|
|
|
|
|
$("#main_div").on("mouseover", ".user_info_hover", function (e) {
|
|
|
|
var row = $(this).closest(".message_row");
|
|
|
|
show_email(row);
|
|
|
|
row.find(".sender_name").addClass("sender_hovered");
|
|
|
|
});
|
|
|
|
|
|
|
|
$("#main_div").on("mouseout", ".user_info_hover", function (e) {
|
|
|
|
var row = $(this).closest(".message_row");
|
|
|
|
hide_email();
|
|
|
|
row.find(".sender_name").removeClass("sender_hovered");
|
|
|
|
});
|
|
|
|
|
|
|
|
$("#main_div").on("click", ".user_info_hover", function (e) {
|
|
|
|
var row = $(this).closest(".message_row");
|
2012-11-16 02:30:44 +01:00
|
|
|
e.stopPropagation();
|
|
|
|
var last_popover_elem = current_userinfo_popover_elem;
|
2012-11-16 16:45:39 +01:00
|
|
|
ui.hide_userinfo_popover();
|
2012-11-16 02:30:44 +01:00
|
|
|
if (last_popover_elem === undefined
|
|
|
|
|| last_popover_elem.get()[0] !== this) {
|
|
|
|
// Only show the popover if either no popover is
|
|
|
|
// currently up or if the user has clicked on a different
|
|
|
|
// user_info_hover element than the one that deployed the
|
|
|
|
// last popover. That is, we want it to be the case that
|
|
|
|
// a user can dismiss a popover by clicking on the same
|
|
|
|
// element that caused the popover
|
|
|
|
show_userinfo_popover(this, row.attr('zid'));
|
|
|
|
}
|
2012-11-14 23:33:13 +01:00
|
|
|
});
|
|
|
|
|
2012-11-21 20:22:20 +01:00
|
|
|
$("#home").on("click", ".narrows_by_recipient", function (e) {
|
2012-11-14 23:33:13 +01:00
|
|
|
var row = $(this).closest(".recipient_row");
|
|
|
|
narrow.target(row.attr('zid'));
|
|
|
|
narrow.by_recipient();
|
|
|
|
});
|
|
|
|
|
2012-11-21 20:22:20 +01:00
|
|
|
$("#home").on("click", ".narrows_by_subject", function (e) {
|
2012-11-14 23:33:13 +01:00
|
|
|
var row = $(this).closest(".recipient_row");
|
|
|
|
narrow.target(row.attr('zid'));
|
|
|
|
narrow.by_subject();
|
|
|
|
});
|
2013-01-04 21:05:18 +01:00
|
|
|
|
|
|
|
$("#subscriptions_table").on("mouseover", ".subscription_header", function (e) {
|
|
|
|
$(this).addClass("active");
|
|
|
|
});
|
|
|
|
|
|
|
|
$("#subscriptions_table").on("mouseout", ".subscription_header", function (e) {
|
|
|
|
$(this).removeClass("active");
|
|
|
|
});
|
2013-01-15 18:48:53 +01:00
|
|
|
|
|
|
|
$("#search_query").on("focus", search.focus_search);
|
|
|
|
$("#search_query").on("blur", search.update_button_visibility);
|
|
|
|
$("#search_up") .on("click", function () { search.search_button_handler(true); });
|
|
|
|
$("#search_down").on("click", function () { search.search_button_handler(false); });
|
|
|
|
$("#search_exit").on("click", search.clear_search);
|
2012-10-03 21:44:07 +02:00
|
|
|
});
|
2012-11-05 23:51:27 +01:00
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
return exports;
|
|
|
|
}());
|