2012-10-18 20:12:04 +02:00
|
|
|
var narrow = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
2013-04-24 21:40:08 +02:00
|
|
|
var current_filter;
|
2014-02-13 18:49:44 +01:00
|
|
|
var unnarrow_times;
|
2012-10-24 00:29:06 +02:00
|
|
|
|
2013-08-09 02:05:23 +02:00
|
|
|
// A small concession to unit testing follows:
|
|
|
|
exports._set_current_filter = function (filter) {
|
|
|
|
current_filter = filter;
|
|
|
|
};
|
|
|
|
|
2012-10-18 20:12:04 +02:00
|
|
|
exports.active = function () {
|
2013-04-24 21:40:08 +02:00
|
|
|
return current_filter !== undefined;
|
2012-10-18 20:12:04 +02:00
|
|
|
};
|
|
|
|
|
2013-04-25 19:38:21 +02:00
|
|
|
exports.filter = function () {
|
|
|
|
return current_filter;
|
|
|
|
};
|
|
|
|
|
2012-10-18 20:12:04 +02:00
|
|
|
exports.predicate = function () {
|
2013-04-25 21:22:48 +02:00
|
|
|
if (current_filter === undefined) {
|
2012-10-18 20:12:04 +02:00
|
|
|
return function () { return true; };
|
|
|
|
}
|
2013-04-24 21:40:08 +02:00
|
|
|
return current_filter.predicate();
|
2012-10-18 20:12:04 +02:00
|
|
|
};
|
|
|
|
|
2012-12-19 23:58:02 +01:00
|
|
|
exports.operators = function () {
|
2013-04-24 21:40:08 +02:00
|
|
|
if (current_filter === undefined) {
|
2014-01-30 21:42:57 +01:00
|
|
|
return new Filter(page_params.narrow).operators();
|
2013-04-24 21:40:08 +02:00
|
|
|
}
|
|
|
|
return current_filter.operators();
|
2012-12-19 23:58:02 +01:00
|
|
|
};
|
|
|
|
|
2017-02-13 21:42:57 +01:00
|
|
|
exports.update_email = function (user_id, new_email) {
|
|
|
|
if (current_filter !== undefined) {
|
|
|
|
current_filter.update_email(user_id, new_email);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-02-05 19:25:30 +01:00
|
|
|
/* Operators we should send to the server. */
|
|
|
|
exports.public_operators = function () {
|
2013-04-24 21:40:08 +02:00
|
|
|
if (current_filter === undefined) {
|
|
|
|
return undefined;
|
2013-02-05 19:25:30 +01:00
|
|
|
}
|
2013-04-24 21:40:08 +02:00
|
|
|
return current_filter.public_operators();
|
2013-02-05 19:25:30 +01:00
|
|
|
};
|
|
|
|
|
2013-07-23 03:41:21 +02:00
|
|
|
exports.search_string = function () {
|
2013-08-22 01:29:28 +02:00
|
|
|
return Filter.unparse(exports.operators());
|
2013-07-23 03:41:21 +02:00
|
|
|
};
|
|
|
|
|
2013-02-27 19:24:56 +01:00
|
|
|
// Collect operators which appear only once into an object,
|
|
|
|
// and discard those which appear more than once.
|
|
|
|
function collect_single(operators) {
|
2013-08-07 23:56:51 +02:00
|
|
|
var seen = new Dict();
|
|
|
|
var result = new Dict();
|
2013-07-30 00:35:44 +02:00
|
|
|
_.each(operators, function (elem) {
|
2014-01-30 19:39:17 +01:00
|
|
|
var key = elem.operator;
|
2013-08-07 23:56:51 +02:00
|
|
|
if (seen.has(key)) {
|
|
|
|
result.del(key);
|
2013-02-27 19:24:56 +01:00
|
|
|
} else {
|
2014-01-30 19:39:17 +01:00
|
|
|
result.set(key, elem.operand);
|
2013-08-07 23:56:51 +02:00
|
|
|
seen.set(key, true);
|
2013-02-27 19:24:56 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Modify default compose parameters (stream etc.) based on
|
|
|
|
// the current narrowed view.
|
|
|
|
//
|
|
|
|
// This logic is here and not in the 'compose' module because
|
|
|
|
// it will get more complicated as we add things to the narrow
|
|
|
|
// operator language.
|
|
|
|
exports.set_compose_defaults = function (opts) {
|
|
|
|
var single = collect_single(exports.operators());
|
|
|
|
|
2013-11-27 16:43:00 +01:00
|
|
|
// Set the stream, subject, and/or PM recipient if they are
|
|
|
|
// uniquely specified in the narrow view.
|
2013-08-05 23:23:34 +02:00
|
|
|
|
2013-08-07 23:56:51 +02:00
|
|
|
if (single.has('stream')) {
|
2013-08-19 21:04:28 +02:00
|
|
|
opts.stream = stream_data.get_name(single.get('stream'));
|
2013-08-05 23:23:34 +02:00
|
|
|
}
|
2013-11-27 16:43:00 +01:00
|
|
|
|
|
|
|
if (single.has('topic')) {
|
|
|
|
opts.subject = single.get('topic');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (single.has('pm-with')) {
|
|
|
|
opts.private_message_recipient = single.get('pm-with');
|
|
|
|
}
|
2013-02-27 19:24:56 +01:00
|
|
|
};
|
|
|
|
|
2013-04-10 23:38:30 +02:00
|
|
|
exports.stream = function () {
|
2013-04-24 21:40:08 +02:00
|
|
|
if (current_filter === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
2013-06-13 21:03:01 +02:00
|
|
|
var stream_operands = current_filter.operands("stream");
|
|
|
|
if (stream_operands.length === 1) {
|
|
|
|
return stream_operands[0];
|
2013-04-10 23:38:30 +02:00
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
};
|
|
|
|
|
2016-11-10 18:43:34 +01:00
|
|
|
exports.topic = function () {
|
|
|
|
if (current_filter === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
var operands = current_filter.operands("topic");
|
|
|
|
if (operands.length === 1) {
|
|
|
|
return operands[0];
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
};
|
|
|
|
|
2013-12-06 00:03:08 +01:00
|
|
|
function report_narrow_time(initial_core_time, initial_free_time, network_time) {
|
2013-12-18 19:55:18 +01:00
|
|
|
channel.post({
|
2013-12-06 00:03:08 +01:00
|
|
|
url: '/json/report_narrow_time',
|
2016-12-03 03:08:47 +01:00
|
|
|
data: {initial_core: initial_core_time.toString(),
|
|
|
|
initial_free: initial_free_time.toString(),
|
2017-01-12 00:17:43 +01:00
|
|
|
network: network_time.toString()},
|
2013-12-06 00:03:08 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function maybe_report_narrow_time(msg_list) {
|
|
|
|
if (msg_list.network_time === undefined || msg_list.initial_core_time === undefined ||
|
|
|
|
msg_list.initial_free_time === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
report_narrow_time(msg_list.initial_core_time - msg_list.start_time,
|
|
|
|
msg_list.initial_free_time - msg_list.start_time,
|
|
|
|
msg_list.network_time - msg_list.start_time);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-02-13 18:49:44 +01:00
|
|
|
function report_unnarrow_time() {
|
|
|
|
if (unnarrow_times === undefined ||
|
|
|
|
unnarrow_times.start_time === undefined ||
|
|
|
|
unnarrow_times.initial_core_time === undefined ||
|
|
|
|
unnarrow_times.initial_free_time === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var initial_core_time = unnarrow_times.initial_core_time - unnarrow_times.start_time;
|
|
|
|
var initial_free_time = unnarrow_times.initial_free_time - unnarrow_times.start_time;
|
|
|
|
|
|
|
|
channel.post({
|
|
|
|
url: '/json/report_unnarrow_time',
|
2016-12-03 03:08:47 +01:00
|
|
|
data: {initial_core: initial_core_time.toString(),
|
2017-01-12 00:17:43 +01:00
|
|
|
initial_free: initial_free_time.toString()},
|
2014-02-13 18:49:44 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
unnarrow_times = {};
|
|
|
|
}
|
|
|
|
|
2015-12-10 20:37:01 +01:00
|
|
|
exports.narrow_title = "home";
|
2014-01-30 20:54:43 +01:00
|
|
|
exports.activate = function (raw_operators, opts) {
|
2013-12-06 00:03:08 +01:00
|
|
|
var start_time = new Date();
|
2014-02-12 20:03:05 +01:00
|
|
|
var was_narrowed_already = exports.active();
|
2013-10-09 22:42:15 +02:00
|
|
|
// most users aren't going to send a bunch of a out-of-narrow messages
|
|
|
|
// and expect to visit a list of narrows, so let's get these out of the way.
|
|
|
|
notifications.clear_compose_notifications();
|
|
|
|
|
2014-01-30 20:54:43 +01:00
|
|
|
if (raw_operators.length === 0) {
|
2013-07-19 21:03:46 +02:00
|
|
|
return exports.deactivate();
|
|
|
|
}
|
2014-01-30 20:54:43 +01:00
|
|
|
var filter = new Filter(raw_operators);
|
|
|
|
var operators = filter.operators();
|
|
|
|
|
2015-12-10 20:37:01 +01:00
|
|
|
// Take the most detailed part of the narrow to use as the title.
|
|
|
|
// If the operator is something other than "stream", "topic", or
|
|
|
|
// "is", we shouldn't update the narrow title
|
|
|
|
if (filter.has_operator("stream")) {
|
|
|
|
if (filter.has_operator("topic")) {
|
|
|
|
exports.narrow_title = filter.operands("topic")[0];
|
|
|
|
} else {
|
|
|
|
exports.narrow_title = filter.operands("stream")[0];
|
|
|
|
}
|
|
|
|
} else if (filter.has_operator("is")) {
|
|
|
|
exports.narrow_title = filter.operands("is")[0];
|
|
|
|
} else if (filter.has_operator("pm-with")) {
|
|
|
|
exports.narrow_title = "private";
|
|
|
|
}
|
|
|
|
|
|
|
|
notifications.redraw_title();
|
|
|
|
|
2014-01-30 20:54:43 +01:00
|
|
|
blueslip.debug("Narrowed", {operators: _.map(operators,
|
|
|
|
function (e) { return e.operator; }),
|
2013-11-05 20:26:03 +01:00
|
|
|
trigger: opts ? opts.trigger : undefined,
|
2014-02-03 22:48:25 +01:00
|
|
|
previous_id: current_msg_list.selected_id()});
|
2013-07-19 21:03:46 +02:00
|
|
|
|
2013-10-10 16:43:49 +02:00
|
|
|
var had_message_content = compose.has_message_content();
|
|
|
|
|
|
|
|
if (!had_message_content) {
|
|
|
|
compose.cancel();
|
2016-06-09 23:02:49 +02:00
|
|
|
} else {
|
2013-11-27 20:01:58 +01:00
|
|
|
compose_fade.update_message_list();
|
|
|
|
}
|
2013-10-10 15:08:17 +02:00
|
|
|
|
2013-07-30 05:11:50 +02:00
|
|
|
opts = _.defaults({}, opts, {
|
2013-03-14 18:27:58 +01:00
|
|
|
then_select_id: home_msg_list.selected_id(),
|
2013-03-19 23:45:51 +01:00
|
|
|
select_first_unread: false,
|
2014-01-31 20:19:12 +01:00
|
|
|
first_unread_from_server: false,
|
2014-02-12 20:03:05 +01:00
|
|
|
from_reload: false,
|
2013-05-21 19:34:15 +02:00
|
|
|
change_hash: true,
|
2017-01-12 00:17:43 +01:00
|
|
|
trigger: 'unknown',
|
2013-07-30 05:11:50 +02:00
|
|
|
});
|
2013-07-31 20:33:38 +02:00
|
|
|
if (filter.has_operator("near")) {
|
2014-01-27 17:16:26 +01:00
|
|
|
opts.then_select_id = parseInt(filter.operands("near")[0], 10);
|
2013-07-31 20:33:38 +02:00
|
|
|
opts.select_first_unread = false;
|
|
|
|
}
|
2013-07-31 20:54:51 +02:00
|
|
|
if (filter.has_operator("id")) {
|
2014-01-27 17:16:26 +01:00
|
|
|
opts.then_select_id = parseInt(filter.operands("id")[0], 10);
|
2013-07-31 20:54:51 +02:00
|
|
|
opts.select_first_unread = false;
|
|
|
|
}
|
2012-12-07 20:52:39 +01:00
|
|
|
|
2014-01-31 20:19:12 +01:00
|
|
|
if (opts.then_select_id === -1 && !opts.first_unread_from_server) {
|
|
|
|
// According to old comments, this shouldn't happen anymore
|
2014-02-05 05:07:13 +01:00
|
|
|
blueslip.warn("Setting then_select_id to page_params.initial_pointer.");
|
2013-04-11 00:13:28 +02:00
|
|
|
opts.then_select_id = page_params.initial_pointer;
|
2013-07-17 18:07:06 +02:00
|
|
|
opts.select_first_unread = false;
|
2013-04-11 00:13:28 +02:00
|
|
|
}
|
|
|
|
|
2013-07-01 18:16:05 +02:00
|
|
|
var then_select_id = opts.then_select_id;
|
|
|
|
var then_select_offset;
|
2013-11-26 19:06:21 +01:00
|
|
|
|
|
|
|
if (!was_narrowed_already) {
|
2016-04-03 16:45:07 +02:00
|
|
|
unread.messages_read_in_narrow = false;
|
2013-11-26 19:06:21 +01:00
|
|
|
}
|
|
|
|
|
2013-08-14 22:00:32 +02:00
|
|
|
if (!opts.select_first_unread && current_msg_list.get_row(then_select_id).length > 0) {
|
2014-01-28 20:29:18 +01:00
|
|
|
then_select_offset = current_msg_list.get_row(then_select_id).offset().top;
|
2013-07-01 18:16:05 +02:00
|
|
|
}
|
2012-10-27 02:58:21 +02:00
|
|
|
|
2013-09-22 21:11:43 +02:00
|
|
|
// For legacy reasons, we need to set current_filter before calling
|
|
|
|
// muting_enabled.
|
2013-07-31 20:33:38 +02:00
|
|
|
current_filter = filter;
|
2013-09-22 21:11:43 +02:00
|
|
|
var muting_enabled = exports.muting_enabled();
|
2012-12-12 19:00:50 +01:00
|
|
|
|
2013-07-03 21:39:41 +02:00
|
|
|
// Save how far from the pointer the top of the message list was.
|
|
|
|
if (current_msg_list.selected_id() !== -1) {
|
2013-10-31 18:01:52 +01:00
|
|
|
if (current_msg_list.selected_row().length === 0) {
|
|
|
|
blueslip.debug("narrow.activate missing selected row", {
|
|
|
|
selected_id: current_msg_list.selected_id(),
|
|
|
|
selected_idx: current_msg_list.selected_idx(),
|
2016-12-02 15:16:33 +01:00
|
|
|
selected_idx_exact: current_msg_list._items.indexOf(
|
|
|
|
current_msg_list.get(current_msg_list.selected_id())),
|
2013-10-31 18:01:52 +01:00
|
|
|
render_start: current_msg_list.view._render_win_start,
|
2017-01-12 00:17:43 +01:00
|
|
|
render_end: current_msg_list.view._render_win_end,
|
2013-10-31 18:01:52 +01:00
|
|
|
});
|
|
|
|
}
|
2014-01-28 20:29:18 +01:00
|
|
|
current_msg_list.pre_narrow_offset = current_msg_list.selected_row().offset().top;
|
2013-07-03 21:39:41 +02:00
|
|
|
}
|
2013-07-25 22:08:16 +02:00
|
|
|
|
2014-02-12 20:03:05 +01:00
|
|
|
if (opts.first_unread_from_server && opts.from_reload) {
|
|
|
|
then_select_id = page_params.initial_narrow_pointer;
|
|
|
|
then_select_offset = page_params.initial_narrow_offset;
|
|
|
|
opts.first_unread_from_server = false;
|
|
|
|
opts.select_first_unread = false;
|
|
|
|
home_msg_list.pre_narrow_offset = page_params.initial_offset;
|
|
|
|
}
|
|
|
|
|
2016-04-20 22:37:26 +02:00
|
|
|
var msg_list = new message_list.MessageList('zfilt', current_filter, {
|
2013-07-25 22:08:16 +02:00
|
|
|
collapse_messages: ! current_filter.is_search(),
|
2017-01-12 00:17:43 +01:00
|
|
|
muting_enabled: muting_enabled,
|
2013-07-25 22:08:16 +02:00
|
|
|
});
|
2013-12-06 00:03:08 +01:00
|
|
|
msg_list.start_time = start_time;
|
2013-07-03 21:39:41 +02:00
|
|
|
|
2013-12-09 19:20:55 +01:00
|
|
|
// Show the new set of messages. It is important to set current_msg_list to
|
|
|
|
// the view right as it's being shown, because we rely on current_msg_list
|
|
|
|
// being shown for deciding when to condense messages.
|
|
|
|
$("body").addClass("narrowed_view");
|
|
|
|
$("#zfilt").addClass("focused_table");
|
|
|
|
$("#zhome").removeClass("focused_table");
|
2016-06-18 19:34:20 +02:00
|
|
|
|
|
|
|
ui.change_tab_to('#home');
|
2016-04-25 23:45:25 +02:00
|
|
|
message_list.narrowed = msg_list;
|
|
|
|
current_msg_list = message_list.narrowed;
|
2013-02-12 20:01:24 +01:00
|
|
|
|
2013-02-20 18:18:15 +01:00
|
|
|
function maybe_select_closest() {
|
2016-04-25 23:45:25 +02:00
|
|
|
if (! message_list.narrowed.empty()) {
|
2013-03-14 18:27:58 +01:00
|
|
|
if (opts.select_first_unread) {
|
2016-04-25 23:45:25 +02:00
|
|
|
then_select_id = message_list.narrowed.last().id;
|
2016-12-02 15:16:33 +01:00
|
|
|
var first_unread =
|
|
|
|
_.find(message_list.narrowed.all_messages(), unread.message_unread);
|
2013-08-01 17:47:48 +02:00
|
|
|
if (first_unread) {
|
2013-07-31 20:35:48 +02:00
|
|
|
then_select_id = first_unread.id;
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2013-03-14 18:27:58 +01:00
|
|
|
}
|
2013-07-01 18:16:05 +02:00
|
|
|
|
2013-07-09 04:54:23 +02:00
|
|
|
var preserve_pre_narrowing_screen_position =
|
|
|
|
!opts.select_first_unread &&
|
2016-04-25 23:45:25 +02:00
|
|
|
(message_list.narrowed.get(then_select_id) !== undefined) &&
|
2013-07-09 04:54:23 +02:00
|
|
|
(then_select_offset !== undefined);
|
|
|
|
|
|
|
|
var then_scroll = !preserve_pre_narrowing_screen_position;
|
|
|
|
|
2016-04-25 23:45:25 +02:00
|
|
|
message_list.narrowed.select_id(then_select_id, {then_scroll: then_scroll,
|
2014-01-22 22:20:36 +01:00
|
|
|
use_closest: true,
|
2017-01-12 00:17:43 +01:00
|
|
|
force_rerender: true,
|
2013-07-11 17:14:11 +02:00
|
|
|
});
|
2013-07-09 04:54:23 +02:00
|
|
|
|
|
|
|
if (preserve_pre_narrowing_screen_position) {
|
2013-07-01 18:16:05 +02:00
|
|
|
// Scroll so that the selected message is in the same
|
|
|
|
// position in the viewport as it was prior to
|
|
|
|
// narrowing
|
2014-01-28 20:29:18 +01:00
|
|
|
viewport.set_message_offset(then_select_offset);
|
2013-07-01 18:16:05 +02:00
|
|
|
}
|
2013-02-20 18:18:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-20 22:59:56 +01:00
|
|
|
// Don't bother populating a message list when it won't contain
|
2013-04-25 19:38:21 +02:00
|
|
|
// the message we want anyway or if the filter can't be applied
|
|
|
|
// locally.
|
2016-04-21 22:49:23 +02:00
|
|
|
if (message_list.all.get(then_select_id) !== undefined && current_filter.can_apply_locally()) {
|
2016-12-02 15:16:33 +01:00
|
|
|
message_store.add_messages(message_list.all.all_messages(), message_list.narrowed,
|
|
|
|
{delay_render: true});
|
2013-02-20 22:59:56 +01:00
|
|
|
}
|
|
|
|
|
2016-04-25 23:45:25 +02:00
|
|
|
var defer_selecting_closest = message_list.narrowed.empty();
|
2014-01-31 16:27:24 +01:00
|
|
|
message_store.load_old_messages({
|
2013-12-19 17:03:08 +01:00
|
|
|
anchor: then_select_id.toFixed(),
|
2013-06-14 23:47:37 +02:00
|
|
|
num_before: 50,
|
|
|
|
num_after: 50,
|
2016-04-25 23:45:25 +02:00
|
|
|
msg_list: message_list.narrowed,
|
2014-01-31 20:19:12 +01:00
|
|
|
use_first_unread_anchor: opts.first_unread_from_server,
|
2016-12-15 07:26:09 +01:00
|
|
|
cont: function () {
|
2014-01-22 22:20:36 +01:00
|
|
|
ui.hide_loading_more_messages_indicator();
|
2013-04-10 17:55:02 +02:00
|
|
|
if (defer_selecting_closest) {
|
2013-03-14 19:42:37 +01:00
|
|
|
maybe_select_closest();
|
2013-04-10 17:55:02 +02:00
|
|
|
}
|
2013-12-06 00:03:08 +01:00
|
|
|
msg_list.network_time = new Date();
|
|
|
|
maybe_report_narrow_time(msg_list);
|
2013-04-10 17:55:02 +02:00
|
|
|
},
|
2017-01-12 00:17:43 +01:00
|
|
|
cont_will_add_messages: false,
|
2013-04-10 17:55:02 +02:00
|
|
|
});
|
2013-02-20 19:35:15 +01:00
|
|
|
|
2013-04-10 17:55:02 +02:00
|
|
|
if (! defer_selecting_closest) {
|
2014-01-31 16:27:24 +01:00
|
|
|
message_store.reset_load_more_status();
|
2013-04-10 17:55:02 +02:00
|
|
|
maybe_select_closest();
|
2014-01-22 22:20:36 +01:00
|
|
|
} else {
|
|
|
|
ui.show_loading_more_messages_indicator();
|
2013-04-10 17:55:02 +02:00
|
|
|
}
|
2012-10-26 16:59:38 +02:00
|
|
|
|
2013-03-19 23:45:51 +01:00
|
|
|
// Put the narrow operators in the URL fragment.
|
|
|
|
// Disabled when the URL fragment was the source
|
|
|
|
// of this narrow.
|
2013-08-01 17:47:48 +02:00
|
|
|
if (opts.change_hash) {
|
2013-03-19 23:45:51 +01:00
|
|
|
hashchange.save_narrow(operators);
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2013-03-19 23:45:51 +01:00
|
|
|
|
|
|
|
// Put the narrow operators in the search bar.
|
2013-08-22 01:29:28 +02:00
|
|
|
$('#search_query').val(Filter.unparse(operators));
|
2012-12-18 23:38:55 +01:00
|
|
|
search.update_button_visibility();
|
2013-10-10 15:54:18 +02:00
|
|
|
|
2013-10-10 16:43:49 +02:00
|
|
|
if (!had_message_content && opts.trigger === 'sidebar' && exports.narrowed_by_reply()) {
|
2013-10-10 15:54:18 +02:00
|
|
|
if (exports.narrowed_to_topic()) {
|
|
|
|
compose.start('stream');
|
2016-06-09 23:02:49 +02:00
|
|
|
} else {
|
2013-10-10 15:54:18 +02:00
|
|
|
compose.start('private');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-25 23:45:25 +02:00
|
|
|
$(document).trigger($.Event('narrow_activated.zulip', {msg_list: message_list.narrowed,
|
2013-05-21 19:34:15 +02:00
|
|
|
filter: current_filter,
|
|
|
|
trigger: opts.trigger}));
|
2013-12-06 00:03:08 +01:00
|
|
|
msg_list.initial_core_time = new Date();
|
|
|
|
setTimeout(function () {
|
|
|
|
msg_list.initial_free_time = new Date();
|
|
|
|
maybe_report_narrow_time(msg_list);
|
|
|
|
}, 0);
|
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) {
|
2014-02-10 20:53:38 +01:00
|
|
|
exports.activate([{operator: operator, operand: operand}], opts);
|
2013-02-09 08:27:20 +01:00
|
|
|
};
|
|
|
|
|
2013-05-21 19:34:15 +02:00
|
|
|
exports.by_subject = function (target_id, opts) {
|
2013-10-09 22:42:15 +02:00
|
|
|
// don't use current_msg_list as it won't work for muted messages or for out-of-narrow links
|
2014-01-31 22:02:57 +01:00
|
|
|
var original = message_store.get(target_id);
|
2012-10-24 05:04:42 +02:00
|
|
|
if (original.type !== 'stream') {
|
2016-10-28 19:07:25 +02:00
|
|
|
// Only stream messages have topics, but the
|
2012-10-24 05:04:42 +02:00
|
|
|
// user wants us to narrow in some way.
|
2013-05-21 19:34:15 +02:00
|
|
|
exports.by_recipient(target_id, opts);
|
2012-10-03 20:49:58 +02:00
|
|
|
return;
|
2012-10-24 05:04:42 +02:00
|
|
|
}
|
2017-02-11 09:55:18 +01:00
|
|
|
unread_ui.mark_message_as_read(original);
|
2014-02-10 20:53:38 +01:00
|
|
|
var search_terms = [
|
|
|
|
{operator: 'stream', operand: original.stream},
|
2017-01-12 00:17:43 +01:00
|
|
|
{operator: 'topic', operand: original.subject},
|
2014-02-10 20:53:38 +01:00
|
|
|
];
|
2013-07-30 05:11:50 +02:00
|
|
|
opts = _.defaults({}, opts, {then_select_id: target_id});
|
2014-02-10 20:53:38 +01:00
|
|
|
exports.activate(search_terms, opts);
|
2012-11-15 16:57:59 +01:00
|
|
|
};
|
|
|
|
|
2012-10-10 23:24:11 +02:00
|
|
|
// Called for the 'narrow by stream' hotkey.
|
2013-05-21 19:34:15 +02:00
|
|
|
exports.by_recipient = function (target_id, opts) {
|
2013-07-30 05:11:50 +02:00
|
|
|
opts = _.defaults({}, opts, {then_select_id: target_id});
|
2013-10-09 22:42:15 +02:00
|
|
|
// don't use current_msg_list as it won't work for muted messages or for out-of-narrow links
|
2014-01-31 22:02:57 +01:00
|
|
|
var message = message_store.get(target_id);
|
2017-02-11 09:55:18 +01:00
|
|
|
unread_ui.mark_message_as_read(message);
|
2012-10-19 19:00:46 +02:00
|
|
|
switch (message.type) {
|
2012-12-03 19:49:12 +01:00
|
|
|
case 'private':
|
2013-05-21 19:34:15 +02:00
|
|
|
exports.by('pm-with', message.reply_to, opts);
|
2012-10-19 17:11:31 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'stream':
|
2013-05-21 19:34:15 +02:00
|
|
|
exports.by('stream', message.stream, opts);
|
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
|
|
|
|
2013-05-21 19:34:15 +02:00
|
|
|
exports.by_time_travel = function (target_id, opts) {
|
2013-07-30 05:11:50 +02:00
|
|
|
opts = _.defaults({}, opts, {then_select_id: target_id});
|
2014-02-10 20:53:38 +01:00
|
|
|
narrow.activate([{operator: "near", operand: target_id}], opts);
|
2013-02-28 23:21:37 +01:00
|
|
|
};
|
|
|
|
|
2013-10-10 22:05:20 +02:00
|
|
|
exports.by_id = function (target_id, opts) {
|
|
|
|
opts = _.defaults({}, opts, {then_select_id: target_id});
|
2014-02-10 20:53:38 +01:00
|
|
|
narrow.activate([{operator: "id", operand: target_id}], opts);
|
2013-10-10 22:05:20 +02:00
|
|
|
};
|
|
|
|
|
2013-11-25 16:54:08 +01:00
|
|
|
exports.by_conversation_and_time = function (target_id, opts) {
|
2014-02-10 20:53:38 +01:00
|
|
|
var args = [{operator: "near", operand: target_id}];
|
2014-01-31 22:02:57 +01:00
|
|
|
var original = message_store.get(target_id);
|
2013-11-25 16:54:08 +01:00
|
|
|
opts = _.defaults({}, opts, {then_select_id: target_id});
|
|
|
|
|
|
|
|
if (original.type !== 'stream') {
|
2014-02-10 20:53:38 +01:00
|
|
|
args.push({operator: "pm-with", operand: original.reply_to});
|
2013-11-25 16:54:08 +01:00
|
|
|
} else {
|
2014-02-10 20:53:38 +01:00
|
|
|
args.push({operator: 'stream', operand: original.stream});
|
|
|
|
args.push({operator: 'topic', operand: original.subject});
|
2013-11-25 16:54:08 +01:00
|
|
|
}
|
|
|
|
narrow.activate(args, opts);
|
|
|
|
};
|
|
|
|
|
2012-12-12 20:36:05 +01:00
|
|
|
exports.deactivate = function () {
|
2013-04-24 21:40:08 +02:00
|
|
|
if (current_filter === undefined) {
|
2012-10-03 20:49:58 +02:00
|
|
|
return;
|
|
|
|
}
|
2014-02-13 18:49:44 +01:00
|
|
|
unnarrow_times = {start_time: new Date()};
|
2013-10-30 18:38:16 +01:00
|
|
|
blueslip.debug("Unnarrowed");
|
2013-04-24 21:40:08 +02:00
|
|
|
|
2013-07-16 20:00:47 +02:00
|
|
|
if (ui.actively_scrolling()) {
|
|
|
|
// There is no way to intercept in-flight scroll events, and they will
|
|
|
|
// cause you to end up in the wrong place if you are actively scrolling
|
|
|
|
// on an unnarrow. Wait a bit and try again once the scrolling is over.
|
|
|
|
setTimeout(exports.deactivate, 50);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-10 16:43:49 +02:00
|
|
|
if (!compose.has_message_content()) {
|
|
|
|
compose.cancel();
|
|
|
|
}
|
2013-10-10 15:08:17 +02:00
|
|
|
|
2013-04-24 21:40:08 +02:00
|
|
|
current_filter = undefined;
|
2012-10-03 20:49:58 +02:00
|
|
|
|
2013-03-29 19:55:28 +01:00
|
|
|
exports.hide_empty_narrow_message();
|
2013-02-23 19:38:25 +01:00
|
|
|
|
2013-07-17 00:52:18 +02:00
|
|
|
$("body").removeClass('narrowed_view');
|
2013-01-09 23:22:21 +01:00
|
|
|
$("#zfilt").removeClass('focused_table');
|
|
|
|
$("#zhome").addClass('focused_table');
|
2013-12-09 19:20:55 +01:00
|
|
|
current_msg_list = home_msg_list;
|
2014-03-13 20:22:09 +01:00
|
|
|
condense.condense_and_collapse($("#zhome tr.message_row"));
|
2013-01-09 23:46:32 +01:00
|
|
|
|
2012-12-12 19:36:18 +01:00
|
|
|
$('#search_query').val('');
|
2014-01-31 16:27:24 +01:00
|
|
|
message_store.reset_load_more_status();
|
2014-01-15 17:48:40 +01:00
|
|
|
hashchange.save_narrow();
|
2013-02-14 23:48:37 +01:00
|
|
|
|
2013-12-02 19:16:37 +01:00
|
|
|
if (current_msg_list.selected_id() !== -1) {
|
|
|
|
var preserve_pre_narrowing_screen_position =
|
|
|
|
(current_msg_list.selected_row().length > 0) &&
|
|
|
|
(current_msg_list.pre_narrow_offset !== undefined);
|
2014-02-04 22:13:34 +01:00
|
|
|
var message_id_to_select;
|
|
|
|
var select_opts = {
|
|
|
|
then_scroll: true,
|
|
|
|
use_closest: true,
|
2017-01-12 00:17:43 +01:00
|
|
|
empty_ok: true,
|
2014-02-04 22:13:34 +01:00
|
|
|
};
|
2013-12-02 19:16:37 +01:00
|
|
|
|
2014-02-04 22:13:34 +01:00
|
|
|
// We fall back to the closest selected id, if the user has removed a
|
|
|
|
// stream from the home view since leaving it the old selected id might
|
|
|
|
// no longer be there
|
|
|
|
// Additionally, we pass empty_ok as the user may have removed **all** streams
|
|
|
|
// from her home view
|
2016-04-03 16:45:07 +02:00
|
|
|
if (unread.messages_read_in_narrow) {
|
2013-11-26 19:06:21 +01:00
|
|
|
// We read some unread messages in a narrow. Instead of going back to
|
|
|
|
// where we were before the narrow, go to our first unread message (or
|
|
|
|
// the bottom of the feed, if there are no unread messages).
|
2016-04-23 00:56:44 +02:00
|
|
|
var first_unread = _.find(current_msg_list.all_messages(), unread.message_unread);
|
2013-11-26 19:06:21 +01:00
|
|
|
if (first_unread) {
|
2014-02-04 22:13:34 +01:00
|
|
|
message_id_to_select = first_unread.id;
|
2013-11-26 19:06:21 +01:00
|
|
|
} else {
|
2014-02-04 22:13:34 +01:00
|
|
|
message_id_to_select = current_msg_list.last().id;
|
2013-11-26 19:06:21 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// We narrowed, but only backwards in time (ie no unread were read). Try
|
|
|
|
// to go back to exactly where we were before narrowing.
|
2014-01-09 22:57:53 +01:00
|
|
|
if (preserve_pre_narrowing_screen_position) {
|
|
|
|
// We scroll the user back to exactly the offset from the selected
|
|
|
|
// message that he was at the time that he narrowed.
|
|
|
|
// TODO: Make this correctly handle the case of resizing while narrowed.
|
2014-02-04 22:13:34 +01:00
|
|
|
select_opts.target_scroll_offset = current_msg_list.pre_narrow_offset;
|
2014-01-09 22:57:53 +01:00
|
|
|
}
|
2014-02-04 22:13:34 +01:00
|
|
|
message_id_to_select = current_msg_list.selected_id();
|
2013-12-02 19:16:37 +01:00
|
|
|
}
|
2014-02-04 22:13:34 +01:00
|
|
|
current_msg_list.select_id(message_id_to_select, select_opts);
|
2013-07-03 21:39:41 +02:00
|
|
|
}
|
2013-01-11 16:57:17 +01:00
|
|
|
|
2013-08-12 17:40:55 +02:00
|
|
|
compose_fade.update_message_list();
|
2013-01-11 16:57:17 +01:00
|
|
|
|
2013-07-25 22:48:55 +02:00
|
|
|
$(document).trigger($.Event('narrow_deactivated.zulip', {msg_list: current_msg_list}));
|
2014-02-13 18:49:44 +01:00
|
|
|
|
2015-12-10 20:37:01 +01:00
|
|
|
exports.narrow_title = "home";
|
|
|
|
notifications.redraw_title();
|
|
|
|
|
2014-02-13 18:49:44 +01:00
|
|
|
unnarrow_times.initial_core_time = new Date();
|
|
|
|
setTimeout(function () {
|
|
|
|
unnarrow_times.initial_free_time = new Date();
|
|
|
|
report_unnarrow_time();
|
|
|
|
});
|
2012-10-18 20:12:04 +02:00
|
|
|
};
|
|
|
|
|
2013-07-05 17:43:56 +02:00
|
|
|
exports.restore_home_state = function () {
|
2012-11-04 02:16:15 +01:00
|
|
|
// 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-03-13 05:39:33 +01:00
|
|
|
if (!ui.home_tab_obscured()) {
|
2012-12-12 20:36:05 +01:00
|
|
|
exports.deactivate();
|
2012-11-04 02:16:15 +01:00
|
|
|
}
|
2016-05-25 13:24:33 +02:00
|
|
|
navigate.maybe_scroll_to_selected();
|
2012-11-04 02:16:15 +01:00
|
|
|
};
|
|
|
|
|
2013-03-29 19:55:28 +01:00
|
|
|
function pick_empty_narrow_banner() {
|
|
|
|
var default_banner = $('#empty_narrow_message');
|
2013-04-24 21:40:08 +02:00
|
|
|
if (current_filter === undefined) {
|
2013-03-29 19:55:28 +01:00
|
|
|
return default_banner;
|
|
|
|
}
|
|
|
|
|
2014-02-06 21:41:01 +01:00
|
|
|
var first_term = current_filter.operators()[0];
|
|
|
|
var first_operator = first_term.operator;
|
|
|
|
var first_operand = first_term.operand;
|
2016-08-01 04:31:34 +02:00
|
|
|
var num_operators = current_filter.operators().length;
|
2013-03-29 19:55:28 +01:00
|
|
|
|
2016-08-01 04:31:34 +02:00
|
|
|
if (num_operators !== 1) {
|
|
|
|
// For multi-operator narrows, we just use the default banner
|
|
|
|
return default_banner;
|
|
|
|
} else if (first_operator === "is") {
|
2013-03-29 19:55:28 +01:00
|
|
|
if (first_operand === "starred") {
|
|
|
|
// You have no starred messages.
|
|
|
|
return $("#empty_star_narrow_message");
|
2013-07-10 03:07:01 +02:00
|
|
|
} else if (first_operand === "mentioned") {
|
2013-05-29 00:33:03 +02:00
|
|
|
return $("#empty_narrow_all_mentioned");
|
2013-07-10 03:22:34 +02:00
|
|
|
} else if (first_operand === "private") {
|
2013-03-29 19:55:28 +01:00
|
|
|
// You have no private messages.
|
2013-04-02 20:57:53 +02:00
|
|
|
return $("#empty_narrow_all_private_message");
|
2013-03-29 19:55:28 +01:00
|
|
|
}
|
2013-08-15 21:11:07 +02:00
|
|
|
} else if ((first_operator === "stream") && !stream_data.is_subscribed(first_operand)) {
|
2013-03-29 19:55:28 +01:00
|
|
|
// You are narrowed to a stream to which you aren't subscribed.
|
2017-01-25 19:13:10 +01:00
|
|
|
if (!stream_data.get_sub(narrow.stream())) {
|
|
|
|
return $("#nonsubbed_private_nonexistent_stream_narrow_message");
|
|
|
|
}
|
2013-03-29 19:55:28 +01:00
|
|
|
return $("#nonsubbed_stream_narrow_message");
|
|
|
|
} else if (first_operator === "search") {
|
|
|
|
// You are narrowed to empty search results.
|
|
|
|
return $("#empty_search_narrow_message");
|
2013-04-02 20:57:53 +02:00
|
|
|
} else if (first_operator === "pm-with") {
|
|
|
|
if (first_operand.indexOf(',') === -1) {
|
|
|
|
// You have no private messages with this person
|
|
|
|
return $("#empty_narrow_private_message");
|
|
|
|
}
|
2016-12-02 21:34:35 +01:00
|
|
|
return $("#empty_narrow_multi_private_message");
|
2016-07-29 16:15:50 +02:00
|
|
|
} else if (first_operator === "sender") {
|
|
|
|
if (people.get_by_email(first_operand)) {
|
|
|
|
return $("#silent_user");
|
|
|
|
}
|
2016-12-02 21:34:35 +01:00
|
|
|
return $("#non_existing_user");
|
2013-03-29 19:55:28 +01:00
|
|
|
}
|
|
|
|
return default_banner;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.show_empty_narrow_message = function () {
|
|
|
|
$(".empty_feed_notice").hide();
|
|
|
|
pick_empty_narrow_banner().show();
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.hide_empty_narrow_message = function () {
|
|
|
|
$(".empty_feed_notice").hide();
|
|
|
|
};
|
|
|
|
|
2013-10-03 23:05:46 +02:00
|
|
|
exports.pm_with_uri = function (reply_to) {
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
return hashchange.operators_to_hash([
|
|
|
|
{operator: 'pm-with', operand: reply_to},
|
|
|
|
]);
|
2013-10-03 23:05:46 +02:00
|
|
|
};
|
|
|
|
|
2017-01-06 14:42:52 +01:00
|
|
|
exports.huddle_with_uri = function (user_ids_string) {
|
|
|
|
// This method is convenient is convenient for callers
|
|
|
|
// that have already converted emails to a comma-delimited
|
|
|
|
// list of user_ids. We should be careful to keep this
|
|
|
|
// consistent with hashchange.decode_operand.
|
|
|
|
return "#narrow/pm-with/" + user_ids_string + '-group';
|
|
|
|
};
|
|
|
|
|
2013-11-25 16:25:34 +01:00
|
|
|
exports.by_sender_uri = function (reply_to) {
|
2017-01-19 03:53:50 +01:00
|
|
|
return hashchange.operators_to_hash([
|
|
|
|
{operator: 'sender', operand: reply_to},
|
|
|
|
]);
|
2013-11-25 16:25:34 +01:00
|
|
|
};
|
|
|
|
|
2013-05-09 21:12:53 +02:00
|
|
|
exports.by_stream_uri = function (stream) {
|
|
|
|
return "#narrow/stream/" + hashchange.encodeHashComponent(stream);
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.by_stream_subject_uri = function (stream, subject) {
|
|
|
|
return "#narrow/stream/" + hashchange.encodeHashComponent(stream) +
|
|
|
|
"/subject/" + hashchange.encodeHashComponent(subject);
|
|
|
|
};
|
|
|
|
|
2013-11-25 16:25:34 +01:00
|
|
|
exports.by_message_uri = function (message_id) {
|
|
|
|
return "#narrow/id/" + hashchange.encodeHashComponent(message_id);
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.by_near_uri = function (message_id) {
|
|
|
|
return "#narrow/near/" + hashchange.encodeHashComponent(message_id);
|
|
|
|
};
|
|
|
|
|
2013-11-25 16:54:08 +01:00
|
|
|
exports.by_conversation_and_time_uri = function (message) {
|
|
|
|
if (message.type === "stream") {
|
|
|
|
return "#narrow/stream/" + hashchange.encodeHashComponent(message.stream) +
|
|
|
|
"/subject/" + hashchange.encodeHashComponent(message.subject) +
|
|
|
|
"/near/" + hashchange.encodeHashComponent(message.id);
|
|
|
|
}
|
2016-12-02 21:34:35 +01:00
|
|
|
return "#narrow/pm-with/" + hashchange.encodeHashComponent(message.reply_to) +
|
|
|
|
"/near/" + hashchange.encodeHashComponent(message.id);
|
2013-11-25 16:54:08 +01:00
|
|
|
};
|
|
|
|
|
2013-06-13 19:44:36 +02:00
|
|
|
// Are we narrowed to PMs: all PMs or PMs with particular people.
|
2013-05-24 22:00:23 +02:00
|
|
|
exports.narrowed_to_pms = function () {
|
2013-06-13 21:03:01 +02:00
|
|
|
if (current_filter === undefined) {
|
2013-05-24 22:00:23 +02:00
|
|
|
return false;
|
|
|
|
}
|
2013-06-13 21:03:01 +02:00
|
|
|
return (current_filter.has_operator("pm-with") ||
|
2013-07-10 03:22:34 +02:00
|
|
|
current_filter.has_operand("is", "private"));
|
2013-05-24 22:00:23 +02:00
|
|
|
};
|
|
|
|
|
2013-05-16 23:16:15 +02:00
|
|
|
// We auto-reply under certain conditions, namely when you're narrowed
|
|
|
|
// to a PM (or huddle), and when you're narrowed to some stream/subject pair
|
|
|
|
exports.narrowed_by_reply = function () {
|
2013-06-13 21:03:01 +02:00
|
|
|
if (current_filter === undefined) {
|
2013-05-16 23:16:15 +02:00
|
|
|
return false;
|
|
|
|
}
|
2013-06-13 21:03:01 +02:00
|
|
|
var operators = current_filter.operators();
|
2013-06-13 19:44:36 +02:00
|
|
|
return ((operators.length === 1 &&
|
2013-06-13 21:03:01 +02:00
|
|
|
current_filter.operands("pm-with").length === 1) ||
|
2013-06-13 19:44:36 +02:00
|
|
|
(operators.length === 2 &&
|
2013-06-13 21:03:01 +02:00
|
|
|
current_filter.operands("stream").length === 1 &&
|
2013-07-16 22:52:02 +02:00
|
|
|
current_filter.operands("topic").length === 1));
|
2013-05-16 23:16:15 +02:00
|
|
|
};
|
|
|
|
|
2013-09-18 19:27:12 +02:00
|
|
|
exports.narrowed_to_topic = function () {
|
|
|
|
if (current_filter === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return (current_filter.has_operator("stream") &&
|
|
|
|
current_filter.has_operator("topic"));
|
|
|
|
};
|
|
|
|
|
2013-07-25 22:08:16 +02:00
|
|
|
exports.narrowed_to_search = function () {
|
|
|
|
return (current_filter !== undefined) && current_filter.is_search();
|
|
|
|
};
|
|
|
|
|
2013-09-19 14:42:05 +02:00
|
|
|
exports.muting_enabled = function () {
|
2016-12-02 15:16:33 +01:00
|
|
|
return (!exports.narrowed_to_topic() && !exports.narrowed_to_search() &&
|
|
|
|
!exports.narrowed_to_pms());
|
2013-09-19 14:42:05 +02:00
|
|
|
};
|
|
|
|
|
2012-10-18 20:12:04 +02:00
|
|
|
return exports;
|
|
|
|
|
|
|
|
}());
|
2013-07-28 23:03:43 +02:00
|
|
|
if (typeof module !== 'undefined') {
|
|
|
|
module.exports = narrow;
|
|
|
|
}
|