stream-settings: Replace "Not subscribed" tab with "Available".

This commit replaces "Not subscribed" tab in stream settings
with "Available" tab where only streams which the user can
subscribe to are shown.

Fixes #35919.
This commit is contained in:
Sahil Batra
2025-09-03 12:00:17 +05:30
committed by Tim Abbott
parent 24f678feb6
commit 683eca97a7
13 changed files with 63 additions and 69 deletions

View File

@@ -280,9 +280,9 @@ const relative_link_mapping: Record<
label: "All", label: "All",
relative_link: "/#channels/all", relative_link: "/#channels/all",
}, },
"not-subscribed": { available: {
label: "Not subscribed", label: "Available",
relative_link: "/#channels/notsubscribed", relative_link: "/#channels/available",
}, },
}, },
template: ` template: `

View File

@@ -58,7 +58,7 @@ quick overview of recent messages in a channel.
<Tabs> <Tabs>
<TabItem label="Desktop/Web"> <TabItem label="Desktop/Web">
<FlattenedSteps> <FlattenedSteps>
<NavigationSteps target="relative/channel/not-subscribed" /> <NavigationSteps target="relative/channel/available" />
1. Select a channel. 1. Select a channel.
1. Click the channel name in the top bar. 1. Click the channel name in the top bar.

View File

