Make the back button work when narrowed

(imported from commit be922b06e7b53ec21751e67a490bb518333c3e6c)
This commit is contained in:
Jeff Arnold 2012-12-07 14:52:39 -05:00
parent a2f26f1106
commit db6f03d46d
9 changed files with 168 additions and 66 deletions

View File

@ -55,6 +55,7 @@
<script type="text/javascript" src="{{ static_hidden }}js/composebox_typeahead.js"></script>
<script type="text/javascript" src="{{ static_hidden }}js/hotkey.js"></script>
<script type="text/javascript" src="{{ static_hidden }}js/notifications.js"></script>
<script type="text/javascript" src="{{ static_hidden }}js/hashchange.js"></script>
<script type="text/javascript" src="{{ static_hidden }}js/zephyr.js"></script>
{% if debug %}

View File

@ -45,6 +45,9 @@ var globals =
// notifications.js
+ ' notifications'
// hashchange.js
+ ' hashchange'
// ui.js
+ ' ui'

View File

@ -0,0 +1 @@
../../../static/js/hashchange.js

View File

@ -0,0 +1,38 @@
var hashchange = (function () {
var exports = {};
var expected_hash = false;
exports.changehash = function (newhash) {
expected_hash = newhash;
window.location.hash = newhash;
};
function hashchanged() {
// If window.location.hash changed because our app explicitly
// changed it, then we don't need to do anything.
// (This function only neds to jump into action if it changed
// because e.g. the back button was pressed by the user)
if (window.location.hash === expected_hash) {
return;
}
var hash = window.location.hash.split("/");
if (hash[0] === "#narrow") {
narrow.hashchanged(hash);
ui.update_floating_recipient_bar();
}
else if (narrow.active()) {
narrow.show_all_messages();
ui.update_floating_recipient_bar();
}
}
exports.initialize = function () {
window.onhashchange = hashchanged;
};
return exports;
}());

View File

