stream_filtering: Filter streams on subscriptions page.

Filter behaves similarly to filter in left sidebar, see PR #684. Added
stream input field to the stream creation modal along with other settings,
for clarity.

Fixes #455, #563.
This commit is contained in:
trueskawka
2016-10-12 02:53:57 -04:00
committed by Tim Abbott
parent 357fbfcaa3
commit 9c8f4d9a1e
6 changed files with 130 additions and 38 deletions

View File

@@ -21,11 +21,12 @@ casper.then(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');
});
casper.waitForText('Waseemio', function () {
casper.test.assertTextExists('Create stream Waseemio', 'Modal for specifying new stream users');
casper.waitForSelector('#create_stream_button', function () {
casper.test.assertTextExists('Create stream', 'Modal for specifying new stream users');
casper.fill('form#stream_creation_form', {stream_name: 'Waseemio'});
casper.click('form#stream_creation_form button.btn.btn-primary');
});
casper.then(function () {
casper.test.assertExists('#user-checkboxes [data-name="cordelia@zulip.com"]', 'Original user list contains Cordelia');
@@ -59,16 +60,11 @@ casper.then(function () {
"King Hamlet is visible again"
);
});
casper.then(function () {
casper.test.assertTextExists('Create stream Waseemio', 'Create a new stream');
casper.click('form#stream_creation_form button.btn.btn-primary');
});
casper.waitFor(function () {
return casper.evaluate(function () {
return $('.subscription_name').is(':contains("Waseemio")');
});
});
casper.then(function () {
casper.test.assertSelectorHasText('.subscription_name', 'Waseemio', 'Subscribing to a stream');
casper.fill('form#add_new_subscription', {stream_name: 'WASeemio'});
@@ -79,8 +75,32 @@ casper.waitForText('Already subscribed', function () {
casper.fill('form#add_new_subscription', {stream_name: ' '});
casper.click('form#add_new_subscription input.btn');
});
casper.waitForText('Error adding subscription', function () {
casper.test.assertTextExists('Error adding subscription', "Can't subscribe to an empty stream name");
casper.waitForText('Create stream', function () {
casper.test.assertTextExists('Create stream', 'Modal for specifying new stream users');
casper.fill('form#stream_creation_form', {stream_name: ' '});
casper.click('form#stream_creation_form button.btn.btn-primary');
});
casper.waitForText('Error creating stream', function () {
casper.test.assertTextExists('Invalid stream name', "Can't create a stream without a name");
});
casper.then(function () {
casper.test.info('Streams should be filtered when typing in the create box');
});
casper.then(function () {
casper.test.assertSelectorHasText('.subscription_row .subscription_name', 'Verona', 'Verona stream exists before filtering');
casper.test.assertSelectorDoesntHaveText('.subscription_row.notdisplayed .subscription_name', 'Verona', 'Verona stream shown before filtering');
});
casper.then(function () {
casper.fill('form#add_new_subscription', {stream_name: 'was'});
casper.evaluate(function () {
$('#add_new_subscription input[type="text"]').expectOne()
.trigger($.Event('input'));
});
});
casper.then(function () {
casper.test.assertSelectorHasText('.subscription_row.notdisplayed .subscription_name', 'Verona', 'Verona stream not shown after filtering');
casper.test.assertSelectorHasText('.subscription_row .subscription_name', 'Waseemio', 'Waseemio stream exists after filtering');
casper.test.assertSelectorDoesntHaveText('.subscription_row.notdisplayed .subscription_name', 'Waseemio', 'Waseemio stream shown after filtering');
});
common.then_log_out();

View File

@@ -52,7 +52,7 @@ exports.initialize = function () {
// link (in the gear menu), then focus the new stream textbox.
subs_link.on('click', function (e) {
$(document).one('subs_page_loaded.zulip', function (e) {
$('#create_stream_name').focus().select();
$('#create_or_filter_stream_row input[type="text"]').focus().select();
});
});

View File

@@ -266,7 +266,7 @@ function add_email_hint(row, email_address_hint_content) {
function add_sub_to_table(sub) {
sub = add_admin_options(sub);
var html = templates.render('subscription', sub);
$('#create_stream_row').after(html);
$('#create_or_filter_stream_row').after(html);
settings_for_sub(sub).collapse('show');
var email_address_hint_content = templates.render('email_address_hint', { page_params: page_params });
add_email_hint(sub, email_address_hint_content);
@@ -437,6 +437,54 @@ function populate_subscriptions(subs, subscribed) {
return sub_rows;
}
exports.filter_table = function (query) {
var sub_name_elements = $('#subscriptions_table .subscription_name');
if (query === '') {
_.each(sub_name_elements, function (sub_name_elem) {
$(sub_name_elem).parents('.subscription_row').removeClass("notdisplayed");
});
return;
}
var search_terms = query.toLowerCase().split(",");
search_terms = _.map(search_terms, function (s) {
return s.trim();
});
_.each(sub_name_elements, function (sub_name_elem) {
var sub_name = $(sub_name_elem).text();
var matches = _.any(search_terms, function (search_term) {
var lower_sub_name = sub_name.toLowerCase();
var idx = lower_sub_name.indexOf(search_term);
if (idx === 0) {
// matched at beginning of the string
return true;
}
// we know now that idx === -1 or idx > 0
if (idx !== -1 && lower_sub_name.charAt(idx - 1) === ' ') {
// matched with a space immediately preceding
return true;
}
return false;
});
if (matches) {
$(sub_name_elem).parents('.subscription_row').removeClass("notdisplayed");
} else {
$(sub_name_elem).parents('.subscription_row').addClass("notdisplayed");
}
});
};
function actually_filter_streams() {
var search_box = $("#create_or_filter_stream_row input[type='text']");
var query = search_box.expectOne().val().trim();
exports.filter_table(query);
}
var filter_streams = _.throttle(actually_filter_streams, 50);
exports.setup_page = function () {
loading.make_indicator($('#subs_page_loading_indicator'));
@@ -483,9 +531,11 @@ exports.setup_page = function () {
});
$('#subscriptions_table').empty();
var template_data = {
can_create_streams: page_params.can_create_streams,
subscriptions: sub_rows
subscriptions: sub_rows,
hide_all_streams: !should_list_all_streams()
};
var rendered = templates.render('subscription_table_body', template_data);
$('#subscriptions_table').append(rendered);
@@ -496,6 +546,7 @@ exports.setup_page = function () {
});
loading.destroy_indicator($('#subs_page_loading_indicator'));
$("#create_or_filter_stream_row input[type='text']").on("input", filter_streams);
$(document).trigger($.Event('subs_page_loaded.zulip'));
}
@@ -575,6 +626,7 @@ function ajaxSubscribe(stream) {
data: {"subscriptions": JSON.stringify([{"name": stream}]) },
success: function (resp, statusText, xhr, form) {
$("#create_stream_name").val("");
exports.filter_table("");
var res = JSON.parse(xhr.responseText);
if (!$.isEmptyObject(res.already_subscribed)) {
@@ -608,6 +660,10 @@ function ajaxUnsubscribe(stream) {
});
}
function hide_new_stream_modal() {
$('#stream-creation').modal("hide");
}
function ajaxSubscribeForCreation(stream, principals, invite_only, announce) {
// Subscribe yourself and possible other people to a new stream.
return channel.post({
@@ -620,13 +676,13 @@ function ajaxSubscribeForCreation(stream, principals, invite_only, announce) {
success: function (data) {
$("#create_stream_name").val("");
$("#subscriptions-status").hide();
$('#stream-creation').modal("hide");
hide_new_stream_modal();
// The rest of the work is done via the subscribe event we will get
},
error: function (xhr) {
ui.report_error(i18n.t("Error creating stream"), xhr,
$("#subscriptions-status"), 'subscriptions-status');
$('#stream-creation').modal("hide");
hide_new_stream_modal();
}
});
}
@@ -718,14 +774,14 @@ $(function () {
e.preventDefault();
if (!should_list_all_streams()) {
ajaxSubscribe($("#create_stream_name").val());
ajaxSubscribe($("#search_stream_name").val());
return;
}
var stream = $.trim($("#create_stream_name").val());
var stream = $.trim($("#search_stream_name").val());
var stream_status = compose.check_stream_existence(stream);
if (stream_status === "does-not-exist") {
$("#stream_name").text(stream);
if (stream_status === "does-not-exist" || !stream) {
$('#create_stream_name').val(stream);
show_new_stream_modal();
} else {
ajaxSubscribe(stream);

View File

@@ -2645,7 +2645,11 @@ div.floating_recipient {
}
#add_new_subscription {
text-align: center;
text-align: left;
}
#create_or_filter_stream_row > .subscription_table_elem {
text-align: left;
}
#subscriptions-status {
@@ -2917,21 +2921,28 @@ button.topic_edit_cancel {
padding: 0 15px 0 0;
}
#create_stream_row td {
#create_or_filter_stream_row td {
background-color: #f3f3f3;
border-color: #BBB;
border-bottom: 1px solid #BBB;
}
#create_stream_name {
#create_or_filter_stream_row input[type="text"] {
width: 220px;
margin-top: 10px;
margin: 5px 0 20px 38px;
}
form#add_new_subscription {
display: inline;
}
#create_stream_button {
min-width: 140px;
margin-left: 15px;
margin-top: 10px;
float: right;
margin-top: 9px;
/* margin-right: 38px will align with .sub_unsub_button
10px will align with the right edge of #subscriptions_table */
margin-right: 38px;
}
.sub_settings_title {

View File

@@ -1,15 +1,15 @@
{{#if can_create_streams}}
<div id="create_stream_row">
<div class="subscription_table_elem">
<form id="add_new_subscription" class="form-inline" action="">
<input type="text" name="stream_name" id="create_stream_name"
placeholder="{{t 'Stream name' }}" value="" />
<input type="submit" class="btn btn-primary"
id="create_stream_button" value="{{t 'Create new stream' }}" />
</form>
</div>
</div>
{{/if}}
<div id="create_or_filter_stream_row">
<div class="subscription_table_elem">
<form id="add_new_subscription" class="form-inline" action="">
<input type="text" name="stream_name" id="search_stream_name"
placeholder="{{t 'Filter by stream name' }}" value="" autocomplete="off" />
{{#if can_create_streams}}
<input type="submit" class="btn btn-primary"
id="create_stream_button" value="{{t 'Create new stream' }}" />
{{/if}}
</form>
</div>
</div>
{{#each subscriptions}}
{{partial "subscription"}}

View File

@@ -2,10 +2,15 @@
aria-labelledby="stream-creation-label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="stream-creation-label">{% trans %}Create stream{% endtrans %} <span id="stream_name"></span></h3>
<h3 id="stream-creation-label">{% trans %}Create stream{% endtrans %}</h3>
</div>
<form id="stream_creation_form" class="form-inline">
<div class="modal-body">
<div>
<b>{% trans %}Stream name{% endtrans %}</b><br />
<input type="text" name="stream_name" id="create_stream_name"
placeholder="{{ _('Stream name') }}" value="" autocomplete="off" />
</div>
<div id="make-invite-only">
<b>{% trans %}Stream accessibility{% endtrans %}</b><br />
<label class="radio">