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-10-10 16:20:48 +02:00
|
|
|
function register_onclick(message_row, message_id) {
|
|
|
|
message_row.find(".messagebox").click(function (e) {
|
2012-10-03 21:44:07 +02:00
|
|
|
if (!(clicking && mouse_moved)) {
|
|
|
|
// Was a click (not a click-and-drag).
|
2012-10-10 16:20:48 +02:00
|
|
|
select_message_by_id(message_id);
|
2012-10-10 16:18:51 +02:00
|
|
|
respond_to_message();
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
|
|
|
mouse_moved = false;
|
|
|
|
clicking = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2012-10-09 17:41:34 +02:00
|
|
|
function focus_on(field_id) {
|
|
|
|
// 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-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. */
|
|
|
|
|
|
|
|
function hide_email() {
|
2012-10-09 23:58:24 +02:00
|
|
|
$('.sender_email').addClass('invisible');
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
|
|
|
|
2012-10-10 16:20:48 +02:00
|
|
|
function show_email(message_id) {
|
2012-10-03 21:44:07 +02:00
|
|
|
hide_email();
|
2012-10-10 16:20:48 +02:00
|
|
|
get_message_row(message_id).find('.sender_email').removeClass('invisible');
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function report_error(response, xhr, status_box) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
status_box.removeClass(status_classes).addClass('alert-error')
|
|
|
|
.text(response).stop(true).fadeTo(0, 1);
|
|
|
|
status_box.show();
|
|
|
|
}
|
|
|
|
|
2012-10-09 20:56:11 +02:00
|
|
|
function report_success(response, status_box) {
|
|
|
|
status_box.removeClass(status_classes).addClass('alert-success')
|
|
|
|
.text(response).stop(true).fadeTo(0, 1);
|
|
|
|
status_box.show();
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-11 22:34:57 +02:00
|
|
|
function resizehandler(e) {
|
|
|
|
var viewport = $(window);
|
|
|
|
var sidebar = $("#sidebar");
|
|
|
|
var sidebar_nav = $(".sidebar-nav");
|
|
|
|
var composebox = $("#compose");
|
2012-10-15 21:11:16 +02:00
|
|
|
var top_statusbar = $("#top_statusbar");
|
2012-10-11 22:34:57 +02:00
|
|
|
if (viewport.width() <= 767) {
|
|
|
|
sidebar.removeClass('nav-stacked');
|
|
|
|
|
|
|
|
var space_taken_up_by_navbar = sidebar_nav.outerHeight(true);
|
|
|
|
$("#nav_whitespace").height(space_taken_up_by_navbar); // .visible-phone only, so doesn't need undoing
|
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
|
|
|
|
|
|
|
var message_list_width = $("#main_div").outerWidth();
|
|
|
|
composebox.width(message_list_width);
|
2012-10-15 21:11:16 +02:00
|
|
|
top_statusbar.width(message_list_width);
|
2012-10-11 22:34:57 +02:00
|
|
|
sidebar_nav.width(message_list_width);
|
|
|
|
} 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
|
|
|
|
|
|
|
// 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-03 21:44:07 +02:00
|
|
|
var autocomplete_needs_update = false;
|
|
|
|
|
|
|
|
function update_autocomplete() {
|
2012-10-10 23:26:01 +02:00
|
|
|
stream_list.sort();
|
2012-10-12 22:51:44 +02:00
|
|
|
people_list.sort(function (x, y) {
|
|
|
|
if (x.email === y.email) return 0;
|
|
|
|
if (x.email < y.email) return -1;
|
|
|
|
return 1;
|
|
|
|
});
|
2012-10-03 21:44:07 +02:00
|
|
|
|
2012-10-12 17:26:04 +02:00
|
|
|
var huddle_typeahead_list = $.map(people_list, function (person) {
|
|
|
|
return person.full_name + " <" + person.email + ">";
|
|
|
|
});
|
|
|
|
|
2012-10-12 22:51:44 +02:00
|
|
|
// We need to muck with the internal state of Typeahead in order to update
|
|
|
|
// its data source
|
|
|
|
$("#stream").data("typeahead").source = stream_list;
|
|
|
|
$("#huddle_recipient").data("typeahead").source = huddle_typeahead_list;
|
2012-10-03 21:44:07 +02:00
|
|
|
|
|
|
|
autocomplete_needs_update = false;
|
|
|
|
}
|
|
|
|
|
2012-10-15 18:21:05 +02:00
|
|
|
function replace_narrowbar(desired_label) {
|
|
|
|
if (desired_label.children(".message_newstyle_stream").length !== 0) {
|
|
|
|
$("#current_label_stream td:first").replaceWith(desired_label.children(".message_newstyle_stream").clone());
|
|
|
|
$("#current_label_stream td:last").replaceWith(desired_label.children(".message_newstyle_subject").clone());
|
2012-10-14 04:13:27 +02:00
|
|
|
$("#current_label_huddle").css('display', 'none');
|
|
|
|
$("#current_label_stream").css('display', 'table-row');
|
|
|
|
} else {
|
2012-10-15 18:21:05 +02:00
|
|
|
$("#current_label_huddle td:first").replaceWith(desired_label.children(".message_newstyle_pm").clone());
|
2012-10-14 04:13:27 +02:00
|
|
|
$("#current_label_stream").css('display', 'none');
|
|
|
|
$("#current_label_huddle").css('display', 'table-row');
|
|
|
|
}
|
2012-10-15 21:11:16 +02:00
|
|
|
$(".floating_recipient_bar").css('visibility', 'visible');
|
2012-10-14 04:13:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function hide_narrowbar() {
|
2012-10-15 21:11:16 +02:00
|
|
|
$(".floating_recipient_bar").css('visibility', 'hidden');
|
2012-10-14 04:13:27 +02:00
|
|
|
}
|
|
|
|
|
2012-10-15 21:11:16 +02:00
|
|
|
function update_floating_recipient_bar() {
|
|
|
|
var top_statusbar = $("#top_statusbar");
|
|
|
|
var top_statusbar_top = top_statusbar.offset().top;
|
|
|
|
var top_statusbar_bottom = top_statusbar_top + top_statusbar.height();
|
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;
|
|
|
|
while (true) {
|
|
|
|
candidate = candidate.prev();
|
|
|
|
if (candidate.length === 0) {
|
|
|
|
// We're at the top of the page and no labels are above us.
|
|
|
|
hide_narrowbar();
|
|
|
|
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-15 18:21:05 +02:00
|
|
|
(current_label.offset().top + current_label.height())) {
|
|
|
|
hide_narrowbar();
|
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
|
|
|
|
// message above us, so why bother showing the label?)
|
2012-10-16 00:14:04 +02:00
|
|
|
var current_label_bookend = current_label.nextUntil(".bookend_tr")
|
|
|
|
.next(".bookend_tr");
|
2012-10-15 18:21:05 +02:00
|
|
|
// (The last message currently doesn't have a bookend, which is why this might be 0).
|
|
|
|
if (current_label_bookend.length > 0) {
|
|
|
|
var my_bookend = $(current_label_bookend[0]);
|
2012-10-15 21:11:16 +02:00
|
|
|
if (top_statusbar_bottom >
|
2012-10-15 18:21:05 +02:00
|
|
|
(my_bookend.offset().top - my_bookend.height())) {
|
2012-10-14 04:13:27 +02:00
|
|
|
hide_narrowbar();
|
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
|
|
|
|
|
|
|
// If we've gotten this far, well, show it.
|
|
|
|
replace_narrowbar(current_label);
|
2012-10-14 04:13:27 +02: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-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:29:02 +02:00
|
|
|
$('#message-type-tabs a[href="#stream-message"]').on('shown', function (e) {
|
2012-10-05 23:42:58 +02:00
|
|
|
$('#personal-message').hide();
|
2012-10-10 23:29:02 +02:00
|
|
|
$('#stream-message').show();
|
2012-10-10 23:09:16 +02:00
|
|
|
$('#new_message_type').val('stream');
|
2012-10-10 22:57:02 +02:00
|
|
|
$("#send-status").removeClass(status_classes).hide();
|
2012-10-10 23:09:16 +02:00
|
|
|
focus_on("stream");
|
2012-10-03 21:44:07 +02:00
|
|
|
});
|
2012-10-09 23:46:41 +02:00
|
|
|
$('#message-type-tabs a[href="#personal-message"]').on('shown', function (e) {
|
2012-10-05 23:42:58 +02:00
|
|
|
$('#personal-message').show();
|
2012-10-10 23:29:02 +02:00
|
|
|
$('#stream-message').hide();
|
2012-10-05 23:42:58 +02:00
|
|
|
$('#new_message_type').val('personal');
|
2012-10-10 22:57:02 +02:00
|
|
|
$("#send-status").removeClass(status_classes).hide();
|
|
|
|
focus_on("huddle_recipient");
|
2012-10-03 21:44:07 +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-10 23:33:38 +02:00
|
|
|
sub_from_home(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-10 23:33:38 +02:00
|
|
|
sub_from_home(compose_stream_name(), $('#stream-nosub'));
|
2012-10-03 21:44:07 +02:00
|
|
|
});
|
|
|
|
|
2012-10-14 04:13:27 +02:00
|
|
|
var throttled_scrollhandler = $.throttle(50, function(e, delta) {
|
2012-10-05 19:22:43 +02:00
|
|
|
if ($('#home').hasClass('active')) {
|
2012-10-10 20:20:31 +02:00
|
|
|
keep_pointer_in_view();
|
|
|
|
if (e.type === 'mousewheel') {
|
|
|
|
// If we mousewheel (or trackpad-scroll) when
|
|
|
|
// we're at the top and bottom of the page, the
|
|
|
|
// pointer may still want to move.
|
|
|
|
move_pointer_at_page_top_and_bottom();
|
|
|
|
}
|
2012-10-15 21:11:16 +02:00
|
|
|
print_elapsed_time("update_floating_recipient_bar", update_floating_recipient_bar);
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
2012-10-05 21:35:36 +02:00
|
|
|
});
|
|
|
|
$(window).mousewheel(throttled_scrollhandler);
|
|
|
|
$(window).scroll(throttled_scrollhandler);
|
2012-10-03 21:44:07 +02:00
|
|
|
|
2012-10-11 22:34:57 +02:00
|
|
|
var throttled_resizehandler = $.throttle(50, resizehandler);
|
|
|
|
$(window).resize(throttled_resizehandler);
|
|
|
|
|
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 viewport = $(window);
|
|
|
|
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 viewport = $(window);
|
|
|
|
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
|
|
|
|
|
|
|
// Set the URL bar title to show the sub-page you're currently on.
|
|
|
|
var browser_url = $(e.target).attr('href');
|
|
|
|
if (browser_url === "#home") {
|
|
|
|
browser_url = "#";
|
|
|
|
}
|
|
|
|
window.history.pushState("object or string", "Title", browser_url);
|
2012-10-05 20:25:27 +02:00
|
|
|
});
|
|
|
|
|
2012-10-03 21:44:07 +02:00
|
|
|
$('.button-slide').click(function () {
|
2012-10-10 23:33:38 +02:00
|
|
|
show_compose('stream', $("#stream"));
|
2012-10-03 21:44:07 +02:00
|
|
|
});
|
2012-10-03 22:24:17 +02:00
|
|
|
|
|
|
|
$('#sidebar a[href="#subscriptions"]').click(fetch_subs);
|
2012-10-03 22:35:35 +02:00
|
|
|
|
|
|
|
var settings_status = $('#settings-status');
|
|
|
|
$("#current_settings 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);
|
|
|
|
if ((result.full_name !== undefined) || (result.short_name !== undefined)) {
|
|
|
|
message = "Updated settings! You will need to reload the page for your changes to take effect.";
|
|
|
|
}
|
|
|
|
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-03 23:22:51 +02:00
|
|
|
}
|
2012-10-03 22:35:35 +02:00
|
|
|
});
|
2012-10-12 00:48:37 +02:00
|
|
|
|
|
|
|
// A little hackish, because it doesn't seem to totally get us
|
|
|
|
// the exact right width for the narrowbar and compose box,
|
|
|
|
// 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-10-12 22:51:44 +02:00
|
|
|
// limit number of items so the list doesn't fall off the screen
|
|
|
|
$( "#stream" ).typeahead({
|
|
|
|
source: [], // will be set in update_autocomplete()
|
|
|
|
items: 3
|
|
|
|
});
|
|
|
|
$( "#subject" ).typeahead({
|
|
|
|
source: function (query, process) {
|
|
|
|
var stream_name = $("#stream").val();
|
|
|
|
if (subject_dict.hasOwnProperty(stream_name)) {
|
|
|
|
return subject_dict[stream_name];
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
},
|
|
|
|
items: 2
|
|
|
|
});
|
|
|
|
$( "#huddle_recipient" ).typeahead({
|
|
|
|
source: [], // will be set in update_autocomplete()
|
|
|
|
items: 4,
|
|
|
|
matcher: function (item) {
|
|
|
|
// Assumes email addresses don't have commas or semicolons in them
|
2012-10-12 23:29:52 +02:00
|
|
|
var current_recipient = $(this.query.split(/[,;] */)).last()[0];
|
2012-10-12 22:51:44 +02:00
|
|
|
// Case-insensitive (from Bootstrap's default matcher).
|
|
|
|
return (item.toLowerCase().indexOf(current_recipient.toLowerCase()) !== -1);
|
|
|
|
},
|
|
|
|
updater: function (item) {
|
2012-10-12 23:29:52 +02:00
|
|
|
var previous_recipients = this.query.split(/[,;] */);
|
2012-10-12 22:51:44 +02:00
|
|
|
previous_recipients.pop();
|
|
|
|
previous_recipients = previous_recipients.join(", ");
|
|
|
|
if (previous_recipients.length !== 0) {
|
|
|
|
previous_recipients += ", ";
|
|
|
|
}
|
|
|
|
// Extracting the email portion via regex is icky, but the Bootstrap
|
|
|
|
// typeahead widget doesn't seem to be flexible enough to pass
|
|
|
|
// objects around
|
|
|
|
var email_re = /<[^<]*>$/;
|
|
|
|
var email = email_re.exec(item)[0];
|
2012-10-12 23:29:52 +02:00
|
|
|
return previous_recipients + email.substring(1, email.length - 1) + ", ";
|
2012-10-12 22:51:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2012-10-12 23:29:52 +02:00
|
|
|
$( "#huddle_recipient" ).blur(function (event) {
|
|
|
|
var val = $(this).val();
|
|
|
|
$(this).val(val.replace(/[,;] *$/, ''));
|
|
|
|
});
|
|
|
|
|
2012-10-12 17:26:04 +02:00
|
|
|
update_autocomplete();
|
2012-10-03 21:44:07 +02:00
|
|
|
});
|