@@ -27,7 +27,7 @@ subscribe to [private](/help/channel-permissions#private-channels) channels.
<Tabs> <Tabs>
<TabItem label="Desktop/Web"> <TabItem label="Desktop/Web">
<FlattenedSteps> <FlattenedSteps>
<NavigationSteps target="relative/channel/not-subscribed" /> <NavigationSteps target="relative/channel/available" />
1. Scroll through the list of channels. You can use the **search box** near the 1. Scroll through the list of channels. You can use the **search box** near the
top of the menu to filter the list by channel name or description. top of the menu to filter the list by channel name or description.

View File

@@ -560,7 +560,7 @@ export async function open_streams_modal(page: Page): Promise<void> {
await page.waitForSelector("#subscription_overlay", {visible: true}); await page.waitForSelector("#subscription_overlay", {visible: true});
const url = await page_url_with_fragment(page); const url = await page_url_with_fragment(page);
assert.ok(url.includes("#channels/notsubscribed")); assert.ok(url.includes("#channels/available"));
} }
export async function open_personal_menu(page: Page): Promise<void> { export async function open_personal_menu(page: Page): Promise<void> {

View File

@@ -253,7 +253,7 @@ export function channels_settings_edit_url(
} }
export function channels_settings_section_url(section = "subscribed"): string { export function channels_settings_section_url(section = "subscribed"): string {
const valid_section_values = new Set(["new", "subscribed", "all", "notsubscribed"]); const valid_section_values = new Set(["new", "subscribed", "all", "available"]);
if (!valid_section_values.has(section)) { if (!valid_section_values.has(section)) {
blueslip.warn("invalid section for channels settings: " + section); blueslip.warn("invalid section for channels settings: " + section);
return "#channels/subscribed"; return "#channels/subscribed";

View File

@@ -89,8 +89,8 @@ export function setup_subscriptions_tab_hash(tab_key_value: string): void {
browser_history.update("#channels/subscribed"); browser_history.update("#channels/subscribed");
break; break;
} }
case "not-subscribed": { case "available": {
browser_history.update("#channels/notsubscribed"); browser_history.update("#channels/available");
break; break;
} }
default: { default: {

View File

@@ -539,8 +539,12 @@ function triage_stream(left_panel_params: LeftPanelParams, sub: StreamSubscripti
return "rejected"; return "rejected";
} }
if (left_panel_params.show_not_subscribed && sub.subscribed) { if (
// reject subscribed streams left_panel_params.show_available &&
(sub.subscribed || !stream_data.can_toggle_subscription(sub))
) {
// reject subscribed streams and unsubscribed streams
// that user does not have permission to subscribe to.
return "rejected"; return "rejected";
} }
@@ -627,7 +631,7 @@ export function update_empty_left_panel_message(): void {
has_streams = has_streams =
stream_data.subscribed_subs().length > 0 || stream_data.subscribed_subs().length > 0 ||
$("#channels_overlay_container .stream-row:not(.notdisplayed)").length > 0; $("#channels_overlay_container .stream-row:not(.notdisplayed)").length > 0;
} else if (stream_ui_updates.is_not_subscribed_stream_tab_active()) { } else if (stream_ui_updates.is_available_stream_tab_active()) {
has_streams = has_streams =
stream_data.unsubscribed_subs().length > 0 || stream_data.unsubscribed_subs().length > 0 ||
$("#channels_overlay_container .stream-row:not(.notdisplayed)").length > 0; $("#channels_overlay_container .stream-row:not(.notdisplayed)").length > 0;
@@ -657,8 +661,8 @@ export function update_empty_left_panel_message(): void {
$(".no-streams-to-show").children().hide(); $(".no-streams-to-show").children().hide();
if (stream_ui_updates.is_subscribed_stream_tab_active()) { if (stream_ui_updates.is_subscribed_stream_tab_active()) {
$(".subscribed_streams_tab_empty_text").show(); $(".subscribed_streams_tab_empty_text").show();
} else if (stream_ui_updates.is_not_subscribed_stream_tab_active()) { } else if (stream_ui_updates.is_available_stream_tab_active()) {
$(".not_subscribed_streams_tab_empty_text").show(); $(".available_streams_tab_empty_text").show();
} else { } else {
$(".all_streams_tab_empty_text").show(); $(".all_streams_tab_empty_text").show();
} }
@@ -729,7 +733,7 @@ let sort_order = "by-stream-name";
type LeftPanelParams = { type LeftPanelParams = {
input: string; input: string;
show_subscribed: boolean; show_subscribed: boolean;
show_not_subscribed: boolean; show_available: boolean;
sort_order: string; sort_order: string;
}; };
@@ -739,7 +743,7 @@ export function get_left_panel_params(): LeftPanelParams {
return { return {
input, input,
show_subscribed: stream_ui_updates.show_subscribed, show_subscribed: stream_ui_updates.show_subscribed,
show_not_subscribed: stream_ui_updates.show_not_subscribed, show_available: stream_ui_updates.show_available,
sort_order, sort_order,
}; };
} }
@@ -757,17 +761,17 @@ export function switch_stream_tab(tab_name: string): void {
switch (tab_name) { switch (tab_name) {
case "all-streams": { case "all-streams": {
stream_ui_updates.set_show_subscribed(false); stream_ui_updates.set_show_subscribed(false);
stream_ui_updates.set_show_not_subscribed(false); stream_ui_updates.set_show_available(false);
break; break;
} }
case "subscribed": { case "subscribed": {
stream_ui_updates.set_show_subscribed(true); stream_ui_updates.set_show_subscribed(true);
stream_ui_updates.set_show_not_subscribed(false); stream_ui_updates.set_show_available(false);
break; break;
} }
case "not-subscribed": { case "available": {
stream_ui_updates.set_show_subscribed(false); stream_ui_updates.set_show_subscribed(false);
stream_ui_updates.set_show_not_subscribed(true); stream_ui_updates.set_show_available(true);
break; break;
} }
// No default // No default
@@ -894,12 +898,12 @@ function setup_page(callback: () => void): void {
// Reset our internal state to reflect that we're initially in // Reset our internal state to reflect that we're initially in
// the "Subscribed" tab if we're reopening "Stream settings". // the "Subscribed" tab if we're reopening "Stream settings".
stream_ui_updates.set_show_subscribed(true); stream_ui_updates.set_show_subscribed(true);
stream_ui_updates.set_show_not_subscribed(false); stream_ui_updates.set_show_available(false);
toggler = components.toggle({ toggler = components.toggle({
child_wants_focus: true, child_wants_focus: true,
values: [ values: [
{label: $t({defaultMessage: "Subscribed"}), key: "subscribed"}, {label: $t({defaultMessage: "Subscribed"}), key: "subscribed"},
{label: $t({defaultMessage: "Not subscribed"}), key: "not-subscribed"}, {label: $t({defaultMessage: "Available"}), key: "available"},
{label: $t({defaultMessage: "All"}), key: "all-streams"}, {label: $t({defaultMessage: "All"}), key: "all-streams"},
], ],
callback(_value, key) { callback(_value, key) {
@@ -913,7 +917,7 @@ function setup_page(callback: () => void): void {
} }
if (current_user.is_guest) { if (current_user.is_guest) {
toggler.disable_tab("all-streams"); toggler.disable_tab("all-streams");
toggler.disable_tab("not-subscribed"); toggler.disable_tab("available");
} }
// show the "Stream settings" header by default. // show the "Stream settings" header by default.
@@ -1061,8 +1065,8 @@ export function change_state(
return; return;
} }
if (section === "notsubscribed") { if (section === "available") {
toggler.goto("not-subscribed"); toggler.goto("available");
stream_edit.empty_right_panel(); stream_edit.empty_right_panel();
return; return;
} }
@@ -1193,20 +1197,20 @@ export function toggle_view(event: string): void {
case "right_arrow": case "right_arrow":
switch (stream_filter_tab_key) { switch (stream_filter_tab_key) {
case "subscribed": case "subscribed":
toggler.goto("not-subscribed"); toggler.goto("available");
break; break;
case "not-subscribed": case "available":
toggler.goto("all-streams"); toggler.goto("all-streams");
break; break;
} }
break; break;
case "left_arrow": case "left_arrow":
switch (stream_filter_tab_key) { switch (stream_filter_tab_key) {
case "not-subscribed": case "available":
toggler.goto("subscribed"); toggler.goto("subscribed");
break; break;
case "all-streams": case "all-streams":
toggler.goto("not-subscribed"); toggler.goto("available");
break; break;
} }
break; break;

View File

@@ -39,7 +39,7 @@ function settings_button_for_sub(sub: StreamSubscription): JQuery {
} }
export let show_subscribed = true; export let show_subscribed = true;
export let show_not_subscribed = false; export let show_available = false;
export function is_subscribed_stream_tab_active(): boolean { export function is_subscribed_stream_tab_active(): boolean {
// Returns true if "Subscribed" tab in stream settings is open // Returns true if "Subscribed" tab in stream settings is open
@@ -47,18 +47,18 @@ export function is_subscribed_stream_tab_active(): boolean {
return show_subscribed; return show_subscribed;
} }
export function is_not_subscribed_stream_tab_active(): boolean { export function is_available_stream_tab_active(): boolean {
// Returns true if "not-subscribed" tab in stream settings is open // Returns true if "available" tab in stream settings is open
// otherwise false. // otherwise false.
return show_not_subscribed; return show_available;
} }
export function set_show_subscribed(value: boolean): void { export function set_show_subscribed(value: boolean): void {
show_subscribed = value; show_subscribed = value;
} }
export function set_show_not_subscribed(value: boolean): void { export function set_show_available(value: boolean): void {
show_not_subscribed = value; show_available = value;
} }
export function update_web_public_stream_privacy_option_state($container: JQuery): void { export function update_web_public_stream_privacy_option_state($container: JQuery): void {
@@ -483,15 +483,17 @@ export function update_stream_row_in_settings_tab(sub: StreamSubscription): void
// This function display/hide stream row in stream settings tab, // This function display/hide stream row in stream settings tab,
// used to display immediate effect of add/removal subscription event. // used to display immediate effect of add/removal subscription event.
// If user is subscribed or unsubscribed to stream, it will show sub or unsub // If user is subscribed or unsubscribed to stream, it will show sub or unsub
// row under "Subscribed" or "Not subscribed" (only if the stream is public) tab, otherwise // row under "Subscribed" or "Available" (only if the stream is public) tab, otherwise
// if stream is not public hide stream row under tab. // if stream is not public hide stream row under tab.
if (is_subscribed_stream_tab_active() || is_not_subscribed_stream_tab_active()) { if (is_subscribed_stream_tab_active() || is_available_stream_tab_active()) {
const $row = row_for_stream_id(sub.stream_id); const $row = row_for_stream_id(sub.stream_id);
if ( if (
(is_subscribed_stream_tab_active() && sub.subscribed) || (is_subscribed_stream_tab_active() && sub.subscribed) ||
(is_not_subscribed_stream_tab_active() && !sub.subscribed) (is_available_stream_tab_active() &&
!sub.subscribed &&
stream_data.can_toggle_subscription(sub))
) { ) {
if (stream_settings_components.filter_includes_channel(sub)) { if (stream_settings_components.filter_includes_channel(sub)) {
$row.removeClass("notdisplayed"); $row.removeClass("notdisplayed");

View File

@@ -443,7 +443,7 @@ export function initialize(): void {
tippy.delegate("body", { tippy.delegate("body", {
target: [ target: [
"[data-tab-key='not-subscribed'].disabled", "[data-tab-key='available'].disabled",
"[data-tab-key='all-streams'].disabled", "[data-tab-key='all-streams'].disabled",
].join(","), ].join(","),
content: $t({ content: $t({

View File

@@ -1,7 +1,7 @@
<div class="popover-menu" data-simplebar data-simplebar-tab-index="-1"> <div class="popover-menu" data-simplebar data-simplebar-tab-index="-1">
<ul role="menu" class="popover-menu-list"> <ul role="menu" class="popover-menu-list">
<li role="none" class="link-item popover-menu-list-item"> <li role="none" class="link-item popover-menu-list-item">
<a href="#channels/notsubscribed" role="menuitem" class="popover-menu-link navigate_and_close_popover" tabindex="0"> <a href="#channels/available" role="menuitem" class="popover-menu-link navigate_and_close_popover" tabindex="0">
<i class="popover-menu-icon zulip-icon zulip-icon-browse-channels" aria-hidden="true"></i> <i class="popover-menu-icon zulip-icon zulip-icon-browse-channels" aria-hidden="true"></i>
<span class="popover-menu-label">{{t "Browse channels" }}</span> <span class="popover-menu-label">{{t "Browse channels" }}</span>
</a> </a>

View File

@@ -48,7 +48,7 @@
{{/if}} {{/if}}
</span> </span>
</div> </div>
<div class="not_subscribed_streams_tab_empty_text"> <div class="available_streams_tab_empty_text">
<span class="settings-empty-option-text"> <span class="settings-empty-option-text">
{{t 'No channels to show.'}} {{t 'No channels to show.'}}
<a href="#channels/all">{{t 'View all channels'}}</a> <a href="#channels/all">{{t 'View all channels'}}</a>

View File

@@ -1,10 +1,10 @@
{{#if exactly_one_unsubscribed_stream}} {{#if exactly_one_unsubscribed_stream}}
<a class="subscribe-more-link" href="#channels/notsubscribed"> <a class="subscribe-more-link" href="#channels/available">
<i class="subscribe-more-icon zulip-icon zulip-icon-browse-channels" aria-hidden="true" ></i> <i class="subscribe-more-icon zulip-icon zulip-icon-browse-channels" aria-hidden="true" ></i>
<span class="subscribe-more-label">{{~t "BROWSE 1 MORE CHANNEL" ~}}</span> <span class="subscribe-more-label">{{~t "BROWSE 1 MORE CHANNEL" ~}}</span>
</a> </a>
{{else if can_subscribe_stream_count}} {{else if can_subscribe_stream_count}}
<a class="subscribe-more-link" href="#channels/notsubscribed"> <a class="subscribe-more-link" href="#channels/available">
<i class="subscribe-more-icon zulip-icon zulip-icon-browse-channels" aria-hidden="true" ></i> <i class="subscribe-more-icon zulip-icon zulip-icon-browse-channels" aria-hidden="true" ></i>
<span class="subscribe-more-label">{{~t "BROWSE {can_subscribe_stream_count} MORE CHANNELS" ~}}</span> <span class="subscribe-more-label">{{~t "BROWSE {can_subscribe_stream_count} MORE CHANNELS" ~}}</span>
</a> </a>

View File

@@ -263,58 +263,46 @@ run_test("redraw_left_panel", ({override, mock_template}) => {
} }
// Search with single keyword // Search with single keyword
test_filter({input: "Po", show_subscribed: false, show_not_subscribed: false}, [ test_filter({input: "Po", show_subscribed: false, show_available: false}, [poland, pomona]);
poland,
pomona,
]);
assert.ok(ui_called); assert.ok(ui_called);
// The denmark row is active, even though it's not displayed. // The denmark row is active, even though it's not displayed.
assert.ok($denmark_row.hasClass("active")); assert.ok($denmark_row.hasClass("active"));
// Search with multiple keywords // Search with multiple keywords
test_filter({input: "Denmark, Pol", show_subscribed: false, show_not_subscribed: false}, [ test_filter({input: "Denmark, Pol", show_subscribed: false, show_available: false}, [
denmark, denmark,
poland, poland,
]); ]);
test_filter({input: "Den, Pol", show_subscribed: false, show_not_subscribed: false}, [ test_filter({input: "Den, Pol", show_subscribed: false, show_available: false}, [
denmark, denmark,
poland, poland,
]); ]);
// Search is case-insensitive // Search is case-insensitive
test_filter({input: "po", show_subscribed: false, show_not_subscribed: false}, [ test_filter({input: "po", show_subscribed: false, show_available: false}, [poland, pomona]);
poland,
pomona,
]);
// Search handles unusual characters like C++ // Search handles unusual characters like C++
test_filter({input: "c++", show_subscribed: false, show_not_subscribed: false}, [cpp]); test_filter({input: "c++", show_subscribed: false, show_available: false}, [cpp]);
// Search subscribed streams only // Search subscribed streams only
test_filter({input: "d", show_subscribed: true, show_not_subscribed: false}, [poland]); test_filter({input: "d", show_subscribed: true, show_available: false}, [poland]);
// Search unsubscribed streams only // Search unsubscribed streams only
test_filter({input: "d", show_subscribed: false, show_not_subscribed: true}, [abcd, denmark]); test_filter({input: "d", show_subscribed: false, show_available: true}, [abcd, denmark]);
// Search terms match stream description // Search terms match stream description
test_filter({input: "Co", show_subscribed: false, show_not_subscribed: false}, [ test_filter({input: "Co", show_subscribed: false, show_available: false}, [denmark, pomona]);
denmark,
pomona,
]);
// Search names AND descriptions // Search names AND descriptions
test_filter({input: "Mon", show_subscribed: false, show_not_subscribed: false}, [ test_filter({input: "Mon", show_subscribed: false, show_available: false}, [pomona, poland]);
pomona,
poland,
]);
// Explicitly order streams by name // Explicitly order streams by name
test_filter( test_filter(
{ {
input: "", input: "",
show_subscribed: false, show_subscribed: false,
show_not_subscribed: false, show_available: false,
sort_order: "by-stream-name", sort_order: "by-stream-name",
}, },
[abcd, cpp, denmark, jerry, poland, pomona, utopia, zzyzx], [abcd, cpp, denmark, jerry, poland, pomona, utopia, zzyzx],
@@ -325,7 +313,7 @@ run_test("redraw_left_panel", ({override, mock_template}) => {
{ {
input: "", input: "",
show_subscribed: false, show_subscribed: false,
show_not_subscribed: false, show_available: false,
sort_order: "by-subscriber-count", sort_order: "by-subscriber-count",
}, },
[utopia, abcd, poland, cpp, zzyzx, denmark, jerry, pomona], [utopia, abcd, poland, cpp, zzyzx, denmark, jerry, pomona],
@@ -336,7 +324,7 @@ run_test("redraw_left_panel", ({override, mock_template}) => {
{ {
input: "", input: "",
show_subscribed: false, show_subscribed: false,
show_not_subscribed: false, show_available: false,
sort_order: "by-weekly-traffic", sort_order: "by-weekly-traffic",
}, },
[poland, utopia, cpp, zzyzx, jerry, abcd, pomona, denmark], [poland, utopia, cpp, zzyzx, jerry, abcd, pomona, denmark],
@@ -347,7 +335,7 @@ run_test("redraw_left_panel", ({override, mock_template}) => {
{ {
input: "", input: "",
show_subscribed: true, show_subscribed: true,
show_not_subscribed: false, show_available: false,
sort_order: "by-subscriber-count", sort_order: "by-subscriber-count",
}, },
[poland, cpp, zzyzx, pomona], [poland, cpp, zzyzx, pomona],
@@ -358,7 +346,7 @@ run_test("redraw_left_panel", ({override, mock_template}) => {
{ {
input: "", input: "",
show_subscribed: false, show_subscribed: false,
show_not_subscribed: true, show_available: true,
sort_order: "by-subscriber-count", sort_order: "by-subscriber-count",
}, },
[utopia, abcd, denmark, jerry], [utopia, abcd, denmark, jerry],