2012-11-16 16:45:39 +01:00
|
|
|
var ui = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
2013-07-16 20:00:47 +02:00
|
|
|
var actively_scrolling = false;
|
|
|
|
|
2016-04-10 14:15:47 +02:00
|
|
|
exports.have_scrolled_away_from_top = true;
|
|
|
|
|
2013-07-16 20:00:47 +02:00
|
|
|
exports.actively_scrolling = function () {
|
|
|
|
return actively_scrolling;
|
|
|
|
};
|
|
|
|
|
2012-12-03 20:54:29 +01:00
|
|
|
// What, if anything, obscures the home tab?
|
|
|
|
exports.home_tab_obscured = function () {
|
2013-08-01 17:47:48 +02:00
|
|
|
if ($('.modal:visible').length > 0) {
|
2012-12-03 20:54:29 +01:00
|
|
|
return 'modal';
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
|
|
|
if (! $('#home').hasClass('active')) {
|
2012-12-03 20:54:29 +01:00
|
|
|
return 'other_tab';
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2012-12-03 20:54:29 +01:00
|
|
|
return false;
|
2012-11-30 18:28:23 +01:00
|
|
|
};
|
|
|
|
|
2012-12-19 21:19:29 +01:00
|
|
|
exports.change_tab_to = function (tabname) {
|
2013-01-16 21:42:35 +01:00
|
|
|
$('#gear-menu a[href="' + tabname + '"]').tab('show');
|
2012-12-19 21:19:29 +01:00
|
|
|
};
|
|
|
|
|
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
|
|
|
|
2013-05-10 21:06:40 +02:00
|
|
|
function amount_to_paginate() {
|
|
|
|
// Some day we might have separate versions of this function
|
|
|
|
// for Page Up vs. Page Down, but for now it's the same
|
|
|
|
// strategy in either direction.
|
2013-06-06 16:10:12 +02:00
|
|
|
var info = viewport.message_viewport_info();
|
2013-05-20 22:26:20 +02:00
|
|
|
var page_size = info.visible_height;
|
2013-05-10 21:06:40 +02:00
|
|
|
|
2013-08-06 21:32:15 +02:00
|
|
|
// We don't want to page up a full page, because Zulip users
|
2013-05-10 21:06:40 +02:00
|
|
|
// are especially worried about missing messages, so we want
|
|
|
|
// a little bit of the old page to stay on the screen. The
|
|
|
|
// value chosen here is roughly 2 or 3 lines of text, but there
|
|
|
|
// is nothing sacred about it, and somebody more anal than me
|
|
|
|
// might wish to tie this to the size of some particular DOM
|
|
|
|
// element.
|
|
|
|
var overlap_amount = 55;
|
|
|
|
|
|
|
|
var delta = page_size - overlap_amount;
|
|
|
|
|
|
|
|
// If the user has shrunk their browser a whole lot, pagination
|
|
|
|
// is not going to be very pleasant, but we can at least
|
|
|
|
// ensure they go in the right direction.
|
2013-08-01 17:47:48 +02:00
|
|
|
if (delta < 1) {
|
|
|
|
delta = 1;
|
|
|
|
}
|
2013-05-10 21:06:40 +02:00
|
|
|
|
|
|
|
return delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.page_up_the_right_amount = function () {
|
|
|
|
// This function's job is to scroll up the right amount,
|
|
|
|
// after the user hits Page Up. We do this ourselves
|
|
|
|
// because we can't rely on the browser to account for certain
|
|
|
|
// page elements, like the compose box, that sit in fixed
|
|
|
|
// positions above the message pane. For other scrolling
|
|
|
|
// related adjustements, try to make those happen in the
|
|
|
|
// scroll handlers, not here.
|
|
|
|
var delta = amount_to_paginate();
|
|
|
|
viewport.scrollTop(viewport.scrollTop() - delta);
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.page_down_the_right_amount = function () {
|
|
|
|
// see also: page_up_the_right_amount
|
|
|
|
var delta = amount_to_paginate();
|
|
|
|
viewport.scrollTop(viewport.scrollTop() + delta);
|
|
|
|
};
|
|
|
|
|
2013-07-23 00:25:25 +02:00
|
|
|
exports.replace_emoji_with_text = function (element) {
|
|
|
|
element.find(".emoji").replaceWith(function () {
|
|
|
|
return $(this).attr("alt");
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
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. */
|
|
|
|
|
2013-02-09 04:33:06 +01:00
|
|
|
var current_message_hover;
|
|
|
|
function message_unhover() {
|
2013-08-15 23:43:16 +02:00
|
|
|
var message;
|
2013-08-01 17:47:48 +02:00
|
|
|
if (current_message_hover === undefined) {
|
2013-02-09 04:33:06 +01:00
|
|
|
return;
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2013-08-15 23:43:16 +02:00
|
|
|
message = current_msg_list.get(rows.id(current_message_hover));
|
2016-11-16 21:26:41 +01:00
|
|
|
current_message_hover.find('span.edit_content').html("");
|
2013-02-09 04:33:06 +01:00
|
|
|
current_message_hover.removeClass('message_hovered');
|
|
|
|
current_message_hover = undefined;
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
|
|
|
|
2013-02-09 04:33:06 +01:00
|
|
|
function message_hover(message_row) {
|
2013-08-15 23:43:16 +02:00
|
|
|
var message;
|
2016-11-09 01:26:15 +01:00
|
|
|
|
2016-11-11 00:41:00 +01:00
|
|
|
var id = parseInt(message_row.attr("zid"), 10);
|
2013-08-15 23:43:16 +02:00
|
|
|
if (current_message_hover && message_row && current_message_hover.attr("zid") === message_row.attr("zid")) {
|
|
|
|
return;
|
|
|
|
}
|
2014-01-24 18:01:12 +01:00
|
|
|
// Don't allow on-hover editing for local-only messages
|
|
|
|
if (message_row.hasClass('local')) {
|
|
|
|
return;
|
|
|
|
}
|
2013-08-15 23:43:16 +02:00
|
|
|
message = current_msg_list.get(rows.id(message_row));
|
2013-02-09 04:33:06 +01:00
|
|
|
message_unhover();
|
|
|
|
message_row.addClass('message_hovered');
|
2016-12-02 15:16:33 +01:00
|
|
|
if ((message_edit.get_editability(message) === message_edit.editability_types.FULL) &&
|
|
|
|
!message.status_message) {
|
2016-11-11 00:41:00 +01:00
|
|
|
message_row.find(".edit_content").html('<i class="icon-vector-pencil edit_content_button"></i>');
|
|
|
|
} else {
|
|
|
|
message_row.find(".edit_content").html('<i class="icon-vector-file-text-alt edit_content_button" data-msgid="' + id + '"></i>');
|
2013-08-15 23:43:16 +02:00
|
|
|
}
|
2013-02-09 04:33:06 +01:00
|
|
|
current_message_hover = message_row;
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
|
|
|
|
2016-03-15 18:22:20 +01:00
|
|
|
/* Arguments used in the report_* functions are,
|
|
|
|
response- response that we want to display
|
|
|
|
status_box- element being used to display the response
|
|
|
|
cls- class that we want to add/remove to/from the status_box
|
|
|
|
type- used to define more complex logic for special cases (currently being
|
|
|
|
used only for subscriptions-status) */
|
|
|
|
|
|
|
|
exports.report_message = function (response, status_box, cls, type) {
|
2013-08-01 17:47:48 +02:00
|
|
|
if (cls === undefined) {
|
2012-10-31 23:38:34 +01:00
|
|
|
cls = 'alert';
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2012-10-31 23:38:34 +01:00
|
|
|
|
2016-03-15 18:22:20 +01:00
|
|
|
if (type === undefined) {
|
|
|
|
type = ' ';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type === 'subscriptions-status') {
|
|
|
|
status_box.removeClass(status_classes).addClass(cls).children('#response')
|
|
|
|
.text(response).stop(true).fadeTo(0, 1);
|
|
|
|
} else {
|
|
|
|
status_box.removeClass(status_classes).addClass(cls)
|
2012-10-31 23:38:34 +01:00
|
|
|
.text(response).stop(true).fadeTo(0, 1);
|
2016-03-15 18:22:20 +01:00
|
|
|
}
|
|
|
|
|
2012-10-31 23:38:34 +01:00
|
|
|
status_box.show();
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
2012-10-31 23:38:34 +01:00
|
|
|
|
2016-03-15 18:22:20 +01:00
|
|
|
exports.report_error = function (response, xhr, status_box, type) {
|
2015-08-21 01:23:53 +02:00
|
|
|
if (xhr && xhr.status.toString().charAt(0) === "4") {
|
2012-10-03 21:44:07 +02:00
|
|
|
// Only display the error response for 4XX, where we've crafted
|
|
|
|
// a nice response.
|
2016-08-24 23:56:23 +02:00
|
|
|
response += ": " + JSON.parse(xhr.responseText).msg;
|
2012-10-03 21:44:07 +02:00
|
|
|
}
|
|
|
|
|
2016-03-15 18:22:20 +01:00
|
|
|
ui.report_message(response, status_box, 'alert-error', type);
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
2012-10-03 21:44:07 +02:00
|
|
|
|
2016-03-15 18:22:20 +01:00
|
|
|
exports.report_success = function (response, status_box, type) {
|
|
|
|
ui.report_message(response, status_box, 'alert-success', type);
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
2012-10-17 20:43:20 +02:00
|
|
|
|
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;
|
|
|
|
}
|
2016-12-02 21:34:35 +01:00
|
|
|
// IE<10 doesn't support window.matchMedia, so do this
|
|
|
|
// as best we can without it.
|
|
|
|
return window.innerWidth <= 767;
|
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
|
|
|
}
|
|
|
|
|
2013-12-19 17:03:08 +01:00
|
|
|
function update_message_in_all_views(message_id, callback) {
|
2016-04-25 23:45:25 +02:00
|
|
|
_.each([message_list.all, home_msg_list, message_list.narrowed], function (list) {
|
2013-12-19 17:03:08 +01:00
|
|
|
if (list === undefined) {
|
2013-03-27 18:30:02 +01:00
|
|
|
return;
|
|
|
|
}
|
2013-12-19 17:03:08 +01:00
|
|
|
var row = list.get_row(message_id);
|
2013-03-27 18:30:02 +01:00
|
|
|
if (row === undefined) {
|
2013-12-19 17:03:08 +01:00
|
|
|
// The row may not exist, e.g. if you do an action on a message in
|
|
|
|
// a narrowed view
|
2013-03-27 18:30:02 +01:00
|
|
|
return;
|
|
|
|
}
|
2013-12-19 17:03:08 +01:00
|
|
|
callback(row);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2014-03-14 15:30:29 +01:00
|
|
|
exports.find_message = function (message_id) {
|
2014-01-28 22:44:34 +01:00
|
|
|
// Try to find the message object. It might be in the narrow list
|
2016-04-21 22:49:23 +02:00
|
|
|
// (if it was loaded when narrowed), or only in the message_list.all
|
2014-01-28 22:44:34 +01:00
|
|
|
// (if received from the server while in a different narrow)
|
|
|
|
var message;
|
2016-04-25 23:45:25 +02:00
|
|
|
_.each([message_list.all, home_msg_list, message_list.narrowed], function (msg_list) {
|
2014-01-28 22:44:34 +01:00
|
|
|
if (msg_list !== undefined && message === undefined) {
|
|
|
|
message = msg_list.get(message_id);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return message;
|
2014-03-14 15:30:29 +01:00
|
|
|
};
|
2014-01-28 22:44:34 +01:00
|
|
|
|
2013-12-19 17:03:08 +01:00
|
|
|
exports.update_starred = function (message_id, starred) {
|
|
|
|
// Update the message object pointed to by the various message
|
|
|
|
// lists.
|
2014-03-14 15:30:29 +01:00
|
|
|
var message = exports.find_message(message_id);
|
2013-12-19 17:03:08 +01:00
|
|
|
|
2014-01-31 17:44:21 +01:00
|
|
|
unread.mark_message_as_read(message);
|
2013-12-19 17:03:08 +01:00
|
|
|
|
2014-01-30 18:08:02 +01:00
|
|
|
message.starred = starred;
|
2013-12-19 17:03:08 +01:00
|
|
|
|
|
|
|
// Avoid a full re-render, but update the star in each message
|
|
|
|
// table in which it is visible.
|
|
|
|
update_message_in_all_views(message_id, function update_row(row) {
|
2014-01-23 17:52:28 +01:00
|
|
|
var elt = row.find(".message_star");
|
|
|
|
if (starred) {
|
|
|
|
elt.addClass("icon-vector-star").removeClass("icon-vector-star-empty").removeClass("empty-star");
|
|
|
|
} else {
|
|
|
|
elt.removeClass("icon-vector-star").addClass("icon-vector-star-empty").addClass("empty-star");
|
|
|
|
}
|
2013-04-14 05:04:47 +02:00
|
|
|
var title_state = message.starred ? "Unstar" : "Star";
|
2014-01-23 17:52:28 +01:00
|
|
|
elt.attr("title", title_state + " this message");
|
2013-03-27 18:30:02 +01:00
|
|
|
});
|
2014-01-23 17:52:28 +01:00
|
|
|
};
|
2013-03-27 18:30:02 +01:00
|
|
|
|
2013-12-19 17:03:08 +01:00
|
|
|
var local_messages_to_show = [];
|
|
|
|
var show_message_timestamps = _.throttle(function () {
|
|
|
|
_.each(local_messages_to_show, function (message_id) {
|
|
|
|
update_message_in_all_views(message_id, function update_row(row) {
|
|
|
|
row.find('.message_time').toggleClass('notvisible', false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
local_messages_to_show = [];
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
exports.show_local_message_arrived = function (message_id) {
|
|
|
|
local_messages_to_show.push(message_id);
|
|
|
|
show_message_timestamps();
|
|
|
|
};
|
|
|
|
|
2014-01-02 17:34:16 +01:00
|
|
|
exports.show_message_failed = function (message_id, failed_msg) {
|
2013-12-19 17:03:08 +01:00
|
|
|
// Failed to send message, so display inline retry/cancel
|
|
|
|
update_message_in_all_views(message_id, function update_row(row) {
|
2014-01-02 17:34:16 +01:00
|
|
|
var failed_div = row.find('.message_failed');
|
|
|
|
failed_div.toggleClass('notvisible', false);
|
|
|
|
failed_div.find('.failed_text').attr('title', failed_msg);
|
2013-12-19 17:03:08 +01:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.show_failed_message_success = function (message_id) {
|
|
|
|
// Previously failed message succeeded
|
|
|
|
update_message_in_all_views(message_id, function update_row(row) {
|
|
|
|
row.find('.message_failed').toggleClass('notvisible', true);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2013-06-13 23:48:23 +02:00
|
|
|
exports.small_avatar_url = function (message) {
|
2013-08-02 21:14:08 +02:00
|
|
|
// Try to call this function in all places where we need 25px
|
2013-11-19 17:15:48 +01:00
|
|
|
// avatar images, so that the browser can help
|
2013-06-13 23:48:23 +02:00
|
|
|
// us avoid unnecessary network trips. (For user-uploaded avatars,
|
2013-08-02 21:14:08 +02:00
|
|
|
// the s=25 parameter is essentially ignored, but it's harmless.)
|
2013-06-13 23:48:23 +02:00
|
|
|
//
|
2013-08-02 21:14:08 +02:00
|
|
|
// We actually request these at s=50, so that we look better
|
|
|
|
// on retina displays.
|
2013-06-13 23:48:23 +02:00
|
|
|
if (message.avatar_url) {
|
2013-08-02 21:14:08 +02:00
|
|
|
var url = message.avatar_url + "&s=50";
|
2013-06-14 17:46:01 +02:00
|
|
|
if (message.sent_by_me) {
|
2013-11-19 17:15:48 +01:00
|
|
|
url += "&stamp=" + settings.avatar_stamp;
|
2013-06-13 23:48:23 +02:00
|
|
|
}
|
|
|
|
return url;
|
|
|
|
}
|
2016-12-02 21:34:35 +01:00
|
|
|
return "";
|
2012-11-16 16:45:39 +01:00
|
|
|
};
|
|
|
|
|
2016-10-17 22:02:25 +02:00
|
|
|
exports.lightbox = function (data) {
|
|
|
|
switch (data.type) {
|
|
|
|
case "photo":
|
|
|
|
exports.lightbox_photo(data.image, data.user);
|
|
|
|
break;
|
|
|
|
case "youtube":
|
|
|
|
exports.youtube_video(data.id);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
$("#overlay").addClass("show");
|
|
|
|
};
|
|
|
|
|
2016-09-22 02:05:24 +02:00
|
|
|
exports.lightbox_photo = function (image, user) {
|
|
|
|
// image should be an Image Object in JavaScript.
|
2016-12-02 17:09:31 +01:00
|
|
|
var url = $(image).attr("src");
|
|
|
|
var title = $(image).parent().attr("title");
|
2016-09-22 02:05:24 +02:00
|
|
|
|
2016-10-17 22:02:25 +02:00
|
|
|
$("#overlay .player-container").hide();
|
|
|
|
$("#overlay .image-actions, .image-description, .download").show();
|
|
|
|
|
2016-10-25 23:26:28 +02:00
|
|
|
var img = new Image();
|
|
|
|
img.src = url;
|
|
|
|
$("#overlay .image-preview").html("").show()
|
|
|
|
.append(img);
|
2016-09-22 02:05:24 +02:00
|
|
|
|
2016-10-17 22:02:25 +02:00
|
|
|
$(".image-description .title").text(title || "N/A");
|
|
|
|
$(".image-description .user").text(user);
|
2016-09-22 02:05:24 +02:00
|
|
|
|
|
|
|
$(".image-actions .open, .image-actions .download").attr("href", url);
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.exit_lightbox_photo = function (image) {
|
|
|
|
$("#overlay").removeClass("show");
|
2016-10-17 22:02:25 +02:00
|
|
|
$(".player-container iframe").remove();
|
2016-10-25 23:26:28 +02:00
|
|
|
document.activeElement.blur();
|
2016-10-17 22:02:25 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
exports.youtube_video = function (id) {
|
|
|
|
$("#overlay .image-preview, .image-description, .download").hide();
|
|
|
|
|
|
|
|
var iframe = document.createElement("iframe");
|
|
|
|
iframe.width = window.innerWidth;
|
|
|
|
iframe.height = window.innerWidth * 0.5625;
|
|
|
|
iframe.src = "https://www.youtube.com/embed/" + id;
|
|
|
|
iframe.setAttribute("frameborder", 0);
|
|
|
|
iframe.setAttribute("allowfullscreen", true);
|
|
|
|
|
|
|
|
$("#overlay .player-container").html("").show().append(iframe);
|
|
|
|
$(".image-actions .open").attr("href", "https://youtu.be/" + id);
|
2016-09-22 02:05:24 +02:00
|
|
|
};
|
2016-10-17 22:02:25 +02:00
|
|
|
// k3O01EfM5fU
|
2016-09-22 02:05:24 +02:00
|
|
|
|
2013-01-16 19:50:18 +01:00
|
|
|
var loading_more_messages_indicator_showing = false;
|
|
|
|
exports.show_loading_more_messages_indicator = function () {
|
|
|
|
if (! loading_more_messages_indicator_showing) {
|
2014-03-13 15:03:01 +01:00
|
|
|
loading.make_indicator($('#loading_more_messages_indicator'),
|
2013-07-12 18:31:26 +02:00
|
|
|
{abs_positioned: true});
|
2013-01-16 19:50:18 +01:00
|
|
|
loading_more_messages_indicator_showing = true;
|
2014-03-13 21:14:33 +01:00
|
|
|
floating_recipient_bar.hide();
|
2012-11-28 22:17:57 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-01-16 19:50:18 +01:00
|
|
|
exports.hide_loading_more_messages_indicator = function () {
|
|
|
|
if (loading_more_messages_indicator_showing) {
|
2014-03-13 15:03:01 +01:00
|
|
|
loading.destroy_indicator($("#loading_more_messages_indicator"));
|
2013-01-16 19:50:18 +01:00
|
|
|
loading_more_messages_indicator_showing = false;
|
2012-11-28 22:17:57 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-12-04 17:54:45 +01:00
|
|
|
/* EXPERIMENTS */
|
|
|
|
|
2013-12-05 20:23:02 +01:00
|
|
|
/* This method allows an advanced user to use the console
|
|
|
|
* to switch the application to span full width of the browser.
|
|
|
|
*/
|
|
|
|
exports.switchToFullWidth = function () {
|
|
|
|
$("#full-width-style").remove();
|
|
|
|
$('head').append('<style id="full-width-style" type="text/css">' +
|
|
|
|
'#home .alert-bar, .recipient-bar-content, #compose-container, .app-main, .header-main { max-width: none; }' +
|
|
|
|
'</style>');
|
|
|
|
return ("Switched to full width");
|
|
|
|
};
|
|
|
|
|
2013-12-04 17:54:45 +01:00
|
|
|
/* END OF EXPERIMENTS */
|
|
|
|
|
2012-10-03 21:44:07 +02:00
|
|
|
$(function () {
|
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.
|
2013-08-02 17:41:00 +02:00
|
|
|
|
|
|
|
if (delta > 0) {
|
|
|
|
if (viewport.at_top()) {
|
|
|
|
navigate.up();
|
|
|
|
}
|
|
|
|
} else if (delta < 0) {
|
|
|
|
if (viewport.at_bottom()) {
|
|
|
|
navigate.down();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-25 13:02:38 +02:00
|
|
|
viewport.last_movement_direction = delta;
|
2012-10-16 03:49:51 +02:00
|
|
|
});
|
2012-11-30 18:28:23 +01:00
|
|
|
|
2014-01-29 19:04:51 +01:00
|
|
|
viewport.message_pane.mousewheel(function (e, delta) {
|
2012-11-30 18:28:23 +01:00
|
|
|
// 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
|
|
|
|
2014-03-13 19:03:31 +01:00
|
|
|
$(window).resize($.throttle(50, resize.handler));
|
2012-10-11 22:34:57 +02:00
|
|
|
|
2013-02-12 16:31:55 +01:00
|
|
|
// Scrolling in modals, input boxes, and other elements that
|
|
|
|
// explicitly scroll 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, .scrolling_list, input, textarea').mousewheel(function (e, delta) {
|
2012-11-30 18:28:23 +01:00
|
|
|
var self = $(this);
|
|
|
|
var scroll = self.scrollTop();
|
2013-11-13 23:27:08 +01:00
|
|
|
|
|
|
|
// The -1 fudge factor is important here due to rounding errors. Better
|
|
|
|
// to err on the side of not scrolling.
|
|
|
|
var max_scroll = this.scrollHeight - self.innerHeight() - 1;
|
|
|
|
|
2012-11-30 18:28:23 +01:00
|
|
|
e.stopPropagation();
|
|
|
|
if ( ((delta > 0) && (scroll <= 0))
|
2013-11-13 23:27:08 +01:00
|
|
|
|| ((delta < 0) && (scroll >= max_scroll))) {
|
2012-11-30 18:28:23 +01:00
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-10-15 22:34:30 +02:00
|
|
|
// Override the #compose mousewheel prevention below just for the emoji box
|
|
|
|
$('.emoji_popover').mousewheel(function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
});
|
|
|
|
|
2012-11-30 18:28:23 +01:00
|
|
|
// Ignore wheel events in the compose area which weren't already handled above.
|
|
|
|
$('#compose').mousewheel(function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
|
2013-02-28 19:04:58 +01:00
|
|
|
// A little hackish, because it doesn't seem to totally get us the
|
|
|
|
// exact right width for the floating_recipient_bar and compose
|
|
|
|
// box, but, close enough for now.
|
2014-03-13 19:03:31 +01:00
|
|
|
resize.handler();
|
2012-10-12 17:26:04 +02:00
|
|
|
|
2015-08-20 23:59:44 +02:00
|
|
|
if (!page_params.left_side_userlist) {
|
2013-10-09 00:37:07 +02:00
|
|
|
$("#navbar-buttons").addClass("right-userlist");
|
|
|
|
}
|
|
|
|
|
2013-05-21 17:47:44 +02:00
|
|
|
$("#main_div").on("mouseover", ".message_row", function (e) {
|
2013-08-15 23:43:16 +02:00
|
|
|
var row = $(this).closest(".message_row");
|
2013-02-09 04:33:06 +01:00
|
|
|
message_hover(row);
|
2012-11-14 23:33:13 +01:00
|
|
|
});
|
|
|
|
|
2013-08-15 23:43:16 +02:00
|
|
|
$("#main_div").on("mouseleave", ".message_row", function (e) {
|
2013-02-09 04:33:06 +01:00
|
|
|
message_unhover();
|
2012-11-14 23:33:13 +01:00
|
|
|
});
|
|
|
|
|
2013-07-11 23:06:58 +02:00
|
|
|
$("#main_div").on("mouseover", ".message_sender", function (e) {
|
2012-11-14 23:33:13 +01:00
|
|
|
var row = $(this).closest(".message_row");
|
2013-07-11 23:06:58 +02:00
|
|
|
row.addClass("sender_name_hovered");
|
2012-11-14 23:33:13 +01:00
|
|
|
});
|
|
|
|
|
2013-07-11 23:06:58 +02:00
|
|
|
$("#main_div").on("mouseout", ".message_sender", function (e) {
|
2012-11-14 23:33:13 +01:00
|
|
|
var row = $(this).closest(".message_row");
|
2013-07-11 23:06:58 +02:00
|
|
|
row.removeClass("sender_name_hovered");
|
2012-11-14 23:33:13 +01:00
|
|
|
});
|
|
|
|
|
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
|
|
|
|
2013-01-15 22:19:38 +01:00
|
|
|
$("#stream").on('blur', function () { compose.decorate_stream_bar(this.value); });
|
2013-01-22 01:34:44 +01:00
|
|
|
|
2013-02-12 20:52:33 +01:00
|
|
|
$(window).on('blur', function () {
|
|
|
|
$(document.body).addClass('window_blurred');
|
|
|
|
});
|
|
|
|
|
|
|
|
$(window).on('focus', function () {
|
|
|
|
$(document.body).removeClass('window_blurred');
|
|
|
|
});
|
2013-02-20 20:49:49 +01:00
|
|
|
|
2013-07-25 22:48:55 +02:00
|
|
|
$(document).on('message_selected.zulip', function (event) {
|
2013-02-20 20:49:49 +01:00
|
|
|
if (current_msg_list !== event.msg_list) {
|
|
|
|
return;
|
|
|
|
}
|
2013-05-30 00:10:10 +02:00
|
|
|
if (event.id === -1) {
|
|
|
|
// If the message list is empty, don't do anything
|
|
|
|
return;
|
|
|
|
}
|
2013-08-14 22:00:32 +02:00
|
|
|
var row = event.msg_list.get_row(event.id);
|
2013-02-20 20:49:49 +01:00
|
|
|
$('.selected_message').removeClass('selected_message');
|
2013-06-19 17:10:49 +02:00
|
|
|
row.addClass('selected_message');
|
2013-02-20 20:49:49 +01:00
|
|
|
|
|
|
|
if (event.then_scroll) {
|
2013-10-31 18:01:52 +01:00
|
|
|
if (row.length === 0) {
|
2014-03-10 23:07:17 +01:00
|
|
|
var row_from_dom = current_msg_list.get_row(event.id);
|
2013-10-31 18:01:52 +01:00
|
|
|
blueslip.debug("message_selected missing selected row", {
|
|
|
|
previously_selected: event.previously_selected,
|
|
|
|
selected_id: event.id,
|
|
|
|
selected_idx: event.msg_list.selected_idx(),
|
|
|
|
selected_idx_exact: event.msg_list._items.indexOf(event.msg_list.get(event.id)),
|
|
|
|
render_start: event.msg_list.view._render_win_start,
|
2014-02-04 13:48:03 +01:00
|
|
|
render_end: event.msg_list.view._render_win_end,
|
|
|
|
selected_id_from_idx: event.msg_list._items[event.msg_list.selected_idx()].id,
|
|
|
|
msg_list_sorted: _.isEqual(
|
|
|
|
_.pluck(event.msg_list._items, 'id'),
|
|
|
|
_.chain(current_msg_list._items).pluck('id').clone().value().sort()
|
|
|
|
),
|
|
|
|
found_in_dom: row_from_dom.length
|
2013-10-31 18:01:52 +01:00
|
|
|
});
|
|
|
|
}
|
2014-02-04 22:02:26 +01:00
|
|
|
if (event.target_scroll_offset !== undefined) {
|
|
|
|
viewport.set_message_offset(event.target_scroll_offset);
|
|
|
|
} else {
|
|
|
|
// Scroll to place the message within the current view;
|
|
|
|
// but if this is the initial placement of the pointer,
|
|
|
|
// just place it in the very center
|
2016-05-25 13:42:20 +02:00
|
|
|
viewport.recenter_view(row, {from_scroll: event.from_scroll,
|
2014-02-04 22:02:26 +01:00
|
|
|
force_center: event.previously_selected === -1});
|
|
|
|
}
|
2013-02-20 20:49:49 +01:00
|
|
|
}
|
|
|
|
});
|
2013-03-05 00:18:04 +01:00
|
|
|
|
|
|
|
$("#main_div").on("mouseenter", ".message_time", function (e) {
|
|
|
|
var time_elem = $(e.target);
|
|
|
|
var row = time_elem.closest(".message_row");
|
|
|
|
var message = current_msg_list.get(rows.id(row));
|
|
|
|
timerender.set_full_datetime(message, time_elem);
|
|
|
|
});
|
2013-03-07 20:12:27 +01:00
|
|
|
|
2016-06-13 22:06:12 +02:00
|
|
|
$('#streams_header h4').tooltip({ placement: 'right',
|
2013-07-12 17:00:32 +02:00
|
|
|
animation: false });
|
|
|
|
|
2016-06-13 22:06:12 +02:00
|
|
|
$('#streams_header i[data-toggle="tooltip"]').tooltip({ placement: 'left',
|
|
|
|
animation: false });
|
|
|
|
|
2016-11-22 07:26:38 +01:00
|
|
|
$('.message_failed i[data-toggle="tooltip"]').tooltip();
|
2016-06-13 22:06:12 +02:00
|
|
|
|
2016-06-21 21:34:41 +02:00
|
|
|
if (!page_params.realm_allow_message_editing) {
|
2013-11-30 22:03:12 +01:00
|
|
|
$("#edit-message-hotkey-help").hide();
|
|
|
|
}
|
|
|
|
|
2016-07-27 02:09:10 +02:00
|
|
|
if (page_params.presence_disabled) {
|
2013-11-28 22:13:01 +01:00
|
|
|
$("#user-list").hide();
|
|
|
|
$("#group-pm-list").hide();
|
|
|
|
}
|
|
|
|
|
2013-12-05 20:23:02 +01:00
|
|
|
if (feature_flags.full_width) {
|
|
|
|
exports.switchToFullWidth();
|
|
|
|
}
|
|
|
|
|
2013-05-03 22:12:58 +02:00
|
|
|
// initialize other stuff
|
2014-03-03 23:32:17 +01:00
|
|
|
reload.initialize();
|
2013-05-03 22:12:58 +02:00
|
|
|
composebox_typeahead.initialize();
|
|
|
|
search.initialize();
|
2015-10-26 17:44:15 +01:00
|
|
|
tutorial.initialize();
|
2013-05-03 22:12:58 +02:00
|
|
|
notifications.initialize();
|
2015-09-20 07:47:43 +02:00
|
|
|
gear_menu.initialize();
|
2013-05-03 22:12:58 +02:00
|
|
|
hashchange.initialize();
|
|
|
|
invite.initialize();
|
2016-11-05 18:52:07 +01:00
|
|
|
pointer.initialize();
|
2016-11-05 18:49:34 +01:00
|
|
|
unread.initialize();
|
2013-05-03 22:12:58 +02:00
|
|
|
activity.initialize();
|
2015-10-15 22:34:30 +02:00
|
|
|
emoji.initialize();
|
2012-10-03 21:44:07 +02:00
|
|
|
});
|
2012-11-05 23:51:27 +01:00
|
|
|
|
2013-08-02 23:41:40 +02:00
|
|
|
|
|
|
|
var scroll_start_message;
|
|
|
|
|
|
|
|
function scroll_finished() {
|
|
|
|
actively_scrolling = false;
|
|
|
|
|
|
|
|
if ($('#home').hasClass('active')) {
|
2016-04-12 17:38:47 +02:00
|
|
|
if (!pointer.suppress_scroll_pointer_update) {
|
2016-04-16 14:22:03 +02:00
|
|
|
pointer.keep_pointer_in_view();
|
2013-08-02 23:41:40 +02:00
|
|
|
} else {
|
2016-04-12 17:38:47 +02:00
|
|
|
pointer.suppress_scroll_pointer_update = false;
|
2013-08-02 23:41:40 +02:00
|
|
|
}
|
2014-03-13 21:14:33 +01:00
|
|
|
floating_recipient_bar.update();
|
2013-08-02 23:41:40 +02:00
|
|
|
if (viewport.scrollTop() === 0 &&
|
2016-04-10 14:15:47 +02:00
|
|
|
ui.have_scrolled_away_from_top) {
|
|
|
|
ui.have_scrolled_away_from_top = false;
|
2014-01-31 16:27:24 +01:00
|
|
|
message_store.load_more_messages(current_msg_list);
|
2016-04-10 14:15:47 +02:00
|
|
|
} else if (!ui.have_scrolled_away_from_top) {
|
|
|
|
ui.have_scrolled_away_from_top = true;
|
2013-08-02 23:41:40 +02:00
|
|
|
}
|
|
|
|
// When the window scrolls, it may cause some messages to
|
|
|
|
// enter the screen and become read. Calling
|
2014-01-31 17:44:21 +01:00
|
|
|
// unread.process_visible will update necessary
|
2013-08-02 23:41:40 +02:00
|
|
|
// data structures and DOM elements.
|
2014-01-31 17:44:21 +01:00
|
|
|
setTimeout(unread.process_visible, 0);
|
2013-08-02 23:41:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var scroll_timer;
|
|
|
|
function scroll_finish() {
|
|
|
|
actively_scrolling = true;
|
|
|
|
clearTimeout(scroll_timer);
|
|
|
|
scroll_timer = setTimeout(scroll_finished, 100);
|
|
|
|
}
|
|
|
|
|
2013-02-18 08:16:57 +01:00
|
|
|
// Save the compose content cursor position and restore when we
|
|
|
|
// shift-tab back in (see hotkey.js).
|
|
|
|
var saved_compose_cursor = 0;
|
|
|
|
|
|
|
|
$(function () {
|
2014-01-31 00:00:33 +01:00
|
|
|
viewport.message_pane.scroll($.throttle(50, function (e) {
|
2014-01-31 17:44:21 +01:00
|
|
|
unread.process_visible();
|
2014-01-31 00:00:33 +01:00
|
|
|
scroll_finish();
|
|
|
|
}));
|
|
|
|
|
2013-02-18 08:16:57 +01:00
|
|
|
$('#new_message_content').blur(function () {
|
2016-06-14 22:49:41 +02:00
|
|
|
saved_compose_cursor = $(this).caret();
|
2013-02-18 08:16:57 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
exports.restore_compose_cursor = function () {
|
|
|
|
$('#new_message_content')
|
|
|
|
.focus()
|
2016-06-14 22:49:41 +02:00
|
|
|
.caret(saved_compose_cursor);
|
2013-02-18 08:16:57 +01:00
|
|
|
};
|
2013-07-24 19:43:45 +02:00
|
|
|
|
2013-08-09 21:46:34 +02:00
|
|
|
$(function () {
|
|
|
|
if (window.bridge !== undefined) {
|
2013-08-27 22:07:59 +02:00
|
|
|
// Disable "spellchecking" in our desktop app. The "spellchecking"
|
|
|
|
// in our Mac app is actually autocorrect, and frustrates our
|
|
|
|
// users.
|
2013-08-09 21:46:34 +02:00
|
|
|
$("#new_message_content").attr('spellcheck', 'false');
|
2013-08-27 22:07:59 +02:00
|
|
|
// Modify the zephyr mirroring error message in our desktop
|
|
|
|
// app, since it doesn't work from the desktop version.
|
|
|
|
$("#webathena_login_menu").hide();
|
|
|
|
$("#normal-zephyr-mirror-error-text").addClass("notdisplayed");
|
|
|
|
$("#desktop-zephyr-mirror-error-text").removeClass("notdisplayed");
|
2013-08-09 21:46:34 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2012-11-16 16:45:39 +01:00
|
|
|
return exports;
|
|
|
|
}());
|