streams: Allow creating stream pills to submit Add subscriber form.

We update the pills typeahead logic to also include
stream results and pass the "stream" key in `opts`
to enable this option for the Add subscriber form.

This commit implements the feature of adding all the
subscribers of another stream in the "Add subscribers"
UI, with the help of a new "stream_pill.js` file.

We temporarily add `user_pill.js` to the EXEMPT_FILES
list as typeahead will be set up in `stream_edit.js`
file which does not have any dedicated tests file.

Work towards #15186.
This commit is contained in:
Ryan Rehman 2020-07-18 21:11:07 +05:30 committed by Tim Abbott
parent b93371aa9f
commit c6d9a87d6f
8 changed files with 99 additions and 7 deletions

View File

@ -249,6 +249,7 @@
"stream_topic_history": false, "stream_topic_history": false,
"stream_list": false, "stream_list": false,
"stream_muting": false, "stream_muting": false,
"stream_pill": false,
"stream_popover": false, "stream_popover": false,
"stream_sort": false, "stream_sort": false,
"stream_ui_updates": false, "stream_ui_updates": false,

View File

@ -32,6 +32,7 @@ import "../localstorage.js";
import "../drafts.js"; import "../drafts.js";
import "../input_pill.js"; import "../input_pill.js";
import "../user_pill.js"; import "../user_pill.js";
import "../stream_pill.js";
import "../compose_pm_pill.js"; import "../compose_pm_pill.js";
import "../channel.js"; import "../channel.js";
import "../setup.js"; import "../setup.js";

View File

@ -145,6 +145,7 @@ declare let stream_edit: any;
declare let stream_events: any; declare let stream_events: any;
declare let stream_list: any; declare let stream_list: any;
declare let stream_muting: any; declare let stream_muting: any;
declare let stream_pill: any;
declare let stream_popover: any; declare let stream_popover: any;
declare let stream_sort: any; declare let stream_sort: any;
declare let stream_ui_updates: any; declare let stream_ui_updates: any;

View File

