stream settings: Add pills in add subscribers input.

This commit changes the stream settings UI for adding subscribers to
use our standard user pills in the input box, rather than just
plain-text email addresses.  This is important progress towards
removing display email addresses from the Zulip UI.

It also allows subscribing multiple users at the same time, which is a
nice improvement.
This commit is contained in:
sahil839 2020-04-12 01:19:51 +05:30 committed by Tim Abbott
parent 18f8859baa
commit e52b544213
6 changed files with 75 additions and 56 deletions

View File

@ -129,13 +129,13 @@ exports.update_stream_description = function (sub) {
);
};
exports.invite_user_to_stream = function (user_email, sub, success, failure) {
exports.invite_user_to_stream = function (emails, sub, success, failure) {
// TODO: use stream_id when backend supports it
const stream_name = sub.name;
return channel.post({
url: "/json/users/me/subscriptions",
data: {subscriptions: JSON.stringify([{name: stream_name}]),
principals: JSON.stringify([user_email])},
principals: JSON.stringify(emails)},
success: success,
error: failure,
});
@ -185,6 +185,13 @@ function show_subscription_settings(sub_row) {
stream_color.set_colorpicker_color(colorpicker, color);
stream_ui_updates.update_add_subscriptions_elements(sub);
const container = $("#subscription_overlay .subscription_settings[data-stream-id='" + stream_id + "'] .pill-container");
exports.pill_widget = input_pill.create({
container: container,
create_item_from_text: user_pill.create_item_from_email,
get_text_from_item: user_pill.get_email_from_item,
});
if (!sub.render_subscribers) {
return;
}
@ -198,6 +205,11 @@ function show_subscription_settings(sub_row) {
const users = exports.get_users_from_subscribers(sub.subscribers);
exports.sort_but_pin_current_user_on_top(users);
function get_users_for_subscriber_typeahead() {
const potential_subscribers = stream_data.potential_subscribers(sub);
return user_pill.filter_taken_users(potential_subscribers, exports.pill_widget);
}
list_render.create(list, users, {
name: "stream_subscribers/" + stream_id,
modifier: function (item) {
@ -219,33 +231,10 @@ function show_subscription_settings(sub_row) {
},
});
sub_settings.find('input[name="principal"]').typeahead({
source: () => stream_data.potential_subscribers(sub),
items: 5,
highlighter: function (item) {
return typeahead_helper.render_person(item);
},
matcher: function (item) {
const query = $.trim(this.query.toLowerCase());
if (query === '' || query === item.email) {
return false;
}
// Case-insensitive.
if (!settings_data.show_email()) {
return item.full_name.toLowerCase().includes(query);
}
return item.email.toLowerCase().includes(query) ||
item.full_name.toLowerCase().includes(query);
},
sorter: function (matches) {
const current_stream = compose_state.stream_name();
return typeahead_helper.sort_recipientbox_typeahead(
this.query, matches, current_stream);
},
updater: function (item) {
return item.email;
},
});
user_pill.set_up_typeahead_on_pills(sub_settings.find('.input'),
exports.pill_widget,
function () {},
get_users_for_subscriber_typeahead);
}
exports.is_notification_setting = function (setting_label) {
@ -569,18 +558,23 @@ exports.initialize = function () {
return;
}
const text_box = settings_row.find('input[name="principal"]');
const principal = $.trim(text_box.val());
const user_ids = user_pill.get_user_ids(exports.pill_widget);
const emails = user_ids.map(user_id => {
const person = people.get_by_user_id(user_id);
return person.email;
});
const stream_subscription_info_elem = $('.stream_subscription_info').expectOne();
function invite_success(data) {
text_box.val('');
if (Object.prototype.hasOwnProperty.call(data.subscribed, principal)) {
exports.pill_widget.clear();
if (!Object.entries(data.already_subscribed).length) {
stream_subscription_info_elem.text(i18n.t("Subscribed successfully!"));
// The rest of the work is done via the subscription -> add event we will get
} else {
stream_subscription_info_elem.text(i18n.t("User already subscribed."));
const already_subscribed_users = Object.keys(data.already_subscribed).join(', ');
stream_subscription_info_elem.text(i18n.t(
" __already_subscribed_users__ are already subscribed.", {already_subscribed_users: already_subscribed_users}));
}
stream_subscription_info_elem.addClass("text-success")
.removeClass("text-error");
@ -592,7 +586,7 @@ exports.initialize = function () {
.addClass("text-error").removeClass("text-success");
}
exports.invite_user_to_stream(principal, sub, invite_success, invite_failure);
exports.invite_user_to_stream(emails, sub, invite_success, invite_failure);
});
$("#subscriptions_table").on("submit", ".subscriber_list_remove form", function (e) {

View File

@ -207,7 +207,7 @@ exports.update_add_subscriptions_elements = function (sub) {
// Otherwise, we adjust whether the widgets are disabled based on
// whether this user is authorized to add subscribers.
const input_element = $('.add_subscribers_container').find('input[name="principal"]').expectOne();
const input_element = $('.add_subscribers_container').find('.input').expectOne();
const button_element = $('.add_subscribers_container').find('button[name="add_subscriber"]').expectOne();
const allow_user_to_add_subs = sub.can_add_subscribers;

View File

@ -117,14 +117,15 @@ exports.create_pills = function (pill_container) {
return pills;
};
exports.set_up_typeahead_on_pills = function (input, pills, update_func) {
exports.set_up_typeahead_on_pills = function (input, pills, update_func, source) {
if (!source) {
source = () => exports.typeahead_source(pills);
}
input.typeahead({
items: 5,
fixed: true,
dropup: true,
source: function () {
return exports.typeahead_source(pills);
},
source: source,
highlighter: function (item) {
return typeahead_helper.render_person(item);
},

View File

@ -113,6 +113,16 @@
}
}
.add_subscribers_container .pill-container {
width: 100%;
background-color: hsl(0, 0%, 100%);
.input:first-child:empty::before {
opacity: 0.5;
content: attr(data-placeholder);
}
}
@keyframes shake {
10%,
90% {

View File

@ -312,17 +312,26 @@ form#add_new_subscription {
.subscriber_list_add {
width: 100%;
margin: 10px auto;
text-align: right;
}
.subscriber_list_add .search {
float: left;
.subscriber-search {
margin: 10px 0 0 0;
}
.subscriber_list_add .form-inline {
margin-bottom: 0px;
}
.add_subscribers_container {
display: inline-flex;
width: 100%;
align-items: center;
.add_subscriber_btn_wrapper {
padding-left: 5px;
}
}
.remove-subscriber-form {
margin: 0px 0px 0px 0px;
}
@ -419,15 +428,15 @@ form#add_new_subscription {
transition: all 0.3s ease;
transform: translate(-13px, 0px);
}
}
.exit {
font-weight: 600;
position: absolute;
top: 10px;
right: 10px;
color: hsl(0, 0%, 67%);
cursor: pointer;
.exit {
font-weight: 600;
position: absolute;
top: 10px;
right: 10px;
color: hsl(0, 0%, 67%);
cursor: pointer;
}
}
.exit-sign {

View File

@ -3,20 +3,25 @@
<div class="subscriber_list_settings">
<label class="sub_settings_title float-left">
{{t "Stream membership" }}
<div class="stream_subscription_info small"></div>
</label>
<div class="subscriber_list_add float-right">
<div class="subscriber-search float-right">
<input type="text" class="search" placeholder="{{t 'Search subscribers' }}" />
</div>
<div class="subscriber_list_add float-left">
<form class="form-inline">
<input type="text" class="search" placeholder="{{t 'Search subscribers' }}" />
<div class="add_subscribers_container">
<input type="text" name="principal" placeholder="{{t 'Name or email' }}" value="" class="input-block" autocomplete="off" />
<div class="pill-container person_picker">
<div class="input" contenteditable="true"
data-placeholder="{{t 'Add subscribers' }}"></div>
</div>
<div class="add_subscriber_btn_wrapper inline-block">
<button type="submit" name="add_subscriber" class="button add-subscriber-button small rounded" tabindex="-1">
<button type="submit" name="add_subscriber" class="button add-subscriber-button small rounded" tabindex="0">
{{t 'Add' }}
</button>
</div>
</div>
</form>
<div class="stream_subscription_info"></div>
</div>
<div class="clear-float"></div>
</div>