2012-10-18 20:12:04 +02:00
|
|
|
var narrow = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
2012-10-04 07:13:28 +02:00
|
|
|
// For tracking where you were before you narrowed.
|
2012-10-10 00:10:05 +02:00
|
|
|
var persistent_message_id = 0;
|
2012-10-04 07:13:28 +02:00
|
|
|
|
2012-10-15 18:57:24 +02:00
|
|
|
// For narrowing based on a particular message
|
2012-10-18 20:12:04 +02:00
|
|
|
var target_id = 0;
|
2012-10-15 18:57:24 +02:00
|
|
|
|
2012-12-19 23:58:02 +01:00
|
|
|
var filter_function = false;
|
|
|
|
var current_operators = false;
|
2012-10-24 00:29:06 +02:00
|
|
|
|
2012-10-18 20:12:04 +02:00
|
|
|
exports.active = function () {
|
|
|
|
// Cast to bool
|
2012-12-12 19:00:50 +01:00
|
|
|
return !!filter_function;
|
2012-10-18 20:12:04 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
exports.predicate = function () {
|
2012-11-09 01:06:07 +01:00
|
|
|
if (filter_function) {
|
|
|
|
return filter_function;
|
2012-10-18 20:12:04 +02:00
|
|
|
} else {
|
|
|
|
return function () { return true; };
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-12-19 23:58:02 +01:00
|
|
|
exports.operators = function () {
|
|
|
|
return current_operators;
|
|
|
|
};
|
|
|
|
|
2012-12-12 19:00:50 +01:00
|
|
|
var allow_collapse;
|
|
|
|
|
2012-11-18 22:53:50 +01:00
|
|
|
exports.allow_collapse = function () {
|
2012-12-12 19:00:50 +01:00
|
|
|
return (!filter_function) || allow_collapse;
|
2012-11-18 22:53:50 +01:00
|
|
|
};
|
|
|
|
|
2012-12-12 19:36:18 +01:00
|
|
|
/* Convert a list of operators to a string.
|
2012-12-12 19:00:50 +01:00
|
|
|
Each operator is a key-value pair like
|
|
|
|
|
|
|
|
['subject', 'my amazing subject']
|
|
|
|
|
|
|
|
These are not keys in a JavaScript object, because we
|
|
|
|
might need to support multiple operators of the same type. */
|
2012-12-12 19:36:18 +01:00
|
|
|
function unparse(operators) {
|
|
|
|
var parts = [];
|
|
|
|
$.each(operators, function (index, elem) {
|
|
|
|
var operator = elem[0];
|
|
|
|
if (operator === 'search') {
|
|
|
|
// Search terms are the catch-all case.
|
|
|
|
// All tokens that don't start with a known operator and
|
|
|
|
// a colon are glued together to form a search term.
|
|
|
|
parts.push(elem[1]);
|
|
|
|
} else {
|
|
|
|
// FIXME: URI encoding will look really ugly
|
2012-12-18 21:34:59 +01:00
|
|
|
parts.push(elem[0] + ':' + encodeURIComponent(elem[1]).toLowerCase());
|
2012-12-12 19:36:18 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return parts.join(' ');
|
|
|
|
}
|
|
|
|
|
2012-12-18 00:18:30 +01:00
|
|
|
// Convert a list of operators to a human-readable description.
|
|
|
|
exports.describe = function (operators) {
|
|
|
|
return $.map(operators, function (elem) {
|
|
|
|
var operand = elem[1];
|
|
|
|
switch (elem[0]) {
|
|
|
|
case 'is':
|
|
|
|
if (operand === 'private-message')
|
|
|
|
return 'private messages';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'stream':
|
|
|
|
return 'stream ' + operand;
|
|
|
|
|
|
|
|
case 'subject':
|
|
|
|
return 'subject ' + operand;
|
|
|
|
|
|
|
|
case 'pm-with':
|
|
|
|
return 'private messages with ' + operand;
|
|
|
|
|
|
|
|
case 'search':
|
|
|
|
return 'messages containing ' + operand;
|
|
|
|
}
|
|
|
|
return '(unknown operator)';
|
|
|
|
}).join(', ');
|
|
|
|
};
|
|
|
|
|
2012-12-17 23:18:16 +01:00
|
|
|
// Parse a string into a list of operators (see below).
|
|
|
|
exports.parse = function (str) {
|
|
|
|
var operators = [];
|
|
|
|
var search_term = [];
|
|
|
|
$.each(str.split(/ +/), function (idx, token) {
|
|
|
|
var parts, operator;
|
|
|
|
if (token.length === 0)
|
|
|
|
return;
|
|
|
|
parts = token.split(':');
|
|
|
|
if (parts.length > 1) {
|
|
|
|
// Looks like an operator.
|
|
|
|
// FIXME: Should we skip unknown operator names here?
|
|
|
|
operator = parts.shift();
|
|
|
|
operators.push([operator, decodeURIComponent(parts.join(':'))]);
|
|
|
|
} else {
|
|
|
|
// Looks like a normal search term.
|
|
|
|
search_term.push(token);
|
|
|
|
}
|
|
|
|
});
|
2012-12-18 00:28:31 +01:00
|
|
|
// NB: Callers of 'parse' can assume that the 'search' operator is last.
|
2012-12-17 23:18:16 +01:00
|
|
|
if (search_term.length > 0)
|
2012-12-18 21:34:59 +01:00
|
|
|
operators.push(['search', search_term.join(' ')]);
|
2012-12-17 23:18:16 +01:00
|
|
|
return operators;
|
|
|
|
};
|
|
|
|
|
2012-12-12 19:36:18 +01:00
|
|
|
// Build a filter function from a list of operators.
|
2012-12-18 21:34:59 +01:00
|
|
|
function build_filter(operators_mixed_case) {
|
|
|
|
var operators = [];
|
|
|
|
// We don't use $.map because it flattens returned arrays.
|
|
|
|
$.each(operators_mixed_case, function (idx, operator) {
|
|
|
|
operators.push([operator[0], operator[1].toLowerCase()]);
|
|
|
|
});
|
|
|
|
|
2012-12-12 19:00:50 +01:00
|
|
|
// FIXME: This is probably pretty slow.
|
|
|
|
// We could turn it into something more like a compiler:
|
|
|
|
// build JavaScript code in a string and then eval() it.
|
|
|
|
|
|
|
|
return function (message) {
|
|
|
|
var operand, i;
|
|
|
|
for (i=0; i<operators.length; i++) {
|
|
|
|
operand = operators[i][1];
|
|
|
|
switch (operators[i][0]) {
|
|
|
|
case 'is':
|
2012-12-19 22:30:57 +01:00
|
|
|
if (operand === 'private-message') {
|
2012-12-12 19:00:50 +01:00
|
|
|
if (message.type !== 'private')
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
2012-10-24 00:29:06 +02:00
|
|
|
|
2012-12-12 19:00:50 +01:00
|
|
|
case 'stream':
|
|
|
|
if ((message.type !== 'stream') ||
|
2012-12-18 21:34:59 +01:00
|
|
|
(message.display_recipient.toLowerCase() !== operand))
|
2012-12-12 19:00:50 +01:00
|
|
|
return false;
|
|
|
|
break;
|
2012-12-07 20:52:39 +01:00
|
|
|
|
2012-12-12 19:00:50 +01:00
|
|
|
case 'subject':
|
|
|
|
if ((message.type !== 'stream') ||
|
|
|
|
(message.subject.toLowerCase() !== operand))
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'pm-with':
|
|
|
|
if ((message.type !== 'private') ||
|
2012-12-18 21:34:59 +01:00
|
|
|
(message.reply_to.toLowerCase() !== operand))
|
2012-12-07 20:52:39 +01:00
|
|
|
return false;
|
2012-12-12 19:00:50 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'search':
|
2013-01-31 23:05:47 +01:00
|
|
|
var words = operand.trim().split(/\s+/);
|
|
|
|
var j;
|
|
|
|
for (j = 0; j < words.length; ++j) {
|
|
|
|
if (message.content.toLowerCase().indexOf(words[j]) === -1) {
|
|
|
|
if ((message.type !== 'stream') ||
|
|
|
|
(message.subject.toLowerCase().indexOf(words[j]) === -1)) {
|
|
|
|
return false;
|
|
|
|
}
|
2012-12-12 19:00:50 +01:00
|
|
|
}
|
2012-12-07 20:52:39 +01:00
|
|
|
}
|
2012-12-12 19:00:50 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// All filters passed.
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2012-12-12 20:22:18 +01:00
|
|
|
exports.activate = function (operators, opts) {
|
2012-12-12 19:00:50 +01:00
|
|
|
opts = $.extend({}, {
|
2013-01-08 20:01:38 +01:00
|
|
|
allow_collapse: true
|
2012-12-12 19:00:50 +01:00
|
|
|
}, opts);
|
2012-12-07 20:52:39 +01:00
|
|
|
|
2012-10-27 02:58:21 +02:00
|
|
|
var was_narrowed = exports.active();
|
|
|
|
|
2012-12-19 23:58:02 +01:00
|
|
|
filter_function = build_filter(operators);
|
|
|
|
current_operators = operators;
|
2012-12-12 19:00:50 +01:00
|
|
|
|
|
|
|
allow_collapse = opts.allow_collapse;
|
2012-10-03 20:49:58 +02:00
|
|
|
|
|
|
|
// Your pointer isn't changed when narrowed.
|
2012-10-31 16:31:57 +01:00
|
|
|
if (! was_narrowed) {
|
|
|
|
persistent_message_id = selected_message_id;
|
|
|
|
}
|
2012-10-03 20:49:58 +02:00
|
|
|
|
2012-10-26 16:59:38 +02:00
|
|
|
// Before we clear the table, check if anything was highlighted.
|
2012-11-14 20:52:53 +01:00
|
|
|
var highlighted = search.something_is_highlighted();
|
2012-10-26 16:59:38 +02:00
|
|
|
|
2013-01-30 17:37:02 +01:00
|
|
|
// If our message id is not in range of the loaded message list, we need to fetch the messages
|
|
|
|
// around the target message time
|
|
|
|
if (message_array.length > 0 && (selected_message_id < message_array[0].id ||
|
|
|
|
selected_message_id > message_array[message_array.length - 1])) {
|
2012-11-27 22:30:47 +01:00
|
|
|
load_old_messages(target_id, 200, 200, function (messages) {
|
|
|
|
// We do this work inside the load_old_messages
|
|
|
|
// continuation, to shorten the window with just 1 visible message
|
|
|
|
clear_table('zfilt');
|
2012-12-12 19:00:50 +01:00
|
|
|
add_to_table([message_dict[target_id]], 'zfilt', filter_function, 'bottom', allow_collapse);
|
2012-11-27 22:30:47 +01:00
|
|
|
// Select target_id so that we will correctly arrange messages
|
|
|
|
// surrounding the target message.
|
|
|
|
select_message_by_id(target_id, {then_scroll: false});
|
|
|
|
add_messages(messages, false);
|
|
|
|
}, true, true);
|
2012-11-19 17:43:46 +01:00
|
|
|
} else {
|
2012-11-27 22:30:47 +01:00
|
|
|
clear_table('zfilt');
|
2012-12-12 19:00:50 +01:00
|
|
|
add_to_table(message_array, 'zfilt', filter_function, 'bottom', allow_collapse);
|
2012-11-19 17:43:46 +01:00
|
|
|
}
|
2012-10-03 20:49:58 +02:00
|
|
|
|
|
|
|
// Show the new set of messages.
|
|
|
|
$("#main_div").addClass("narrowed_view");
|
2013-01-09 23:22:21 +01:00
|
|
|
$("#zfilt").addClass("focused_table");
|
2012-10-03 20:49:58 +02:00
|
|
|
$("#zhome").removeClass("focused_table");
|
2013-01-09 23:46:32 +01:00
|
|
|
$("#zfilt").css("opacity", 0).animate({opacity: 1});
|
2013-01-09 23:22:21 +01:00
|
|
|
|
|
|
|
reset_load_more_status();
|
2012-10-15 19:24:34 +02:00
|
|
|
// Indicate both which message is persistently selected and which
|
|
|
|
// is temporarily selected
|
2012-10-31 16:31:57 +01:00
|
|
|
select_message_by_id(persistent_message_id,
|
2012-11-27 21:15:29 +01:00
|
|
|
{then_scroll: false, for_narrow: false});
|
|
|
|
select_message_by_id(target_id, {then_scroll: true});
|
2012-10-26 16:59:38 +02:00
|
|
|
|
|
|
|
// If anything was highlighted before, try to rehighlight it.
|
|
|
|
if (highlighted) {
|
2012-11-14 20:52:53 +01:00
|
|
|
search.update_highlight_on_narrow();
|
2012-10-26 16:59:38 +02:00
|
|
|
}
|
2012-12-12 19:00:50 +01:00
|
|
|
|
2012-12-12 19:36:18 +01:00
|
|
|
// Put the narrow operators in the URL fragment and search bar
|
2012-12-12 19:00:50 +01:00
|
|
|
hashchange.save_narrow(operators);
|
2012-12-12 19:36:18 +01:00
|
|
|
$('#search_query').val(unparse(operators));
|
2012-12-18 23:38:55 +01:00
|
|
|
search.update_button_visibility();
|
2012-12-12 19:00:50 +01:00
|
|
|
};
|
2012-10-03 20:49:58 +02:00
|
|
|
|
2013-01-02 19:21:39 +01:00
|
|
|
// Activate narrowing with a single operator.
|
|
|
|
// This is just for syntactic convenience.
|
|
|
|
exports.by = function (operator, operand, opts) {
|
|
|
|
exports.activate([[operator, operand]], opts);
|
|
|
|
};
|
|
|
|
|
2012-10-19 18:57:04 +02:00
|
|
|
// This is the message we're about to select, within the narrowed view.
|
|
|
|
// But it won't necessarily be selected once the user un-narrows.
|
|
|
|
//
|
|
|
|
// FIXME: We probably don't need this variable, selected_message_id, *and*
|
|
|
|
// persistent_message_id.
|
2012-10-18 20:12:04 +02:00
|
|
|
exports.target = function (id) {
|
|
|
|
target_id = id;
|
|
|
|
};
|
2012-10-15 18:57:24 +02:00
|
|
|
|
2012-10-18 20:12:04 +02:00
|
|
|
exports.by_subject = function () {
|
|
|
|
var original = message_dict[target_id];
|
2012-10-24 05:04:42 +02:00
|
|
|
if (original.type !== 'stream') {
|
|
|
|
// Only stream messages have subjects, but the
|
|
|
|
// user wants us to narrow in some way.
|
|
|
|
exports.by_recipient();
|
2012-10-03 20:49:58 +02:00
|
|
|
return;
|
2012-10-24 05:04:42 +02:00
|
|
|
}
|
2012-12-12 20:34:47 +01:00
|
|
|
exports.activate([
|
|
|
|
['stream', original.display_recipient],
|
2012-12-18 21:34:59 +01:00
|
|
|
['subject', original.subject]
|
2013-01-08 20:01:38 +01:00
|
|
|
]);
|
2012-11-15 16:57:59 +01:00
|
|
|
};
|
|
|
|
|
2012-10-10 23:24:11 +02:00
|
|
|
// Called for the 'narrow by stream' hotkey.
|
2012-10-18 20:12:04 +02:00
|
|
|
exports.by_recipient = function () {
|
2012-10-19 19:00:46 +02:00
|
|
|
var message = message_dict[target_id];
|
2012-12-12 20:22:18 +01:00
|
|
|
var new_narrow, emails;
|
2012-10-19 19:00:46 +02:00
|
|
|
switch (message.type) {
|
2012-12-03 19:49:12 +01:00
|
|
|
case 'private':
|
2013-01-08 20:01:38 +01:00
|
|
|
exports.by('pm-with', message.reply_to);
|
2012-10-19 17:11:31 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'stream':
|
2013-01-02 19:21:39 +01:00
|
|
|
exports.by('stream', message.display_recipient);
|
2012-10-19 17:11:31 +02:00
|
|
|
break;
|
2012-10-03 20:49:58 +02:00
|
|
|
}
|
2012-10-18 20:12:04 +02:00
|
|
|
};
|
2012-10-03 20:49:58 +02:00
|
|
|
|
2012-12-12 20:36:05 +01:00
|
|
|
exports.deactivate = function () {
|
2012-12-12 19:00:50 +01:00
|
|
|
if (!filter_function) {
|
2012-10-03 20:49:58 +02:00
|
|
|
return;
|
|
|
|
}
|
2012-12-19 23:58:02 +01:00
|
|
|
filter_function = false;
|
|
|
|
current_operators = false;
|
2012-10-03 20:49:58 +02:00
|
|
|
|
|
|
|
$("#main_div").removeClass('narrowed_view');
|
2012-10-26 19:55:39 +02:00
|
|
|
$("#searchbox").removeClass('narrowed_view');
|
2013-01-09 23:22:21 +01:00
|
|
|
$("#zfilt").removeClass('focused_table');
|
|
|
|
$("#zhome").addClass('focused_table');
|
2013-01-09 23:46:32 +01:00
|
|
|
$("#zhome").css("opacity", 0).animate({opacity: 1});
|
|
|
|
|
2012-12-12 19:36:18 +01:00
|
|
|
$('#search_query').val('');
|
2013-01-09 23:22:21 +01:00
|
|
|
reset_load_more_status();
|
2012-10-03 20:49:58 +02:00
|
|
|
// Includes scrolling.
|
2012-10-22 19:05:06 +02:00
|
|
|
select_message_by_id(persistent_message_id, {then_scroll: true});
|
2013-01-11 16:57:17 +01:00
|
|
|
|
2012-11-14 20:52:53 +01:00
|
|
|
search.update_highlight_on_narrow();
|
2013-01-11 16:57:17 +01:00
|
|
|
|
2012-12-12 19:00:50 +01:00
|
|
|
hashchange.save_narrow();
|
2013-01-11 16:57:17 +01:00
|
|
|
|
|
|
|
scroll_to_selected();
|
2012-10-18 20:12:04 +02:00
|
|
|
};
|
|
|
|
|
2012-11-04 02:16:15 +01:00
|
|
|
exports.restore_home_state = function() {
|
|
|
|
// If we click on the Home link while already at Home, unnarrow.
|
|
|
|
// If we click on the Home link from another nav pane, just go
|
|
|
|
// back to the state you were in (possibly still narrowed) before
|
|
|
|
// you left the Home pane.
|
2013-01-16 21:42:35 +01:00
|
|
|
if ($('#gear-menu li[title="Home"]').hasClass("active")) {
|
2012-12-12 20:36:05 +01:00
|
|
|
exports.deactivate();
|
2012-11-04 02:16:15 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-10-18 20:12:04 +02:00
|
|
|
return exports;
|
|
|
|
|
|
|
|
}());
|