Search streams from left sidebar (resolves #565).

Assigns hotkey 'w' to search streams.
Only show search box when active. Activate with hotkey or by clicking
STREAMS.
Filter matches at the beginning of words in stream name.
Behaviour is otherwise almost identical to user search.
Casper tests.
This commit is contained in:
Michael Cordover
2016-06-13 16:06:12 -04:00
committed by Tim Abbott
parent 574a304b12
commit a51ec44005
9 changed files with 214 additions and 12 deletions

View File

@@ -222,6 +222,66 @@ casper.then(check_narrow_title('private - Zulip Dev - Zulip'));
un_narrow();
// Make sure stream search filters the stream list
casper.then(function () {
casper.test.info('Search streams using left sidebar');
});
casper.then(function () {
casper.test.assertExists('.stream-list-filter.notdisplayed', 'Stream filter box not visible initially');
});
casper.thenClick('#streams_header .sidebar-title');
casper.then(function () {
casper.test.assertDoesntExist('.stream-list-filter.notdisplayed', 'Stream filter box visible after click');
});
casper.then(function () {
casper.test.assertExists('#stream_filters [data-name="Denmark"]', 'Original stream list contains Denmark');
casper.test.assertExists('#stream_filters [data-name="Scotland"]', 'Original stream list contains Scotland');
casper.test.assertExists('#stream_filters [data-name="Verona"]', 'Original stream list contains Verona');
});
// We search for the beginning of "Verona", not case sensitive
casper.then(function () {
casper.evaluate(function () {
$('.stream-list-filter').expectOne()
.focus()
.val('ver')
.trigger($.Event('input'));
});
});
casper.then(function () {
casper.test.assertDoesntExist('#stream_filters [data-name="Denmark"]', 'Filtered stream list does not contain Denmark');
casper.test.assertDoesntExist('#stream_filters [data-name="Scotland"]', 'Filtered stream list does not contain Scotland');
casper.test.assertExists('#stream_filters [data-name="Verona"]', 'Filtered stream list does contain Verona');
});
// Clearing the list should give us back all the streams in the list
casper.then(function () {
casper.evaluate(function () {
$('.stream-list-filter').expectOne()
.focus()
.val('')
.trigger($.Event('input'));
});
});
casper.then(function () {
casper.test.assertExists('#stream_filters [data-name="Denmark"]', 'Restored stream list contains Denmark');
casper.test.assertExists('#stream_filters [data-name="Scotland"]', 'Restored stream list contains Scotland');
casper.test.assertExists('#stream_filters [data-name="Verona"]', 'Restored stream list contains Verona');
});
casper.thenClick('#streams_header .sidebar-title');
casper.then(function () {
casper.test.assertExists('.stream-list-filter.notdisplayed', 'Stream filter box not visible after second click');
});
un_narrow();
common.then_log_out();
// Run the above queued actions.

View File

@@ -10,7 +10,7 @@ casper.then(function () {
// subscriptions need to load; if they have *any* subs,
// the word "Unsubscribe" will appear
});
casper.waitForText('Subscribed', function () {
casper.waitForSelector('.sub_unsub_button.subscribed-button', function () {
casper.test.assertTextExists('Subscribed', 'Initial subscriptions loaded');
casper.fill('form#add_new_subscription', {stream_name: 'Waseemio'});
casper.click('form#add_new_subscription input.btn');

View File

@@ -235,7 +235,7 @@ $(function () {
// MISC
$('#streams_header a').click(function (e) {
$('#streams_inline_cog').click(function (e) {
ui.change_tab_to('#subscriptions');
e.preventDefault();

View File

@@ -57,7 +57,8 @@ var hotkeys_shift_insensitive = {
113: {name: 'query_users', message_view_only: false}, // 'q'
114: {name: 'reply_message', message_view_only: true}, // 'r'
115: {name: 'narrow_by_recipient', message_view_only: true}, // 's'
118: {name: 'narrow_private', message_view_only: true} // 'v'
118: {name: 'narrow_private', message_view_only: true}, // 'v'
119: {name: 'query_streams', message_view_only: false} // 'w'
};
function get_hotkey_from_event(e) {
@@ -177,6 +178,9 @@ function process_hotkey(e) {
} else if (activity.searching()) {
activity.escape_search();
return true;
} else if (stream_list.searching()) {
stream_list.escape_search();
return true;
} else if (compose.composing()) {
// If the user hit the escape key, cancel the current compose
compose.cancel();
@@ -194,6 +198,9 @@ function process_hotkey(e) {
if (activity.searching()) {
activity.blur_search();
return true;
} else if (stream_list.searching()) {
stream_list.clear_and_hide_search();
return true;
}
}
@@ -245,6 +252,9 @@ function process_hotkey(e) {
case 'query_users':
activity.initiate_search();
return true;
case 'query_streams':
stream_list.initiate_search();
return true;
case 'search':
search.initiate_search();
return true;

View File

@@ -19,12 +19,40 @@ function active_stream_name() {
return false;
}
function filter_streams_by_search(streams) {
var search_box = $(".stream-list-filter");
var search_term = search_box.expectOne().val().trim();
if (search_term === '') {
return streams;
}
var search_terms = search_term.toLowerCase().split(",");
search_terms = _.map(search_terms, function (s) {
return s.trim();
});
var filtered_streams = _.filter(streams, function (stream) {
return _.any(search_terms, function (search_term) {
var lower_stream_name = stream.toLowerCase().split(" ");
return _.any(lower_stream_name, function (name) {
return name.indexOf(search_term) === 0;
});
});
});
return filtered_streams;
}
exports.build_stream_list = function () {
var streams = stream_data.subscribed_streams();
if (streams.length === 0) {
return;
}
streams = filter_streams_by_search(streams);
var sort_recent = (streams.length > 40);
streams.sort(function (a, b) {
@@ -643,6 +671,89 @@ $(function () {
});
function actually_update_streams_for_search() {
exports.update_streams_sidebar();
resize.resize_page_components();
}
var update_streams_for_search = _.throttle(actually_update_streams_for_search, 50);
exports.searching = function () {
return $('.stream-list-filter').expectOne().is(':focus');
};
exports.escape_search = function () {
var filter = $('.stream-list-filter').expectOne();
if (filter.val() === '') {
exports.clear_and_hide_search();
return;
}
filter.val('');
update_streams_for_search();
};
exports.initiate_search = function () {
var filter = $('.stream-list-filter').expectOne();
filter.removeClass('notdisplayed');
filter.focus();
};
exports.clear_and_hide_search = function () {
var filter = $('.stream-list-filter');
if (filter.val() !== '') {
filter.val('');
update_streams_for_search();
}
filter.blur();
filter.addClass('notdisplayed');
};
function focus_stream_filter (e) {
e.stopPropagation();
}
function maybe_select_stream (e) {
if (e.keyCode === 13) {
// Enter key was pressed
var topStream = $('#stream_filters li.narrow-filter').first().data('name');
if (topStream !== undefined) {
// undefined if there are no results
if (ui.home_tab_obscured()) {
ui.change_tab_to('#home');
}
exports.clear_and_hide_search();
narrow.by('stream', topStream, {select_first_unread: true, trigger: 'sidebar enter key'});
e.preventDefault();
e.stopPropagation();
}
}
}
function toggle_filter_displayed(e) {
if (e.target.id === 'streams_inline_cog') {
return;
}
if (0 === $('.stream-list-filter.notdisplayed').length) {
exports.clear_and_hide_search();
} else {
exports.initiate_search();
}
e.preventDefault();
}
$(function () {
$(".stream-list-filter").expectOne()
.on('click', focus_stream_filter)
.on('input', update_streams_for_search)
.on('keydown', maybe_select_stream);
});
$(function () {
$("#streams_header").expectOne()
.on('click', toggle_filter_displayed);
});
return exports;
}());
if (typeof module !== 'undefined') {

View File

@@ -472,9 +472,13 @@ $(function () {
timerender.set_full_datetime(message, time_elem);
});
$('#streams_inline_cog').tooltip({ placement: 'left',
$('#streams_header h4').tooltip({ placement: 'right',
animation: false });
$('#streams_header i[data-toggle="tooltip"]').tooltip({ placement: 'left',
animation: false });
if (feature_flags.disable_message_editing) {
$("#edit-message-hotkey-help").hide();
}

View File

@@ -321,17 +321,24 @@ a:hover code {
display: inline;
}
#streams_list #streams_inline_cog:hover {
#streams_inline_cog,
#streams_filter_icon {
float: right;
color: #000;
font-size: 13px;
margin-top: 3px;
margin-left: 6px;
opacity: 0.5;
}
#streams_inline_cog:hover,
#streams_filter_icon:hover {
opacity: 1.0;
}
#streams_inline_cog {
float: right;
color: #000;
#streams_header a {
color: inherit;
text-decoration: none;
font-size: 13px;
margin-top: 3px;
opacity: 0.5;
}
.tooltip {
@@ -359,6 +366,10 @@ a:hover code {
width: 4px !important;
}
.stream-list-filter {
margin-left: 1ex;
}
.narrow-filter {
display: block;
position: relative;

View File

@@ -20,6 +20,10 @@
<td class="hotkey">q</td>
<td class="definition">{% trans %}Search people{% endtrans %}</td>
</tr>
<tr>
<td class="hotkey">w</td>
<td class="definition">{% trans %}Search streams{% endtrans %}</td>
</tr>
<tr>
<td class="hotkey">Up or k</td>
<td class="definition">{% trans %}Previous message{% endtrans %}</td>

View File

@@ -9,8 +9,9 @@
<li data-name="mentioned" class="global-filter"><span class="filter-icon"><i class="icon-vector-tag"></i></span><a href="#narrow/is/mentioned">{{ _('@-mentions') }}<span class="count"><span class="value"></span></span></a></li>
</ul>
<div id="streams_list" class="zoom-out">
<div id="streams_header" class="zoom-in-hide"><h4 class="sidebar-title">{{ _('STREAMS') }}</h4>
<div id="streams_header" class="zoom-in-hide"><h4 class="sidebar-title" data-toggle="tooltip" title="Subscribed streams"><a href="">{{ _('STREAMS') }}</a></h4>
<a href=""><i id="streams_inline_cog" class='icon-vector-cog' data-toggle="tooltip" title="Subscribe, add, or configure streams"></i></a>
<a href=""><i id='streams_filter_icon' class='icon-vector-search' data-toggle="tooltip" title="Filter streams list"></i></a>
</div>
<div id="topics_header">
<div class="all-streams-padding">
@@ -22,6 +23,7 @@
</div>
</div>
<div id="stream-filters-container" class="scrolling_list">
<input class="stream-list-filter notdisplayed" type="text" placeholder="{{ _('Search streams') }}" />
<ul id="stream_filters" class="filters"></ul>
</div>
</div>