@ -44,6 +44,40 @@ exports.data = function () {
return narrowdata;
};
exports.hashchanged = function (hash) {
var full_names, decoded, emails;
if (hash[1] === "all_private_messages") {
exports.all_private_messages();
}
else if (hash[1] === "stream" && hash[3] === "subject") {
exports.by_stream_and_subject_names(decodeURIComponent(hash[2]),
decodeURIComponent(hash[4]));
}
else if (hash[1] === "stream") {
exports.by_stream_name(decodeURIComponent(hash[2]));
}
else if (hash[1] === "private_messages") {
decoded = decodeURIComponent(hash[2]);
emails = decoded.split(", ");
$.each(emails, function (index, email) {
$.each(people_list, function (index, person) {
if (person.email === email) {
if (full_names === undefined) {
full_names = person.full_name;
}
else {
full_names += ", " + person.full_name;
}
return false;
}
});
});
exports.by_private_message_group(full_names, decoded);
}
};
function do_narrow(new_narrow, bar, time_travel, new_filter) {
var was_narrowed = exports.active();
@ -120,6 +154,8 @@ exports.target = function (id) {
};
exports.all_private_messages = function () {
hashchange.changehash("#narrow/all_private_messages");
var new_narrow = {type: "all_private_messages"};
var bar = {icon: 'user', description: 'You and anyone else'};
do_narrow(new_narrow, bar, false, function (other) {
@ -135,23 +171,33 @@ exports.by_subject = function () {
exports.by_recipient();
return;
}
exports.by_stream_and_subject_names(original.display_recipient,
original.subject);
};
var new_narrow = {type: "subject", recipient_id: original.recipient_id,
subject: original.subject};
exports.by_stream_and_subject_names = function (stream, subject) {
hashchange.changehash("#narrow/stream/" +
encodeURIComponent(stream) +
"/subject/" +
encodeURIComponent(subject));
var new_narrow = {type: "subject", stream: stream, subject: subject};
var bar = {
icon: 'bullhorn',
description: original.display_recipient,
subject: original.subject
description: stream,
subject: subject
};
do_narrow(new_narrow, bar, false, function (other) {
return ((other.type === 'stream') &&
same_stream_and_subject(original, other));
(other.display_recipient === stream &&
other.subject.toLowerCase() === subject.toLowerCase()));
});
};
//TODO: There's probably some room to unify this code
// with the hotkey-narrowing code below.
exports.by_stream_name = function (name) {
hashchange.changehash("#narrow/stream/" +
encodeURIComponent(name));
var new_narrow = {type: "stream", stream: name};
var bar = {icon: 'bullhorn', description: name};
do_narrow(new_narrow, bar, false, function (other) {
@ -160,43 +206,38 @@ exports.by_stream_name = function (name) {
});
};
exports.by_private_message_partner = function (their_name, their_email) {
var new_narrow = {type: "private", one_on_one_email: their_email};
var bar = {icon: 'user', description: "You and " + their_name};
exports.by_private_message_group = function (names, emails) {
hashchange.changehash("#narrow/private_messages/" +
encodeURIComponent(emails));
var new_narrow = {type: "private", emails: emails.split(", ")};
var bar = {icon: 'user', description: "You and " + names};
var my_email = email;
do_narrow(new_narrow, bar, false, function (other) {
return (other.type === 'private' &&
get_private_message_recipient(other, 'email') === their_email);
other.reply_to === emails);
});
};
// Called for the 'narrow by stream' hotkey.
exports.by_recipient = function () {
var message = message_dict[target_id];
var bar;
var new_narrow;
var bar, new_narrow, emails;
switch (message.type) {
case 'private':
new_narrow = {type: "private", recipient_id: message.recipient_id};
bar = {icon: 'user', description: "You and " + message.display_reply_to};
do_narrow(new_narrow, bar, false, function (other) {
return (other.type === "private" &&
other.reply_to === message.reply_to);
});
exports.by_private_message_group(message.display_reply_to,
message.reply_to);
break;
case 'stream':
new_narrow = {type: "stream", recipient_id: message.recipient_id};
bar = {icon: 'bullhorn', description: message.display_recipient};
do_narrow(new_narrow, bar, false, function (other) {
return (other.type === 'stream' &&
message.recipient_id === other.recipient_id);
});
exports.by_stream_name(message.display_recipient);
break;
}
};
exports.by_search_term = function (term) {
hashchange.changehash("#narrow/searchterm/" + encodeURIComponent(term));
var new_narrow = {type: "searchterm", searchterm: term, allow_collapse: false};
var bar = {icon: 'search', description: 'Messages containing "' + term + '"'};
var term_lowercase = term.toLowerCase();
@ -208,6 +249,8 @@ exports.by_search_term = function (term) {
};
exports.show_all_messages = function () {
hashchange.changehash("");
if (!narrowdata) {
return;
}

View File

@ -71,7 +71,7 @@ function narrow_or_search_for_term(item) {
// unnarrow, it'll leave the searchbox.
return ""; // Keep the search box empty
} else if (obj.action === "private_message") {
narrow.by_private_message_partner(obj.query.full_name, obj.query.email);
narrow.by_private_message_group(obj.query.full_name, obj.query.email);
return "";
} else if (obj.action === "search_narrow") {
narrow.by_search_term(obj.query);

View File

@ -176,7 +176,7 @@ function hide_floating_recipient_bar() {
}
}
function update_floating_recipient_bar() {
exports.update_floating_recipient_bar = function () {
var top_statusbar = $("#top_statusbar");
var top_statusbar_top = top_statusbar.offset().top;
var top_statusbar_bottom = top_statusbar_top + top_statusbar.outerHeight();
@ -239,7 +239,8 @@ function update_floating_recipient_bar() {
}
replace_floating_recipient_bar(current_label);
}
};
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
@ -406,7 +407,7 @@ $(function () {
$(window).scroll($.throttle(50, function (e) {
if ($('#home').hasClass('active')) {
keep_pointer_in_view();
update_floating_recipient_bar();
exports.update_floating_recipient_bar();
if (viewport.scrollTop() === 0 &&
have_scrolled_away_from_top) {
have_scrolled_away_from_top = false;
@ -590,6 +591,7 @@ $(function () {
composebox_typeahead.initialize();
search.initialize();
notifications.initialize();
hashchange.initialize();
$("body").bind('click', function () {
ui.hide_userinfo_popover();

View File

@ -643,12 +643,12 @@ class GetOldMessagesTest(AuthedTestCase):
def test_bad_narrow_one_on_one_email_content(self):
"""
If an invalid one_on_one_email is requested in get_old_messages, an
If an invalid 'emails' is requested in get_old_messages, an
error is returned.
"""
self.login("hamlet@humbughq.com")
bad_stream_content = ("non-existent email", 0, [])
self.exercise_bad_narrow_content("one_on_one_email", bad_stream_content)
bad_stream_content = (["non-existent email"], "non-existent email", 0, [])
self.exercise_bad_narrow_content("emails", bad_stream_content)
class DummyHandler(object):
def __init__(self, assert_callback):

View File

@ -283,20 +283,28 @@ def get_old_messages_backend(request, anchor = POST(converter=to_non_negative_in
recipient = Recipient.objects.get(type=Recipient.STREAM, type_id=stream.id)
query = query.filter(recipient_id = recipient.id)
if 'one_on_one_email' in narrow:
if 'emails' in narrow and (type(narrow['emails']) != type([]) or len(narrow['emails']) == 0):
return json_error("Invalid emails %s" % (narrow['emails'],))
elif 'emails' in narrow and len(narrow['emails']) == 1:
query = query.filter(recipient__type=Recipient.PERSONAL)
try:
recipient_user = UserProfile.objects.get(user__email = narrow['one_on_one_email'])
recipient_user = UserProfile.objects.get(user__email = narrow['emails'][0])
except UserProfile.DoesNotExist:
return json_error("Invalid one_on_one_email %s" % (narrow['one_on_one_email'],))
return json_error("Invalid emails ['" + narrow['emails'][0] + "']")
recipient = Recipient.objects.get(type=Recipient.PERSONAL, type_id=recipient_user.id)
# If we are narrowed to personals with ourself, we want to search for personals where the user
# with address "one_on_one_email" is the sender *and* the recipient, not personals where the user
# with address "one_on_one_email is the sender *or* the recipient.
if narrow['one_on_one_email'] == user_profile.user.email:
query = query.filter(Q(sender__user__email=narrow['one_on_one_email']) & Q(recipient=recipient))
# with the desired e-mail address is the sender *and* the recipient, not personals where the
# user with the desired e-mail address is the sender *or* the recipient.
if narrow['emails'][0] == user_profile.user.email:
query = query.filter(Q(sender__user__email=narrow['emails'][0]) & Q(recipient=recipient))
else:
query = query.filter(Q(sender__user__email=narrow['one_on_one_email']) | Q(recipient=recipient))
query = query.filter(Q(sender__user__email=narrow['emails'][0]) | Q(recipient=recipient))
elif 'emails' in narrow:
try:
recipient = recipient_for_emails(narrow['emails'], False, user_profile, user_profile)
except ValidationError, e:
return json_error(e.messages[0])
query = query.filter(recipient=recipient)
elif 'type' in narrow and (narrow['type'] == "private" or narrow['type'] == "all_private_messages"):
query = query.filter(Q(recipient__type=Recipient.PERSONAL) | Q(recipient__type=Recipient.HUDDLE))
@ -611,6 +619,32 @@ def create_mirrored_message_users(request, user_profile, recipients):
sender = UserProfile.objects.get(user__email=sender_email)
return (True, sender)
def recipient_for_emails(emails, not_forged_zephyr_mirror, user_profile, sender):
recipient_profile_ids = set()
for email in emails:
try:
recipient_profile_ids.add(UserProfile.objects.get(user__email__iexact=email).id)
except UserProfile.DoesNotExist:
raise ValidationError("Invalid email '%s'" % (email,))
if not_forged_zephyr_mirror and user_profile.id not in recipient_profile_ids:
raise ValidationError("User not authorized for this query")
# If the private message is just between the sender and
# another person, force it to be a personal internally
if (len(recipient_profile_ids) == 2
and sender.id in recipient_profile_ids):
recipient_profile_ids.remove(sender.id)
if len(recipient_profile_ids) > 1:
# Make sure the sender is included in huddle messages
recipient_profile_ids.add(sender.id)
huddle = get_huddle(list(recipient_profile_ids))
return Recipient.objects.get(type_id=huddle.id, type=Recipient.HUDDLE)
else:
return Recipient.objects.get(type_id=list(recipient_profile_ids)[0],
type=Recipient.PERSONAL)
# We do not @require_login for send_message_backend, since it is used
# both from the API and the web service. Code calling
# send_message_backend should either check the API key or check that
@ -683,31 +717,11 @@ def send_message_backend(request, user_profile, client,
return json_error("Stream does not exist")
recipient = Recipient.objects.get(type_id=stream.id, type=Recipient.STREAM)
elif message_type_name == 'private':
recipient_profile_ids = set()
for email in message_to:
not_forged_zephyr_mirror = client and client.name == "zephyr_mirror" and not forged
try:
recipient_profile_ids.add(UserProfile.objects.get(user__email__iexact=email).id)
except UserProfile.DoesNotExist:
return json_error("Invalid email '%s'" % (email,))
if client.name == "zephyr_mirror":
if user_profile.id not in recipient_profile_ids and not forged:
return json_error("User not authorized for this query")
# If the private message is just between the sender and
# another person, force it to be a personal internally
if (len(recipient_profile_ids) == 2
and sender.id in recipient_profile_ids):
recipient_profile_ids.remove(sender.id)
if len(recipient_profile_ids) > 1:
# Make sure the sender is included in huddle messages
recipient_profile_ids.add(sender.id)
huddle = get_huddle(list(recipient_profile_ids))
recipient = Recipient.objects.get(type_id=huddle.id, type=Recipient.HUDDLE)
else:
recipient = Recipient.objects.get(type_id=list(recipient_profile_ids)[0],
type=Recipient.PERSONAL)
recipient = recipient_for_emails(message_to, not_forged_zephyr_mirror, user_profile, sender)
except ValidationError, e:
return json_error(e.messages[0])
else:
return json_error("Invalid message type")