@ -7,18 +7,35 @@ exports.set_up = function (input, pills, opts) {
if (!opts.source) { if (!opts.source) {
source = () => user_pill.typeahead_source(pills); source = () => user_pill.typeahead_source(pills);
} }
const include_streams = (query) => opts.stream && query.trim().startsWith("#");
input.typeahead({ input.typeahead({
items: 5, items: 5,
fixed: true, fixed: true,
dropup: true, dropup: true,
source, source() {
if (include_streams(this.query)) {
return stream_pill.typeahead_source(pills);
}
return source();
},
highlighter(item) { highlighter(item) {
if (include_streams(this.query)) {
return typeahead_helper.render_stream(item);
}
return typeahead_helper.render_person(item); return typeahead_helper.render_person(item);
}, },
matcher(item) { matcher(item) {
let query = this.query.toLowerCase(); let query = this.query.toLowerCase();
query = query.replace(/\u00A0/g, String.fromCharCode(32)); query = query.replace(/\u00A0/g, String.fromCharCode(32));
if (include_streams(query)) {
query = query.trim().substring(1);
return item.name.toLowerCase().indexOf(query) !== -1;
}
if (!settings_data.show_email()) { if (!settings_data.show_email()) {
return item.full_name.toLowerCase().includes(query); return item.full_name.toLowerCase().includes(query);
} }
@ -28,10 +45,19 @@ exports.set_up = function (input, pills, opts) {
); );
}, },
sorter(matches) { sorter(matches) {
if (include_streams(this.query)) {
return typeahead_helper.sort_streams(matches, this.query.trim().substring(1));
}
return typeahead_helper.sort_recipientbox_typeahead(this.query, matches, ""); return typeahead_helper.sort_recipientbox_typeahead(this.query, matches, "");
}, },
updater(user) { updater(item) {
user_pill.append_user(user, pills); if (include_streams(this.query)) {
stream_pill.append_stream(item, pills);
} else {
user_pill.append_user(item, pills);
}
input.trigger("focus"); input.trigger("focus");
if (opts.update_func) { if (opts.update_func) {
opts.update_func(); opts.update_func();

View File

@ -203,16 +203,25 @@ function submit_add_subscriber_form(e) {
return; return;
} }
const user_ids = user_pill.get_user_ids(exports.pill_widget);
const stream_subscription_info_elem = $(".stream_subscription_info").expectOne(); const stream_subscription_info_elem = $(".stream_subscription_info").expectOne();
let user_ids = user_pill.get_user_ids(exports.pill_widget);
user_ids = user_ids.concat(stream_pill.get_user_ids(exports.pill_widget));
user_ids = new Set(user_ids);
if (user_ids.length === 0) { if (user_ids.has(page_params.user_id) && sub.subscribed) {
// We don't want to send a request to subscribe ourselves
// if we are already subscribed to this stream. This
// case occurs when creating user pills from a stream.
user_ids.delete(page_params.user_id);
}
if (user_ids.size === 0) {
stream_subscription_info_elem stream_subscription_info_elem
.text(i18n.t("No user to subscribe.")) .text(i18n.t("No user to subscribe."))
.addClass("text-error") .addClass("text-error")
.removeClass("text-success"); .removeClass("text-success");
return; return;
} }
user_ids = Array.from(user_ids);
function invite_success(data) { function invite_success(data) {
exports.pill_widget.clear(); exports.pill_widget.clear();
@ -337,7 +346,7 @@ function show_subscription_settings(sub) {
simplebar_container: $(".subscriber_list_container"), simplebar_container: $(".subscriber_list_container"),
}); });
const opts = {source: get_users_for_subscriber_typeahead}; const opts = {source: get_users_for_subscriber_typeahead, stream: true};
pill_typeahead.set_up(sub_settings.find(".input"), exports.pill_widget, opts); pill_typeahead.set_up(sub_settings.find(".input"), exports.pill_widget, opts);
} }

52
static/js/stream_pill.js Normal file
View File

@ -0,0 +1,52 @@
"use strict";
function get_user_ids_from_subs(items) {
let user_ids = [];
const stream_ids = items.map((item) => item.stream_id);
for (const stream_id of stream_ids) {
const sub = stream_data.get_sub_by_id(stream_id);
if (!sub) {
continue;
}
user_ids = user_ids.concat(sub.subscribers.map());
}
return user_ids;
}
exports.get_user_ids = function (pill_widget) {
const items = pill_widget.items();
let user_ids = get_user_ids_from_subs(items);
user_ids = Array.from(new Set(user_ids));
user_ids = user_ids.filter(Boolean);
return user_ids;
};
exports.append_stream = function (stream, pill_widget) {
pill_widget.appendValidatedData({
display_value: "#" + stream.name + ": " + stream.subscriber_count + " users",
stream_id: stream.stream_id,
});
pill_widget.clear_text();
};
exports.get_stream_ids = function (pill_widget) {
const items = pill_widget.items();
let stream_ids = items.map((item) => item.stream_id);
stream_ids = stream_ids.filter(Boolean);
return stream_ids;
};
exports.filter_taken_streams = function (items, pill_widget) {
const taken_stream_ids = exports.get_stream_ids(pill_widget);
items = items.filter((item) => !taken_stream_ids.includes(item.stream_id));
return items;
};
exports.typeahead_source = function (pill_widget) {
const potential_streams = stream_data.get_unsorted_subs();
return exports.filter_taken_streams(potential_streams, pill_widget);
};
window.stream_pill = exports;

View File

@ -12,7 +12,7 @@
<div class="add_subscribers_container"> <div class="add_subscribers_container">
<div class="pill-container person_picker"> <div class="pill-container person_picker">
<div class="input" contenteditable="true" <div class="input" contenteditable="true"
data-placeholder="{{t 'Add subscribers' }}"></div> data-placeholder="{{t 'Add subscribers. Use #streamname to subscribe a whole stream' }}"></div>
</div> </div>
<div class="add_subscriber_btn_wrapper inline-block"> <div class="add_subscriber_btn_wrapper inline-block">
<button type="submit" name="add_subscriber" class="button add-subscriber-button small rounded" tabindex="0"> <button type="submit" name="add_subscriber" class="button add-subscriber-button small rounded" tabindex="0">

View File

@ -136,6 +136,7 @@ EXEMPT_FILES = {
'static/js/stream_edit.js', 'static/js/stream_edit.js',
'static/js/stream_list.js', 'static/js/stream_list.js',
'static/js/stream_muting.js', 'static/js/stream_muting.js',
'static/js/stream_pill.js',
'static/js/stream_popover.js', 'static/js/stream_popover.js',
'static/js/stream_ui_updates.js', 'static/js/stream_ui_updates.js',
'static/js/submessage.js', 'static/js/submessage.js',
@ -157,6 +158,7 @@ EXEMPT_FILES = {
'static/js/unread_ops.js', 'static/js/unread_ops.js',
'static/js/unread_ui.js', 'static/js/unread_ui.js',
'static/js/upload_widget.js', 'static/js/upload_widget.js',
'static/js/user_pill.js',
'static/js/user_status_ui.js', 'static/js/user_status_ui.js',
'static/js/zcommand.js', 'static/js/zcommand.js',
'static/js/zform.js', 'static/js/zform.js',