mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	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:
		@@ -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));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										80
									
								
								frontend_tests/node_tests/stream_create_subscribers_data.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								frontend_tests/node_tests/stream_create_subscribers_data.js
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										118
									
								
								static/js/stream_create_subscribers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								static/js/stream_create_subscribers.js
									
									
									
									
									
										Normal file
									
								
							@@ -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]);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								static/js/stream_create_subscribers_data.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								static/js/stream_create_subscribers_data.js
									
									
									
									
									
										Normal file
									
								
							@@ -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>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								static/templates/stream_settings/new_stream_user.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								static/templates/stream_settings/new_stream_user.hbs
									
									
									
									
									
										Normal file
									
								
							@@ -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 />
 | 
			
		||||
<input class="add-user-list-filter" name="user_list_filter" type="text"
 | 
			
		||||
  autocomplete="off" placeholder="{{t 'Filter' }}" />
 | 
			
		||||
 | 
			
		||||
{{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>
 | 
			
		||||
    <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>
 | 
			
		||||
<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 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",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user