mirror of https://github.com/zulip/zulip.git
stream create: Overhaul create-stream add-subscribers UI.
The most notable change here is that when you are adding subscribers to a stream as part of creating the stream, you can now use the same essential pill-based UI for adding users as we do when you edit subscribers for an existing stream. We don't try to exactly mimic the edit-stream UI or implementation, since when you are adding subscribers during create-stream, we are just updating a list in memory, whereas in the edit-stream UI, we immediately send info to the server. Fixes #20499
This commit is contained in:
parent
43ee1f7b93
commit
c43d48b22f
|
@ -163,13 +163,6 @@ const bob = {
|
|||
full_name: "Bob van Roberts",
|
||||
};
|
||||
|
||||
const alice2 = {
|
||||
email: "alice2@example.com",
|
||||
delivery_email: "alice2-delivery@example.com",
|
||||
user_id: 204,
|
||||
full_name: "Alice",
|
||||
};
|
||||
|
||||
const charles = {
|
||||
email: "charles@example.com",
|
||||
user_id: 301,
|
||||
|
@ -582,77 +575,6 @@ test_people("set_custom_profile_field_data", () => {
|
|||
assert.equal(person.profile_data[field.id].rendered_value, "<p>Field value</p>");
|
||||
});
|
||||
|
||||
test_people("get_people_for_stream_create", () => {
|
||||
people.add_active_user(alice1);
|
||||
people.add_active_user(bob);
|
||||
people.add_active_user(alice2);
|
||||
assert.equal(people.get_active_human_count(), 4);
|
||||
page_params.is_admin = true;
|
||||
page_params.realm_email_address_visibility = admins_only;
|
||||
|
||||
let others = people.get_people_for_stream_create();
|
||||
let expected = [
|
||||
{
|
||||
email: "alice1-delivery@example.com",
|
||||
user_id: alice1.user_id,
|
||||
full_name: "Alice",
|
||||
checked: false,
|
||||
disabled: false,
|
||||
show_email: true,
|
||||
},
|
||||
{
|
||||
email: "alice2-delivery@example.com",
|
||||
user_id: alice2.user_id,
|
||||
full_name: "Alice",
|
||||
checked: false,
|
||||
disabled: false,
|
||||
show_email: true,
|
||||
},
|
||||
{
|
||||
email: "bob-delivery@example.com",
|
||||
user_id: bob.user_id,
|
||||
full_name: "Bob van Roberts",
|
||||
checked: false,
|
||||
disabled: false,
|
||||
show_email: true,
|
||||
},
|
||||
];
|
||||
assert.deepEqual(others, expected);
|
||||
|
||||
page_params.is_admin = false;
|
||||
alice1.delivery_email = undefined;
|
||||
alice2.delivery_email = undefined;
|
||||
bob.delivery_email = undefined;
|
||||
others = people.get_people_for_stream_create();
|
||||
expected = [
|
||||
{
|
||||
email: "alice1@example.com",
|
||||
user_id: alice1.user_id,
|
||||
full_name: "Alice",
|
||||
checked: false,
|
||||
disabled: false,
|
||||
show_email: false,
|
||||
},
|
||||
{
|
||||
email: "alice2@example.com",
|
||||
user_id: alice2.user_id,
|
||||
full_name: "Alice",
|
||||
checked: false,
|
||||
disabled: false,
|
||||
show_email: false,
|
||||
},
|
||||
{
|
||||
email: "bob@example.com",
|
||||
user_id: bob.user_id,
|
||||
full_name: "Bob van Roberts",
|
||||
checked: false,
|
||||
disabled: false,
|
||||
show_email: false,
|
||||
},
|
||||
];
|
||||
assert.deepEqual(others, expected);
|
||||
});
|
||||
|
||||
test_people("recipient_counts", () => {
|
||||
const user_id = 99;
|
||||
assert.equal(people.get_recipient_count({user_id}), 0);
|
||||
|
@ -672,7 +594,7 @@ test_people("filtered_users", () => {
|
|||
people.add_active_user(plain_noah);
|
||||
|
||||
const search_term = "a";
|
||||
const users = people.get_people_for_stream_create();
|
||||
const users = people.get_realm_users();
|
||||
let filtered_people = people.filter_people_by_search_terms(users, [search_term]);
|
||||
assert.equal(filtered_people.size, 2);
|
||||
assert.ok(filtered_people.has(ashton.user_id));
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
"use strict";
|
||||
|
||||
const {strict: assert} = require("assert");
|
||||
|
||||
const {zrequire} = require("../zjsunit/namespace");
|
||||
const {run_test} = require("../zjsunit/test");
|
||||
const {page_params} = require("../zjsunit/zpage_params");
|
||||
|
||||
const people = zrequire("people");
|
||||
const stream_create_subscribers_data = zrequire("stream_create_subscribers_data");
|
||||
|
||||
const me = {
|
||||
email: "me@zulip.com",
|
||||
full_name: "Zed", // Zed will sort to the top by virtue of being the current user.
|
||||
user_id: 400,
|
||||
};
|
||||
|
||||
const test_user101 = {
|
||||
email: "test101@zulip.com",
|
||||
full_name: "Test User 101",
|
||||
user_id: 101,
|
||||
};
|
||||
|
||||
const test_user102 = {
|
||||
email: "test102@zulip.com",
|
||||
full_name: "Test User 102",
|
||||
user_id: 102,
|
||||
};
|
||||
|
||||
const test_user103 = {
|
||||
email: "test102@zulip.com",
|
||||
full_name: "Test User 103",
|
||||
user_id: 103,
|
||||
};
|
||||
|
||||
function test(label, f) {
|
||||
run_test(label, ({override, override_rewire}) => {
|
||||
page_params.is_admin = false;
|
||||
people.init();
|
||||
people.add_active_user(me);
|
||||
people.add_active_user(test_user101);
|
||||
people.add_active_user(test_user102);
|
||||
people.add_active_user(test_user103);
|
||||
page_params.user_id = me.user_id;
|
||||
people.initialize_current_user(me.user_id);
|
||||
f({override, override_rewire});
|
||||
});
|
||||
}
|
||||
|
||||
test("basics", () => {
|
||||
stream_create_subscribers_data.initialize_with_current_user();
|
||||
|
||||
assert.deepEqual(stream_create_subscribers_data.sorted_user_ids(), [me.user_id]);
|
||||
assert.deepEqual(stream_create_subscribers_data.get_principals(), [me.user_id]);
|
||||
|
||||
const all_user_ids = stream_create_subscribers_data.get_all_user_ids();
|
||||
assert.deepEqual(all_user_ids, [101, 102, 103, 400]);
|
||||
|
||||
stream_create_subscribers_data.add_user_ids(all_user_ids);
|
||||
assert.deepEqual(stream_create_subscribers_data.sorted_user_ids(), [400, 101, 102, 103]);
|
||||
|
||||
stream_create_subscribers_data.remove_user_ids([101, 103]);
|
||||
assert.deepEqual(stream_create_subscribers_data.sorted_user_ids(), [400, 102]);
|
||||
assert.deepEqual(stream_create_subscribers_data.get_potential_subscribers(), [
|
||||
test_user101,
|
||||
test_user103,
|
||||
]);
|
||||
|
||||
assert.ok(stream_create_subscribers_data.must_be_subscribed(me.user_id));
|
||||
assert.ok(!stream_create_subscribers_data.must_be_subscribed(test_user101.user_id));
|
||||
});
|
||||
|
||||
test("must_be_subscribed", () => {
|
||||
page_params.is_admin = false;
|
||||
assert.ok(stream_create_subscribers_data.must_be_subscribed(me.user_id));
|
||||
assert.ok(!stream_create_subscribers_data.must_be_subscribed(test_user101.user_id));
|
||||
page_params.is_admin = true;
|
||||
assert.ok(!stream_create_subscribers_data.must_be_subscribed(me.user_id));
|
||||
assert.ok(!stream_create_subscribers_data.must_be_subscribed(test_user101.user_id));
|
||||
});
|
|
@ -4,33 +4,29 @@ import type {ElementHandle, Page} from "puppeteer";
|
|||
|
||||
import common from "../puppeteer_lib/common";
|
||||
|
||||
async function user_checkbox(page: Page, name: string): Promise<string> {
|
||||
async function user_row_selector(page: Page, name: string): Promise<string> {
|
||||
const user_id = await common.get_user_id_from_name(page, name);
|
||||
return `#user_checkbox_${CSS.escape(user_id.toString())}`;
|
||||
const selector = `.remove_potential_subscriber[data-user-id="${user_id}"]`;
|
||||
return selector;
|
||||
}
|
||||
|
||||
async function user_span(page: Page, name: string): Promise<string> {
|
||||
return (await user_checkbox(page, name)) + " span";
|
||||
async function await_user_visible(page: Page, name: string): Promise<void> {
|
||||
const selector = await user_row_selector(page, name);
|
||||
await page.waitForSelector(selector, {visible: true});
|
||||
}
|
||||
|
||||
async function stream_checkbox(page: Page, stream_name: string): Promise<string> {
|
||||
const stream_id = await common.get_stream_id(page, stream_name);
|
||||
return `#stream-checkboxes [data-stream-id="${CSS.escape(stream_id.toString())}"]`;
|
||||
async function await_user_hidden(page: Page, name: string): Promise<void> {
|
||||
const selector = await user_row_selector(page, name);
|
||||
await page.waitForSelector(selector, {hidden: true});
|
||||
}
|
||||
|
||||
async function stream_span(page: Page, stream_name: string): Promise<string> {
|
||||
return (await stream_checkbox(page, stream_name)) + " input ~ span";
|
||||
}
|
||||
|
||||
async function wait_for_checked(page: Page, user_name: string, is_checked: boolean): Promise<void> {
|
||||
const selector = await user_checkbox(page, user_name);
|
||||
await page.waitForFunction(
|
||||
(selector: string, is_checked: boolean) =>
|
||||
$(selector).find("input").prop("checked") === is_checked,
|
||||
{},
|
||||
selector,
|
||||
is_checked,
|
||||
async function add_user_to_stream(page: Page, name: string): Promise<void> {
|
||||
const user_id = await common.get_user_id_from_name(page, name);
|
||||
await page.evaluate(
|
||||
(user_id: Number) => zulip_test.add_user_id_to_new_stream(user_id),
|
||||
user_id,
|
||||
);
|
||||
await await_user_visible(page, name);
|
||||
}
|
||||
|
||||
async function stream_name_error(page: Page): Promise<string> {
|
||||
|
@ -83,29 +79,9 @@ async function test_subscription_button(page: Page): Promise<void> {
|
|||
button = await subscribed();
|
||||
}
|
||||
|
||||
async function click_create_new_stream(
|
||||
page: Page,
|
||||
cordelia_checkbox: string,
|
||||
othello_checkbox: string,
|
||||
): Promise<void> {
|
||||
async function click_create_new_stream(page: Page): Promise<void> {
|
||||
await page.click("#add_new_subscription .create_stream_button");
|
||||
await page.waitForSelector(cordelia_checkbox, {visible: true});
|
||||
await page.waitForSelector(othello_checkbox, {visible: true});
|
||||
}
|
||||
|
||||
async function open_copy_from_stream_dropdown(
|
||||
page: Page,
|
||||
scotland_checkbox: string,
|
||||
rome_checkbox: string,
|
||||
): Promise<void> {
|
||||
await page.click("#copy-from-stream-expand-collapse .control-label");
|
||||
await page.waitForSelector(scotland_checkbox, {visible: true});
|
||||
await page.waitForSelector(rome_checkbox, {visible: true});
|
||||
}
|
||||
|
||||
async function verify_check_all_only_affects_visible_users(page: Page): Promise<void> {
|
||||
await wait_for_checked(page, "cordelia", false);
|
||||
await wait_for_checked(page, "othello", true);
|
||||
await await_user_visible(page, "desdemona");
|
||||
}
|
||||
|
||||
async function clear_ot_filter_with_backspace(page: Page): Promise<void> {
|
||||
|
@ -114,52 +90,33 @@ async function clear_ot_filter_with_backspace(page: Page): Promise<void> {
|
|||
await page.keyboard.press("Backspace");
|
||||
}
|
||||
|
||||
async function verify_filtered_users_are_visible_again(
|
||||
page: Page,
|
||||
cordelia_checkbox: string,
|
||||
othello_checkbox: string,
|
||||
): Promise<void> {
|
||||
await page.waitForSelector(cordelia_checkbox, {visible: true});
|
||||
await page.waitForSelector(othello_checkbox, {visible: true});
|
||||
}
|
||||
|
||||
async function test_user_filter_ui(
|
||||
page: Page,
|
||||
cordelia_checkbox: string,
|
||||
othello_checkbox: string,
|
||||
scotland_checkbox: string,
|
||||
rome_checkbox: string,
|
||||
): Promise<void> {
|
||||
async function test_user_filter_ui(page: Page): Promise<void> {
|
||||
await page.waitForSelector("form#stream_creation_form", {visible: true});
|
||||
// Desdemona should be checked by default
|
||||
await wait_for_checked(page, "desdemona", true);
|
||||
// Desdemona should be there by default
|
||||
await await_user_visible(page, "desdemona");
|
||||
|
||||
await add_user_to_stream(page, "cordelia");
|
||||
await add_user_to_stream(page, "othello");
|
||||
|
||||
await page.type(`form#stream_creation_form [name="user_list_filter"]`, "ot", {delay: 100});
|
||||
await page.waitForSelector("#user-checkboxes", {visible: true});
|
||||
await page.waitForSelector("#create_stream_subscribers", {visible: true});
|
||||
// Wait until filtering is completed.
|
||||
await page.waitForFunction(
|
||||
() => document.querySelectorAll("#user-checkboxes label").length === 1,
|
||||
() =>
|
||||
document.querySelectorAll("#create_stream_subscribers .remove_potential_subscriber")
|
||||
.length === 1,
|
||||
);
|
||||
|
||||
await page.waitForSelector(cordelia_checkbox, {hidden: true});
|
||||
await page.waitForSelector(othello_checkbox, {visible: true});
|
||||
await await_user_hidden(page, "cordelia");
|
||||
await await_user_hidden(page, "desdemona");
|
||||
await await_user_visible(page, "othello");
|
||||
|
||||
// Filter shouldn't affect streams.
|
||||
await page.waitForSelector(scotland_checkbox, {visible: true});
|
||||
await page.waitForSelector(rome_checkbox, {visible: true});
|
||||
|
||||
// Test check all
|
||||
await page.click(".subs_set_all_users");
|
||||
await wait_for_checked(page, "othello", true);
|
||||
// Clear the filter.
|
||||
await clear_ot_filter_with_backspace(page);
|
||||
await verify_filtered_users_are_visible_again(page, cordelia_checkbox, othello_checkbox);
|
||||
await verify_check_all_only_affects_visible_users(page);
|
||||
|
||||
// Test unset all
|
||||
await page.click(".subs_unset_all_users");
|
||||
await verify_filtered_users_are_visible_again(page, cordelia_checkbox, othello_checkbox);
|
||||
await wait_for_checked(page, "cordelia", false);
|
||||
await wait_for_checked(page, "othello", false);
|
||||
await await_user_visible(page, "cordelia");
|
||||
await await_user_visible(page, "desdemona");
|
||||
await await_user_visible(page, "othello");
|
||||
}
|
||||
|
||||
async function create_stream(page: Page): Promise<void> {
|
||||
|
@ -168,13 +125,6 @@ async function create_stream(page: Page): Promise<void> {
|
|||
stream_name: "Puppeteer",
|
||||
stream_description: "Everything Puppeteer",
|
||||
});
|
||||
await page.click(await stream_span(page, "Scotland")); // Subscribes all users from Scotland
|
||||
await page.click(await user_span(page, "cordelia")); // Add cordelia.
|
||||
await page.click(await user_span(page, "desdemona")); // Add cordelia.
|
||||
await page.click(await user_span(page, "othello")); // Remove othello who was selected from Scotland.
|
||||
await wait_for_checked(page, "cordelia", true);
|
||||
await wait_for_checked(page, "desdemona", true); // Add desdemona back as we did unset all in last test.
|
||||
await wait_for_checked(page, "othello", false);
|
||||
await page.click("form#stream_creation_form .finalize_create_stream");
|
||||
await page.waitForFunction(() => $(".stream-name").is(':contains("Puppeteer")'));
|
||||
const stream_name = await common.get_text_from_selector(
|
||||
|
@ -189,9 +139,9 @@ async function create_stream(page: Page): Promise<void> {
|
|||
assert.strictEqual(stream_name, "Puppeteer");
|
||||
assert.strictEqual(stream_description, "Everything Puppeteer");
|
||||
|
||||
// Assert subscriber count becomes 6(scotland(+5), cordelia(+1), othello(-1), Desdemona(+1)).
|
||||
// Assert subscriber count becomes 3 (cordelia, desdemona, othello)
|
||||
await page.waitForFunction(
|
||||
(subscriber_count_selector: string) => $(subscriber_count_selector).text().trim() === "6",
|
||||
(subscriber_count_selector: string) => $(subscriber_count_selector).text().trim() === "3",
|
||||
{},
|
||||
subscriber_count_selector,
|
||||
);
|
||||
|
@ -201,13 +151,13 @@ async function test_streams_with_empty_names_cannot_be_created(page: Page): Prom
|
|||
await page.click("#add_new_subscription .create_stream_button");
|
||||
await page.waitForSelector("form#stream_creation_form", {visible: true});
|
||||
await common.fill_form(page, "form#stream_creation_form", {stream_name: " "});
|
||||
await page.click("form#stream_creation_form button.button.sea-green");
|
||||
await page.click("form#stream_creation_form button.finalize_create_stream");
|
||||
assert.strictEqual(await stream_name_error(page), "A stream needs to have a name");
|
||||
}
|
||||
|
||||
async function test_streams_with_duplicate_names_cannot_be_created(page: Page): Promise<void> {
|
||||
await common.fill_form(page, "form#stream_creation_form", {stream_name: "Puppeteer"});
|
||||
await page.click("form#stream_creation_form button.button.sea-green");
|
||||
await page.click("form#stream_creation_form button.finalize_create_stream");
|
||||
assert.strictEqual(await stream_name_error(page), "A stream with this name already exists");
|
||||
|
||||
const cancel_button_selector = "form#stream_creation_form button.button.white";
|
||||
|
@ -215,20 +165,8 @@ async function test_streams_with_duplicate_names_cannot_be_created(page: Page):
|
|||
}
|
||||
|
||||
async function test_stream_creation(page: Page): Promise<void> {
|
||||
const cordelia_checkbox = await user_checkbox(page, "cordelia");
|
||||
const othello_checkbox = await user_checkbox(page, "othello");
|
||||
const scotland_checkbox = await stream_checkbox(page, "Scotland");
|
||||
const rome_checkbox = await stream_checkbox(page, "Rome");
|
||||
|
||||
await click_create_new_stream(page, cordelia_checkbox, othello_checkbox);
|
||||
await open_copy_from_stream_dropdown(page, scotland_checkbox, rome_checkbox);
|
||||
await test_user_filter_ui(
|
||||
page,
|
||||
cordelia_checkbox,
|
||||
othello_checkbox,
|
||||
scotland_checkbox,
|
||||
rome_checkbox,
|
||||
);
|
||||
await click_create_new_stream(page);
|
||||
await test_user_filter_ui(page);
|
||||
await create_stream(page);
|
||||
await test_streams_with_empty_names_cannot_be_created(page);
|
||||
await test_streams_with_duplicate_names_cannot_be_created(page);
|
||||
|
|
|
@ -1067,44 +1067,6 @@ export function get_user_id_from_name(full_name) {
|
|||
return person.user_id;
|
||||
}
|
||||
|
||||
function people_cmp(person1, person2) {
|
||||
const name_cmp = util.strcmp(person1.full_name, person2.full_name);
|
||||
if (name_cmp < 0) {
|
||||
return -1;
|
||||
} else if (name_cmp > 0) {
|
||||
return 1;
|
||||
}
|
||||
return util.strcmp(person1.email, person2.email);
|
||||
}
|
||||
|
||||
export function get_people_for_stream_create() {
|
||||
/*
|
||||
If you are thinking of reusing this function,
|
||||
a better option in most cases is to just
|
||||
call `get_realm_users()` and then filter out
|
||||
the "me" user yourself as part of any other
|
||||
filtering that you are doing.
|
||||
|
||||
In particular, this function does a sort
|
||||
that is kinda expensive and may not apply
|
||||
to your use case.
|
||||
*/
|
||||
const people_minus_you = [];
|
||||
for (const person of active_user_dict.values()) {
|
||||
if (!is_my_user_id(person.user_id)) {
|
||||
people_minus_you.push({
|
||||
email: get_visible_email(person),
|
||||
show_email: settings_data.show_email(),
|
||||
user_id: person.user_id,
|
||||
full_name: person.full_name,
|
||||
checked: false,
|
||||
disabled: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
return people_minus_you.sort(people_cmp);
|
||||
}
|
||||
|
||||
export function track_duplicate_full_name(full_name, user_id, to_remove) {
|
||||
let ids;
|
||||
if (duplicate_full_name_data.has(full_name)) {
|
||||
|
|
|
@ -2,26 +2,19 @@ import $ from "jquery";
|
|||
|
||||
import render_announce_stream_docs from "../templates/announce_stream_docs.hbs";
|
||||
import render_subscription_invites_warning_modal from "../templates/confirm_dialog/confirm_subscription_invites_warning.hbs";
|
||||
import render_new_stream_user from "../templates/new_stream_user.hbs";
|
||||
import render_new_stream_users from "../templates/stream_settings/new_stream_users.hbs";
|
||||
|
||||
import * as channel from "./channel";
|
||||
import * as confirm_dialog from "./confirm_dialog";
|
||||
import {$t, $t_html} from "./i18n";
|
||||
import * as ListWidget from "./list_widget";
|
||||
import * as loading from "./loading";
|
||||
import {page_params} from "./page_params";
|
||||
import * as peer_data from "./peer_data";
|
||||
import * as people from "./people";
|
||||
import * as settings_data from "./settings_data";
|
||||
import * as stream_create_subscribers from "./stream_create_subscribers";
|
||||
import * as stream_data from "./stream_data";
|
||||
import * as stream_settings_data from "./stream_settings_data";
|
||||
import * as stream_settings_ui from "./stream_settings_ui";
|
||||
import * as ui_report from "./ui_report";
|
||||
|
||||
let created_stream;
|
||||
let all_users;
|
||||
let all_users_list_widget;
|
||||
|
||||
export function reset_created_stream() {
|
||||
created_stream = undefined;
|
||||
|
@ -146,11 +139,6 @@ function update_announce_stream_state() {
|
|||
$("#announce-new-stream").show();
|
||||
}
|
||||
|
||||
function get_principals() {
|
||||
// Return list of user ids which were selected by user.
|
||||
return all_users.filter((user) => user.checked === true).map((user) => user.user_id);
|
||||
}
|
||||
|
||||
function create_stream() {
|
||||
const data = {};
|
||||
const stream_name = $("#create_stream_name").val().trim();
|
||||
|
@ -233,7 +221,7 @@ function create_stream() {
|
|||
|
||||
// TODO: We can eliminate the user_ids -> principals conversion
|
||||
// once we upgrade the backend to accept user_ids.
|
||||
const user_ids = get_principals();
|
||||
const user_ids = stream_create_subscribers.get_principals();
|
||||
data.principals = JSON.stringify(user_ids);
|
||||
|
||||
loading.make_indicator($("#stream_creating_indicator"), {
|
||||
|
@ -303,40 +291,7 @@ export function show_new_stream_modal() {
|
|||
$(".right .settings").hide();
|
||||
stream_settings_ui.hide_or_disable_stream_privacy_options_if_required($("#stream-creation"));
|
||||
|
||||
const add_people_container = $("#people_to_add");
|
||||
add_people_container.html(
|
||||
render_new_stream_users({
|
||||
streams: stream_settings_data.get_streams_for_settings_page(),
|
||||
}),
|
||||
);
|
||||
|
||||
all_users = people.get_people_for_stream_create();
|
||||
// Add current user on top of list
|
||||
const current_user = people.get_by_user_id(page_params.user_id);
|
||||
all_users.unshift({
|
||||
show_email: settings_data.show_email(),
|
||||
email: people.get_visible_email(current_user),
|
||||
user_id: current_user.user_id,
|
||||
full_name: current_user.full_name,
|
||||
checked: true,
|
||||
disabled: !page_params.is_admin,
|
||||
});
|
||||
|
||||
all_users_list_widget = ListWidget.create($("#user-checkboxes"), all_users, {
|
||||
name: "new_stream_add_users",
|
||||
parent_container: add_people_container,
|
||||
modifier(item) {
|
||||
return render_new_stream_user(item);
|
||||
},
|
||||
filter: {
|
||||
element: $("#people_to_add .add-user-list-filter"),
|
||||
predicate(user, search_term) {
|
||||
return people.build_person_matcher(search_term)(user);
|
||||
},
|
||||
},
|
||||
simplebar_container: $("#user-checkboxes-simplebar-wrapper"),
|
||||
html_selector: (user) => $(`#${CSS.escape("user_checkbox_" + user.user_id)}`),
|
||||
});
|
||||
stream_create_subscribers.build_widgets();
|
||||
|
||||
// Select the first visible and enabled choice for stream privacy.
|
||||
$("#make-invite-only input:visible:not([disabled]):first").prop("checked", true);
|
||||
|
@ -365,82 +320,9 @@ export function show_new_stream_modal() {
|
|||
clear_error_display();
|
||||
}
|
||||
|
||||
function create_handlers_for_users(container) {
|
||||
// container should be $('#people_to_add')...see caller to verify
|
||||
function update_checked_state_for_users(value, users) {
|
||||
// Update the all_users backing data structure for
|
||||
// which users will be submitted should the user click save,
|
||||
// and also ensure that any visible checkboxes reflect
|
||||
// the state of that data structure.
|
||||
|
||||
// If we have to rerender a very large number of users, it's
|
||||
// eventually faster to just do a full redraw rather than
|
||||
// many hundreds of single-item rerenders.
|
||||
const full_redraw = !users || users.length > 250;
|
||||
for (const user of all_users) {
|
||||
// We don't want to uncheck the user creating the stream if it is not admin.
|
||||
if (user.user_id === page_params.user_id && value === false && !page_params.is_admin) {
|
||||
continue;
|
||||
}
|
||||
// We update for all users if `users` parameter is empty.
|
||||
if (users === undefined || users.includes(user.user_id)) {
|
||||
user.checked = value;
|
||||
|
||||
if (!full_redraw) {
|
||||
all_users_list_widget.render_item(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (full_redraw) {
|
||||
all_users_list_widget.hard_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
container.on("change", "#user-checkboxes input", (e) => {
|
||||
const elem = $(e.target);
|
||||
const user_id = Number.parseInt(elem.attr("data-user-id"), 10);
|
||||
const checked = elem.prop("checked");
|
||||
update_checked_state_for_users(checked, [user_id]);
|
||||
});
|
||||
|
||||
// 'Check all' and 'Uncheck all' visible users
|
||||
container.on("click", ".subs_set_all_users, .subs_unset_all_users", (e) => {
|
||||
e.preventDefault();
|
||||
// Only `check / uncheck` users who are displayed.
|
||||
const mark_checked = e.target.classList.contains("subs_set_all_users");
|
||||
const users_displayed = all_users_list_widget.get_current_list();
|
||||
if (all_users.length !== users_displayed.length) {
|
||||
update_checked_state_for_users(
|
||||
mark_checked,
|
||||
users_displayed.map((user) => user.user_id),
|
||||
);
|
||||
} else {
|
||||
update_checked_state_for_users(mark_checked);
|
||||
}
|
||||
});
|
||||
|
||||
container.on("click", "#copy-from-stream-expand-collapse", (e) => {
|
||||
e.preventDefault();
|
||||
$("#stream-checkboxes").toggle();
|
||||
$("#copy-from-stream-expand-collapse .toggle").toggleClass("fa-caret-right fa-caret-down");
|
||||
});
|
||||
|
||||
container.on("change", "#stream-checkboxes label.checkbox", (e) => {
|
||||
e.preventDefault();
|
||||
const elem = $(e.target).closest("[data-stream-id]");
|
||||
const stream_id = Number.parseInt(elem.attr("data-stream-id"), 10);
|
||||
const checked = elem.find("input").prop("checked");
|
||||
const subscriber_ids = peer_data.get_subscribers(stream_id);
|
||||
update_checked_state_for_users(checked, subscriber_ids);
|
||||
});
|
||||
}
|
||||
|
||||
export function set_up_handlers() {
|
||||
// Sets up all the event handlers concerning the `People to add`
|
||||
// section in Create stream UI.
|
||||
const people_to_add_holder = $("#people_to_add").expectOne();
|
||||
create_handlers_for_users(people_to_add_holder);
|
||||
stream_create_subscribers.create_handlers(people_to_add_holder);
|
||||
|
||||
const container = $("#stream-creation").expectOne();
|
||||
|
||||
|
@ -457,7 +339,7 @@ export function set_up_handlers() {
|
|||
return;
|
||||
}
|
||||
|
||||
const principals = get_principals();
|
||||
const principals = stream_create_subscribers.get_principals();
|
||||
if (principals.length === 0) {
|
||||
stream_subscription_error.report_no_subs_to_stream();
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
import $ from "jquery";
|
||||
|
||||
import render_new_stream_user from "../templates/stream_settings/new_stream_user.hbs";
|
||||
import render_new_stream_users from "../templates/stream_settings/new_stream_users.hbs";
|
||||
|
||||
import * as add_subscribers_pill from "./add_subscribers_pill";
|
||||
import * as ListWidget from "./list_widget";
|
||||
import {page_params} from "./page_params";
|
||||
import * as people from "./people";
|
||||
import * as settings_data from "./settings_data";
|
||||
import * as stream_create_subscribers_data from "./stream_create_subscribers_data";
|
||||
|
||||
let pill_widget;
|
||||
let all_users_list_widget;
|
||||
|
||||
export function get_principals() {
|
||||
return stream_create_subscribers_data.get_principals();
|
||||
}
|
||||
|
||||
function redraw_subscriber_list() {
|
||||
all_users_list_widget.replace_list_data(stream_create_subscribers_data.sorted_user_ids());
|
||||
}
|
||||
|
||||
function add_user_ids(user_ids) {
|
||||
stream_create_subscribers_data.add_user_ids(user_ids);
|
||||
redraw_subscriber_list();
|
||||
}
|
||||
|
||||
function add_all_users() {
|
||||
const user_ids = stream_create_subscribers_data.get_all_user_ids();
|
||||
add_user_ids(user_ids);
|
||||
}
|
||||
|
||||
function remove_user_ids(user_ids) {
|
||||
stream_create_subscribers_data.remove_user_ids(user_ids);
|
||||
redraw_subscriber_list();
|
||||
}
|
||||
|
||||
function build_pill_widget({parent_container}) {
|
||||
const pill_container = parent_container.find(".pill-container");
|
||||
const get_potential_subscribers = stream_create_subscribers_data.get_potential_subscribers;
|
||||
|
||||
pill_widget = add_subscribers_pill.create({pill_container, get_potential_subscribers});
|
||||
}
|
||||
|
||||
export function create_handlers(container) {
|
||||
container.on("click", ".add_all_users_to_stream", (e) => {
|
||||
e.preventDefault();
|
||||
add_all_users();
|
||||
$(".add-user-list-filter").focus();
|
||||
});
|
||||
|
||||
container.on("click", ".remove_potential_subscriber", (e) => {
|
||||
e.preventDefault();
|
||||
const elem = $(e.target);
|
||||
const user_id = Number.parseInt(elem.attr("data-user-id"), 10);
|
||||
remove_user_ids([user_id]);
|
||||
});
|
||||
|
||||
function add_users({pill_user_ids}) {
|
||||
add_user_ids(pill_user_ids);
|
||||
pill_widget.clear();
|
||||
}
|
||||
|
||||
add_subscribers_pill.set_up_handlers({
|
||||
get_pill_widget: () => pill_widget,
|
||||
parent_container: container,
|
||||
pill_selector: ".add_subscribers_container .input",
|
||||
button_selector: ".add_subscribers_container button.add-subscriber-button",
|
||||
action: add_users,
|
||||
});
|
||||
}
|
||||
|
||||
export function build_widgets() {
|
||||
const add_people_container = $("#people_to_add");
|
||||
add_people_container.html(render_new_stream_users({}));
|
||||
|
||||
const simplebar_container = add_people_container.find(".subscriber_list_container");
|
||||
|
||||
build_pill_widget({parent_container: add_people_container});
|
||||
|
||||
stream_create_subscribers_data.initialize_with_current_user();
|
||||
const current_user_id = page_params.user_id;
|
||||
|
||||
all_users_list_widget = ListWidget.create($("#create_stream_subscribers"), [current_user_id], {
|
||||
name: "new_stream_add_users",
|
||||
parent_container: add_people_container,
|
||||
modifier(user_id) {
|
||||
const user = people.get_by_user_id(user_id);
|
||||
const item = {
|
||||
show_email: settings_data.show_email(),
|
||||
email: people.get_visible_email(user),
|
||||
user_id,
|
||||
full_name: user.full_name,
|
||||
is_current_user: user_id === current_user_id,
|
||||
disabled: stream_create_subscribers_data.must_be_subscribed(user_id),
|
||||
};
|
||||
return render_new_stream_user(item);
|
||||
},
|
||||
filter: {
|
||||
element: $("#people_to_add .add-user-list-filter"),
|
||||
predicate(user_id, search_term) {
|
||||
const user = people.get_by_user_id(user_id);
|
||||
return people.build_person_matcher(search_term)(user);
|
||||
},
|
||||
},
|
||||
simplebar_container,
|
||||
html_selector: (user_id) => {
|
||||
const user = people.get_by_user_id(user_id);
|
||||
return $(`#${CSS.escape("user_checkbox_" + user.user_id)}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function add_user_id_to_new_stream(user_id) {
|
||||
// This is only used by puppeteer tests.
|
||||
add_user_ids([user_id]);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import {page_params} from "./page_params";
|
||||
import * as people from "./people";
|
||||
|
||||
let user_id_set;
|
||||
|
||||
export function initialize_with_current_user() {
|
||||
const current_user_id = page_params.user_id;
|
||||
user_id_set = new Set();
|
||||
user_id_set.add(current_user_id);
|
||||
}
|
||||
|
||||
export function sorted_user_ids() {
|
||||
const users = people.get_users_from_ids(Array.from(user_id_set));
|
||||
people.sort_but_pin_current_user_on_top(users);
|
||||
return users.map((user) => user.user_id);
|
||||
}
|
||||
|
||||
export function get_all_user_ids() {
|
||||
const potential_subscribers = people.get_realm_users();
|
||||
const user_ids = potential_subscribers.map((user) => user.user_id);
|
||||
// sort for determinism
|
||||
user_ids.sort((a, b) => a - b);
|
||||
return user_ids;
|
||||
}
|
||||
|
||||
export function get_principals() {
|
||||
// Return list of user ids which were selected by user.
|
||||
return Array.from(user_id_set);
|
||||
}
|
||||
|
||||
export function get_potential_subscribers() {
|
||||
const potential_subscribers = people.get_realm_users();
|
||||
return potential_subscribers.filter((user) => !user_id_set.has(user.user_id));
|
||||
}
|
||||
|
||||
export function must_be_subscribed(user_id) {
|
||||
return !page_params.is_admin && user_id === page_params.user_id;
|
||||
}
|
||||
|
||||
export function add_user_ids(user_ids) {
|
||||
for (const user_id of user_ids) {
|
||||
if (!user_id_set.has(user_id)) {
|
||||
const user = people.get_by_user_id(user_id);
|
||||
if (user) {
|
||||
user_id_set.add(user_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function remove_user_ids(user_ids) {
|
||||
for (const user_id of user_ids) {
|
||||
user_id_set.delete(user_id);
|
||||
}
|
||||
}
|
|
@ -14,3 +14,4 @@ export {last_visible as last_visible_row, id as row_id} from "./rows";
|
|||
export {cancel as cancel_compose} from "./compose_actions";
|
||||
export {page_params, page_params_parse_time} from "./page_params";
|
||||
export {initiate as initiate_reload} from "./reload";
|
||||
export {add_user_id_to_new_stream} from "./stream_create_subscribers";
|
||||
|
|
|
@ -805,8 +805,22 @@ h4.stream_setting_subsection_title {
|
|||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.add_all_users_to_stream {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.create_stream_subscriber_list_header {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 3px;
|
||||
|
||||
h5 {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.add-user-list-filter {
|
||||
width: calc(100% - 10px);
|
||||
width: 140px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#stream_creation_form {
|
||||
|
|
|
@ -2298,12 +2298,7 @@ div.floating_recipient {
|
|||
color: hsl(0, 0%, 100%);
|
||||
}
|
||||
|
||||
#user-checkboxes-simplebar-wrapper {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#user-checkboxes {
|
||||
#create_stream_subscribers {
|
||||
margin-top: 10px;
|
||||
|
||||
.checkbox {
|
||||
|
@ -2316,24 +2311,6 @@ div.floating_recipient {
|
|||
}
|
||||
}
|
||||
|
||||
#stream-checkboxes {
|
||||
margin-top: 10px;
|
||||
display: none;
|
||||
|
||||
.checkbox {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin: 5px 0;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
#copy-from-stream-expand-collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sub_button_row {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<label class="checkbox add-user-label" id="user_checkbox_{{user_id}}">
|
||||
<input type="checkbox" name="user" {{#if checked}}checked="checked"{{#if disabled}} disabled="disabled"{{/if}}{{/if}} data-user-id="{{user_id}}"/>
|
||||
<span></span>
|
||||
{{full_name}} {{#if show_email}}({{email}}){{else}}({{#tr}}User ID: {user_id}; <em>email hidden</em>{{/tr}}){{/if}}
|
||||
</label>
|
|
@ -6,7 +6,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<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 sea-green" tabindex="0">
|
||||
{{t 'Add' }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<tr>
|
||||
<td>
|
||||
{{full_name}}{{#if is_current_user}} <span class="my_user_status">{{t "(you)"}}</span>{{/if}}
|
||||
</td>
|
||||
{{#if show_email}}
|
||||
<td class="subscriber-email">{{email}}</td>
|
||||
{{else}}
|
||||
<td class="hidden-subscriber-email">{{t "(hidden)"}}</td>
|
||||
{{/if}}
|
||||
<td>{{user_id}} </td>
|
||||
<td>
|
||||
<button {{#if disabled}} disabled="disabled"{{/if}} data-user-id="{{user_id}}" class="remove_potential_subscriber button small rounded btn-danger">Remove</button>
|
||||
</td>
|
||||
</tr>
|
|
@ -1,32 +1,27 @@
|
|||
{{! Client-side Mustache template for rendering users in the stream creation modal.}}
|
||||
|
||||
<div id="copy-from-stream-expand-collapse" class="add-user-label">
|
||||
<i class="toggle fa fa-caret-right" aria-hidden="true"></i>
|
||||
<span class="control-label">
|
||||
{{t "Copy from stream" }}
|
||||
</span>
|
||||
<div class="subscriber_list_add float-left">
|
||||
{{> add_subscribers_form}}
|
||||
</div>
|
||||
|
||||
<div id="stream-checkboxes">
|
||||
{{#each streams}}
|
||||
<label class="checkbox add-user-label" data-stream-id="{{this.stream_id}}">
|
||||
<input type="checkbox" name="stream" />
|
||||
<span></span>
|
||||
{{this.name}} ( <i class="fa fa-user" aria-hidden="true"></i> {{this.subscriber_count}})
|
||||
</label>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
{{t "Do you want to add everyone?"}}
|
||||
<button class="add_all_users_to_stream small button rounded sea-green">{{t 'Add all users'}}</button>
|
||||
|
||||
<div class="create_stream_subscriber_list_header">
|
||||
<h5>Subscribers</h5>
|
||||
<input class="add-user-list-filter" name="user_list_filter" type="text"
|
||||
autocomplete="off" placeholder="{{t 'Filter' }}" />
|
||||
|
||||
|
||||
<div>
|
||||
<a draggable="false" class="subs_set_all_users" tabindex="0">{{t "Check all" }}</a> |
|
||||
<a draggable="false" class="subs_unset_all_users" tabindex="0">{{t "Uncheck all" }}</a>
|
||||
autocomplete="off" placeholder="{{t 'Filter subscribers' }}" />
|
||||
</div>
|
||||
|
||||
<div id="user-checkboxes-simplebar-wrapper" data-simplebar>
|
||||
<div id="user-checkboxes"></div>
|
||||
<div class="subscriber-list-box">
|
||||
<div class="subscriber_list_container" data-simplebar>
|
||||
<table class="subscriber-list table table-striped">
|
||||
<thead class="table-sticky-headers">
|
||||
<th>{{t "Name" }}</th>
|
||||
<th>{{t "Email" }}</th>
|
||||
<th>{{t "User ID" }}</th>
|
||||
<th>{{t "Action" }}</th>
|
||||
</thead>
|
||||
<tbody id="create_stream_subscribers" class="subscriber_table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</section>
|
||||
<section class="block">
|
||||
<label class="stream-title" for="people_to_add">
|
||||
{{t "People to add" }}
|
||||
<h4>{{t "Choose subscribers" }}</h4>
|
||||
</label>
|
||||
<div id="stream_subscription_error" class="stream_creation_error"></div>
|
||||
<div class="controls" id="people_to_add"></div>
|
||||
|
|
|
@ -167,6 +167,7 @@ EXEMPT_FILES = make_set(
|
|||
"static/js/stream_bar.js",
|
||||
"static/js/stream_color.js",
|
||||
"static/js/stream_create.js",
|
||||
"static/js/stream_create_subscribers.js",
|
||||
"static/js/stream_edit.js",
|
||||
"static/js/stream_edit_subscribers.js",
|
||||
"static/js/stream_list.js",
|
||||
|
|
Loading…
Reference in New Issue