diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 6adb72fd86..fec0f149ca 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 8.0 +**Feature level 200** + +* [`PATCH /streams/{stream_id}`](/api/update-stream): Added + `is_default_stream` parameter to add or remove the stream as a default + stream for new users. + **Feature level 199** * [`POST /register`](/api/register-queue), [`GET /events`][/api/get-events], diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index be27a483ac..fd679ebe76 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -102,6 +102,7 @@ export function dispatch_normal_event(event) { case "default_streams": stream_data.set_realm_default_streams(event.default_streams); settings_streams.update_default_streams_table(); + stream_settings_ui.update_is_default_stream(); break; case "delete_message": { diff --git a/web/src/settings_org.js b/web/src/settings_org.js index 0f5d13b881..0db8644801 100644 --- a/web/src/settings_org.js +++ b/web/src/settings_org.js @@ -168,6 +168,9 @@ function get_property_value(property_name, for_realm_default_settings, sub) { if (property_name === "stream_privacy") { return stream_data.get_stream_privacy_policy(sub.stream_id); } + if (property_name === "is_default_stream") { + return stream_data.is_default_stream_id(sub.stream_id); + } return sub[property_name]; } diff --git a/web/src/stream_edit.js b/web/src/stream_edit.js index 1556bd3f6c..618edb9057 100644 --- a/web/src/stream_edit.js +++ b/web/src/stream_edit.js @@ -247,6 +247,7 @@ export function show_settings_for(node) { stream_post_policy_values: stream_data.stream_post_policy_values, stream_privacy_policy_values: stream_data.stream_privacy_policy_values, stream_privacy_policy: stream_data.get_stream_privacy_policy(stream_id), + check_default_stream: stream_data.is_default_stream_id(stream_id), zulip_plan_is_not_limited: page_params.zulip_plan_is_not_limited, upgrade_text_for_wide_organization_logo: page_params.upgrade_text_for_wide_organization_logo, @@ -679,6 +680,9 @@ export function initialize() { const sub = sub_store.get(stream_id); const $subsection = $(e.target).closest(".settings-subsection-parent"); settings_org.save_discard_widget_status_handler($subsection, false, sub); + if (sub) { + stream_ui_updates.update_default_stream_and_stream_privacy_state($subsection); + } return true; }); @@ -714,6 +718,7 @@ export function initialize() { for (const elem of settings_org.get_subsection_property_elements($subsection)) { settings_org.discard_property_element_changes(elem, false, sub); } + stream_ui_updates.update_default_stream_and_stream_privacy_state($subsection); const $save_btn_controls = $(e.target).closest(".save-button-controls"); settings_org.change_save_button_state($save_btn_controls, "discarded"); }, diff --git a/web/src/stream_settings_ui.js b/web/src/stream_settings_ui.js index 5df73605d5..024abfda0a 100644 --- a/web/src/stream_settings_ui.js +++ b/web/src/stream_settings_ui.js @@ -250,6 +250,14 @@ export function update_can_remove_subscribers_group_id(sub, new_value) { stream_edit_subscribers.rerender_subscribers_list(sub); } +export function update_is_default_stream() { + const active_stream_id = get_active_data().id; + if (active_stream_id) { + const sub = sub_store.get(active_stream_id); + stream_ui_updates.update_setting_element(sub, "is_default_stream"); + } +} + export function set_color(stream_id, color) { const sub = sub_store.get(stream_id); stream_edit.set_stream_property(sub, "color", color); @@ -1156,7 +1164,7 @@ export function update_public_stream_privacy_option_state($container) { $public_stream_elem.prop("disabled", !settings_data.user_can_create_public_streams()); } -export function update_private_stream_privacy_option_state($container) { +export function update_private_stream_privacy_option_state($container, is_default_stream = false) { // Disable both "Private, shared history" and "Private, protected history" options. const $private_stream_elem = $container.find( `input[value='${CSS.escape(stream_data.stream_privacy_policy_values.private.code)}']`, @@ -1167,11 +1175,18 @@ export function update_private_stream_privacy_option_state($container) { )}']`, ); - $private_stream_elem.prop("disabled", !settings_data.user_can_create_private_streams()); - $private_with_public_history_elem.prop( - "disabled", - !settings_data.user_can_create_private_streams(), - ); + const disable_private_stream_options = + is_default_stream || !settings_data.user_can_create_private_streams(); + + $private_stream_elem.prop("disabled", disable_private_stream_options); + $private_with_public_history_elem.prop("disabled", disable_private_stream_options); + + $private_stream_elem + .closest("div") + .toggleClass("default_stream_private_tooltip", is_default_stream); + $private_with_public_history_elem + .closest("div") + .toggleClass("default_stream_private_tooltip", is_default_stream); } export function hide_or_disable_stream_privacy_options_if_required($container) { diff --git a/web/src/stream_ui_updates.js b/web/src/stream_ui_updates.js index e298425c3d..f09c71ed85 100644 --- a/web/src/stream_ui_updates.js +++ b/web/src/stream_ui_updates.js @@ -113,6 +113,24 @@ export function update_regular_sub_settings(sub) { } } +export function update_default_stream_and_stream_privacy_state($container) { + const $default_stream = $container.find(".default-stream"); + const privacy_type = $container.find("input[type=radio][name=privacy]:checked").val(); + const is_invite_only = + privacy_type === "invite-only" || privacy_type === "invite-only-public-history"; + + // If a private stream option is selected, the default stream option is disabled. + $default_stream.find("input").prop("disabled", is_invite_only); + $default_stream.toggleClass( + "control-label-disabled default_stream_private_tooltip", + is_invite_only, + ); + + // If the default stream option is checked, the private stream options are disabled. + const is_default_stream = $default_stream.find("input").prop("checked"); + stream_settings_ui.update_private_stream_privacy_option_state($container, is_default_stream); +} + export function enable_or_disable_permission_settings_in_edit_panel(sub) { if (!hash_util.is_editing_stream(sub.stream_id)) { return; @@ -129,6 +147,8 @@ export function enable_or_disable_permission_settings_in_edit_panel(sub) { return; } + update_default_stream_and_stream_privacy_state($stream_settings); + const disable_message_retention_setting = !page_params.zulip_plan_is_not_limited || !page_params.is_owner; $stream_settings diff --git a/web/src/tippyjs.js b/web/src/tippyjs.js index 4c994c40ca..10cb5ef387 100644 --- a/web/src/tippyjs.js +++ b/web/src/tippyjs.js @@ -292,6 +292,28 @@ export function initialize() { }, }); + delegate("body", { + target: [".settings-radio-input-parent.default_stream_private_tooltip"], + content: $t({ + defaultMessage: "Default streams for new users cannot be made private.", + }), + appendTo: () => document.body, + onHidden(instance) { + instance.destroy(); + }, + }); + + delegate("body", { + target: [".default-stream.default_stream_private_tooltip"], + content: $t({ + defaultMessage: "Private streams cannot be default streams for new users.", + }), + appendTo: () => document.body, + onHidden(instance) { + instance.destroy(); + }, + }); + delegate("body", { target: ["#generate_multiuse_invite_radio_container.disabled_setting_tooltip"], content: $t({ diff --git a/web/styles/subscriptions.css b/web/styles/subscriptions.css index 474bdd9d07..da8eff84a2 100644 --- a/web/styles/subscriptions.css +++ b/web/styles/subscriptions.css @@ -1006,6 +1006,14 @@ div.settings-radio-input-parent { cursor: not-allowed; } } + + &.default_stream_private_tooltip { + cursor: not-allowed; + + & label { + pointer-events: none; + } + } } .stream-permissions, @@ -1039,6 +1047,15 @@ div.settings-radio-input-parent { max-width: 100%; height: 30px; } + + .default-stream { + margin: 25px 0; + width: fit-content; + + .inline { + display: inline; + } + } } #change_user_group_description, diff --git a/web/templates/stream_settings/stream_settings.hbs b/web/templates/stream_settings/stream_settings.hbs index 84c9f557b6..cdab10c1a3 100644 --- a/web/templates/stream_settings/stream_settings.hbs +++ b/web/templates/stream_settings/stream_settings.hbs @@ -56,6 +56,7 @@ stream_post_policy_values=../stream_post_policy_values stream_privacy_policy_values=../stream_privacy_policy_values stream_privacy_policy=../stream_privacy_policy + check_default_stream=../check_default_stream zulip_plan_is_not_limited=../zulip_plan_is_not_limited upgrade_text_for_wide_organization_logo=../upgrade_text_for_wide_organization_logo is_business_type_org=../is_business_type_org diff --git a/web/templates/stream_settings/stream_types.hbs b/web/templates/stream_settings/stream_types.hbs index bb3bfbac7d..99419f3259 100644 --- a/web/templates/stream_settings/stream_types.hbs +++ b/web/templates/stream_settings/stream_types.hbs @@ -16,6 +16,18 @@ +{{#if is_stream_edit}} +