From c6d9a87d6f774a9a708fed39334be35c652c6598 Mon Sep 17 00:00:00 2001 From: Ryan Rehman Date: Sat, 18 Jul 2020 21:11:07 +0530 Subject: [PATCH] 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. --- .eslintrc.json | 1 + static/js/bundles/app.js | 1 + static/js/global.d.ts | 1 + static/js/pill_typeahead.js | 32 ++++++++++++-- static/js/stream_edit.js | 15 +++++-- static/js/stream_pill.js | 52 +++++++++++++++++++++++ static/templates/subscription_members.hbs | 2 +- tools/test-js-with-node | 2 + 8 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 static/js/stream_pill.js diff --git a/.eslintrc.json b/.eslintrc.json index a5ba6a6399..f9f56deefa 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -249,6 +249,7 @@ "stream_topic_history": false, "stream_list": false, "stream_muting": false, + "stream_pill": false, "stream_popover": false, "stream_sort": false, "stream_ui_updates": false, diff --git a/static/js/bundles/app.js b/static/js/bundles/app.js index 1bd6062bc5..d05609c2be 100644 --- a/static/js/bundles/app.js +++ b/static/js/bundles/app.js @@ -32,6 +32,7 @@ import "../localstorage.js"; import "../drafts.js"; import "../input_pill.js"; import "../user_pill.js"; +import "../stream_pill.js"; import "../compose_pm_pill.js"; import "../channel.js"; import "../setup.js"; diff --git a/static/js/global.d.ts b/static/js/global.d.ts index 4ff6b88c0a..a6d28558a8 100644 --- a/static/js/global.d.ts +++ b/static/js/global.d.ts @@ -145,6 +145,7 @@ declare let stream_edit: any; declare let stream_events: any; declare let stream_list: any; declare let stream_muting: any; +declare let stream_pill: any; declare let stream_popover: any; declare let stream_sort: any; declare let stream_ui_updates: any; diff --git a/static/js/pill_typeahead.js b/static/js/pill_typeahead.js index ae72b41271..8b8981db92 100644 --- a/static/js/pill_typeahead.js +++ b/static/js/pill_typeahead.js @@ -7,18 +7,35 @@ exports.set_up = function (input, pills, opts) { if (!opts.source) { source = () => user_pill.typeahead_source(pills); } + const include_streams = (query) => opts.stream && query.trim().startsWith("#"); input.typeahead({ items: 5, fixed: true, dropup: true, - source, + source() { + if (include_streams(this.query)) { + return stream_pill.typeahead_source(pills); + } + + return source(); + }, highlighter(item) { + if (include_streams(this.query)) { + return typeahead_helper.render_stream(item); + } + return typeahead_helper.render_person(item); }, matcher(item) { let query = this.query.toLowerCase(); 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()) { return item.full_name.toLowerCase().includes(query); } @@ -28,10 +45,19 @@ exports.set_up = function (input, pills, opts) { ); }, 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, ""); }, - updater(user) { - user_pill.append_user(user, pills); + updater(item) { + if (include_streams(this.query)) { + stream_pill.append_stream(item, pills); + } else { + user_pill.append_user(item, pills); + } + input.trigger("focus"); if (opts.update_func) { opts.update_func(); diff --git a/static/js/stream_edit.js b/static/js/stream_edit.js index 91f9c9888c..3816e817e9 100644 --- a/static/js/stream_edit.js +++ b/static/js/stream_edit.js @@ -203,16 +203,25 @@ function submit_add_subscriber_form(e) { return; } - const user_ids = user_pill.get_user_ids(exports.pill_widget); 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 .text(i18n.t("No user to subscribe.")) .addClass("text-error") .removeClass("text-success"); return; } + user_ids = Array.from(user_ids); function invite_success(data) { exports.pill_widget.clear(); @@ -337,7 +346,7 @@ function show_subscription_settings(sub) { 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); } diff --git a/static/js/stream_pill.js b/static/js/stream_pill.js new file mode 100644 index 0000000000..3807d8db48 --- /dev/null +++ b/static/js/stream_pill.js @@ -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; diff --git a/static/templates/subscription_members.hbs b/static/templates/subscription_members.hbs index d3a9ac0cb8..d296edf6eb 100644 --- a/static/templates/subscription_members.hbs +++ b/static/templates/subscription_members.hbs @@ -12,7 +12,7 @@
+ data-placeholder="{{t 'Add subscribers. Use #streamname to subscribe a whole stream' }}">