stream_settings: Display archived channels.

By default, archived channels will be hidden.
This commit is contained in:
sanchi-t
2025-02-01 11:17:23 +05:30
committed by Tim Abbott
parent f9babb37a2
commit 1aef79078c
9 changed files with 148 additions and 20 deletions

View File

@@ -256,18 +256,13 @@ export function validate_channels_settings_hash(hash: string): string {
const stream_id = Number.parseInt(section, 10); const stream_id = Number.parseInt(section, 10);
const sub = sub_store.get(stream_id); const sub = sub_store.get(stream_id);
// There are a few situations where we can't display stream settings: // There are a few situations where we can't display stream settings:
// 1. This is a stream that's been archived. (sub.is_archived=true) // 1. The stream ID is invalid. (sub=undefined)
// 2. The stream ID is invalid. (sub=undefined) // 2. The current user is a guest, and was unsubscribed from the stream
// 3. The current user is a guest, and was unsubscribed from the stream
// stream in the current session. (In future sessions, the stream will // stream in the current session. (In future sessions, the stream will
// not be in sub_store). // not be in sub_store).
// //
// In all these cases we redirect the user to 'subscribed' tab. // In both cases we redirect the user to 'subscribed' tab.
if ( if (sub === undefined || (page_params.is_guest && !stream_data.is_subscribed(stream_id))) {
sub === undefined ||
sub.is_archived ||
(page_params.is_guest && !stream_data.is_subscribed(stream_id))
) {
return channels_settings_section_url(); return channels_settings_section_url();
} }

View File

@@ -355,6 +355,10 @@ export function subscribed_stream_ids(): number[] {
return subscribed_subs().map((sub) => sub.stream_id); return subscribed_subs().map((sub) => sub.stream_id);
} }
export function get_archived_subs(): StreamSubscription[] {
return [...stream_info.values()].filter((sub) => sub.is_archived);
}
export function muted_stream_ids(): number[] { export function muted_stream_ids(): number[] {
return subscribed_subs() return subscribed_subs()
.filter((sub) => sub.is_muted) .filter((sub) => sub.is_muted)

View File

@@ -28,6 +28,12 @@ export type SettingsSubscription = StreamSubscription & {
subscriber_count: number; subscriber_count: number;
}; };
export const FILTERS = {
ALL_CHANNELS: "all_channels",
NON_ARCHIVED_CHANNELS: "non_archived_channels",
ARCHIVED_CHANNELS: "archived_channels",
};
export function get_sub_for_settings(sub: StreamSubscription): SettingsSubscription { export function get_sub_for_settings(sub: StreamSubscription): SettingsSubscription {
return { return {
...sub, ...sub,
@@ -66,7 +72,7 @@ function get_subs_for_settings(subs: StreamSubscription[]): SettingsSubscription
// delegating, so that we can more efficiently compute subscriber counts // delegating, so that we can more efficiently compute subscriber counts
// (in bulk). If that plan appears to have been aborted, feel free to // (in bulk). If that plan appears to have been aborted, feel free to
// inline this. // inline this.
return subs.filter((sub) => !sub.is_archived).map((sub) => get_sub_for_settings(sub)); return subs.map((sub) => get_sub_for_settings(sub));
} }
export function get_updated_unsorted_subs(): SettingsSubscription[] { export function get_updated_unsorted_subs(): SettingsSubscription[] {

View File

@@ -1,6 +1,7 @@
import $ from "jquery"; import $ from "jquery";
import _ from "lodash"; import _ from "lodash";
import assert from "minimalistic-assert"; import assert from "minimalistic-assert";
import type * as tippy from "tippy.js";
import render_stream_creation_confirmation_banner from "../templates/modal_banner/stream_creation_confirmation_banner.hbs"; import render_stream_creation_confirmation_banner from "../templates/modal_banner/stream_creation_confirmation_banner.hbs";
import render_stream_info_banner from "../templates/modal_banner/stream_info_banner.hbs"; import render_stream_info_banner from "../templates/modal_banner/stream_info_banner.hbs";
@@ -15,6 +16,7 @@ import type {Toggle} from "./components.ts";
import * as compose_banner from "./compose_banner.ts"; import * as compose_banner from "./compose_banner.ts";
import * as compose_recipient from "./compose_recipient.ts"; import * as compose_recipient from "./compose_recipient.ts";
import * as compose_state from "./compose_state.ts"; import * as compose_state from "./compose_state.ts";
import * as dropdown_widget from "./dropdown_widget.ts";
import * as hash_parser from "./hash_parser.ts"; import * as hash_parser from "./hash_parser.ts";
import * as hash_util from "./hash_util.ts"; import * as hash_util from "./hash_util.ts";
import {$t} from "./i18n.ts"; import {$t} from "./i18n.ts";
@@ -46,6 +48,10 @@ import * as stream_ui_updates from "./stream_ui_updates.ts";
import * as sub_store from "./sub_store.ts"; import * as sub_store from "./sub_store.ts";
import type {StreamSubscription} from "./sub_store.ts"; import type {StreamSubscription} from "./sub_store.ts";
import * as util from "./util.ts"; import * as util from "./util.ts";
import * as views_util from "./views_util.ts";
let archived_status_dropdown_filter: string;
let filters_dropdown_widget: dropdown_widget.DropdownWidget;
export function is_sub_already_present(sub: StreamSubscription): boolean { export function is_sub_already_present(sub: StreamSubscription): boolean {
return stream_ui_updates.row_for_stream_id(sub.stream_id).length > 0; return stream_ui_updates.row_for_stream_id(sub.stream_id).length > 0;
@@ -384,6 +390,20 @@ export function update_settings_for_unsubscribed(slim_sub: StreamSubscription):
} }
function triage_stream(left_panel_params: LeftPanelParams, sub: StreamSubscription): string { function triage_stream(left_panel_params: LeftPanelParams, sub: StreamSubscription): string {
const current_channel_visibility_filter = archived_status_dropdown_filter;
const channel_visibility_filters = stream_settings_data.FILTERS;
if (
current_channel_visibility_filter === channel_visibility_filters.NON_ARCHIVED_CHANNELS &&
sub.is_archived
) {
return "rejected";
}
if (
current_channel_visibility_filter === channel_visibility_filters.ARCHIVED_CHANNELS &&
!sub.is_archived
) {
return "rejected";
}
if (left_panel_params.show_subscribed && !sub.subscribed) { if (left_panel_params.show_subscribed && !sub.subscribed) {
// reject non-subscribed streams // reject non-subscribed streams
return "rejected"; return "rejected";
@@ -485,12 +505,15 @@ export function update_empty_left_panel_message(): void {
has_streams = stream_data.get_unsorted_subs().length; has_streams = stream_data.get_unsorted_subs().length;
} }
const has_hidden_streams = const all_channels_hidden =
$("#channels_overlay_container .stream-row:not(.notdisplayed)").length === 0; $("#channels_overlay_container .stream-row:not(.notdisplayed)").length === 0;
const has_search_query = const has_search_query =
$<HTMLInputElement>("#stream_filter input[type='text']").val()!.trim() !== ""; $<HTMLInputElement>("#stream_filter input[type='text']").val()!.trim() !== "";
// Show "no channels match" text if all channels are hidden and there's a search query. const has_filter =
if (has_hidden_streams && has_search_query) { archived_status_dropdown_filter !== stream_settings_data.FILTERS.ALL_CHANNELS;
// Both search queries and filters can lead to all channels being hidden.
if (all_channels_hidden && (has_search_query || (has_filter && has_streams))) {
$(".no-streams-to-show").children().hide(); $(".no-streams-to-show").children().hide();
$(".no_stream_match_filter_empty_text").show(); $(".no_stream_match_filter_empty_text").show();
$(".no-streams-to-show").show(); $(".no-streams-to-show").show();
@@ -639,6 +662,66 @@ export function switch_stream_sort(tab_name: string): void {
redraw_left_panel(); redraw_left_panel();
} }
function filters_dropdown_options(current_value: string | number | undefined): {
unique_id: string;
name: string;
bold_current_selection: boolean;
}[] {
return [
{
unique_id: stream_settings_data.FILTERS.ARCHIVED_CHANNELS,
name: $t({defaultMessage: "Archived channels"}),
bold_current_selection:
current_value === stream_settings_data.FILTERS.ARCHIVED_CHANNELS,
},
{
unique_id: stream_settings_data.FILTERS.NON_ARCHIVED_CHANNELS,
name: $t({defaultMessage: "Non-archived channels"}),
bold_current_selection:
current_value === stream_settings_data.FILTERS.NON_ARCHIVED_CHANNELS,
},
{
unique_id: stream_settings_data.FILTERS.ALL_CHANNELS,
name: $t({defaultMessage: "Archived and non-archived"}),
bold_current_selection: current_value === stream_settings_data.FILTERS.ALL_CHANNELS,
},
];
}
export function set_filters_for_tests(filter_widget: dropdown_widget.DropdownWidget): void {
filters_dropdown_widget = filter_widget;
}
function filter_click_handler(
event: JQuery.TriggeredEvent,
dropdown: tippy.Instance,
widget: dropdown_widget.DropdownWidget,
): void {
event.preventDefault();
event.stopPropagation();
const filter_id = $(event.currentTarget).attr("data-unique-id");
assert(filter_id !== undefined);
// We don't support multiple filters, so we clear existing and add the new filter.
archived_status_dropdown_filter = filter_id;
redraw_left_panel();
dropdown.hide();
widget.render();
}
function set_up_dropdown_widget(): void {
archived_status_dropdown_filter = stream_settings_data.FILTERS.NON_ARCHIVED_CHANNELS;
filters_dropdown_widget = new dropdown_widget.DropdownWidget({
...views_util.COMMON_DROPDOWN_WIDGET_PARAMS,
get_options: filters_dropdown_options,
widget_name: "stream_settings_filter",
item_click_callback: filter_click_handler,
$events_container: $("#stream_filter"),
default_id: archived_status_dropdown_filter,
});
filters_dropdown_widget.setup();
}
function setup_page(callback: () => void): void { function setup_page(callback: () => void): void {
// We should strongly consider only setting up the page once, // We should strongly consider only setting up the page once,
// but I am writing these comments write before a big release, // but I am writing these comments write before a big release,
@@ -723,6 +806,7 @@ function setup_page(callback: () => void): void {
const new_stream_announcements_stream_sub = stream_data.get_sub_by_name( const new_stream_announcements_stream_sub = stream_data.get_sub_by_name(
new_stream_announcements_stream, new_stream_announcements_stream,
); );
const realm_has_archived_channels = stream_data.get_archived_subs().length > 0;
const template_data = { const template_data = {
new_stream_announcements_stream_sub, new_stream_announcements_stream_sub,
@@ -747,6 +831,7 @@ function setup_page(callback: () => void): void {
disable_message_retention_setting: disable_message_retention_setting:
!realm.zulip_plan_is_not_limited || !current_user.is_owner, !realm.zulip_plan_is_not_limited || !current_user.is_owner,
group_setting_labels: settings_config.all_group_setting_labels.stream, group_setting_labels: settings_config.all_group_setting_labels.stream,
realm_has_archived_channels,
}; };
const rendered = render_stream_settings_overlay(template_data); const rendered = render_stream_settings_overlay(template_data);
@@ -756,6 +841,7 @@ function setup_page(callback: () => void): void {
initialize_components(); initialize_components();
redraw_left_panel(); redraw_left_panel();
stream_create.set_up_handlers(); stream_create.set_up_handlers();
set_up_dropdown_widget();
const throttled_redraw_left_panel = _.throttle(redraw_left_panel, 50); const throttled_redraw_left_panel = _.throttle(redraw_left_panel, 50);
$("#stream_filter input[type='text']").on("input", () => { $("#stream_filter input[type='text']").on("input", () => {
@@ -876,6 +962,23 @@ export function change_state(
toggler.goto(left_side_tab); toggler.goto(left_side_tab);
} }
switch_to_stream_row(stream_id); switch_to_stream_row(stream_id);
const sub = stream_data.get_sub_by_id(stream_id);
if (sub) {
const FILTERS = stream_settings_data.FILTERS;
const should_update_filter =
(archived_status_dropdown_filter === FILTERS.NON_ARCHIVED_CHANNELS &&
sub.is_archived) ||
(archived_status_dropdown_filter === FILTERS.ARCHIVED_CHANNELS && !sub.is_archived);
if (should_update_filter) {
if (sub.is_archived) {
archived_status_dropdown_filter = FILTERS.ARCHIVED_CHANNELS;
} else {
archived_status_dropdown_filter = FILTERS.NON_ARCHIVED_CHANNELS;
}
filters_dropdown_widget.render(archived_status_dropdown_filter);
}
}
return; return;
} }

View File

@@ -642,11 +642,21 @@ h4.user_group_setting_subsection_title {
padding: 8px 10px; padding: 8px 10px;
display: grid; display: grid;
grid-template: grid-template:
"search-input clear-search more-options-button" auto / minmax(0, 1fr) "search-input clear-search dropdown-widget" auto / minmax(0, 1fr)
30px; 30px;
border-bottom: 1px solid var(--color-border-modal-bar); border-bottom: 1px solid var(--color-border-modal-bar);
} }
#stream_settings_filter_widget {
margin-left: 10px;
gap: 3px;
width: auto;
}
.stream_settings_filter_container.hide_filter {
display: none;
}
#user_group_visibility_settings_widget { #user_group_visibility_settings_widget {
grid-area: more-options-button; grid-area: more-options-button;
margin-left: 10px; margin-left: 10px;

View File

@@ -1,5 +1,4 @@
{{#if stream_settings_link}} {{#if stream_settings_link}}
{{#unless stream.is_archived}}
<a class="message-header-stream-settings-button tippy-zulip-tooltip" data-tooltip-template-id="stream-details-tooltip-template" data-tippy-placement="bottom" href="{{stream_settings_link}}"> <a class="message-header-stream-settings-button tippy-zulip-tooltip" data-tooltip-template-id="stream-details-tooltip-template" data-tippy-placement="bottom" href="{{stream_settings_link}}">
{{> navbar_icon_and_title . }} {{> navbar_icon_and_title . }}
</a> </a>
@@ -13,11 +12,6 @@
{{/unless}} {{/unless}}
</div> </div>
</template> </template>
{{else}}
<span class="navbar_title">
{{> navbar_icon_and_title . }}
</span>
{{/unless}}
<span class="narrow_description rendered_markdown single-line-rendered-markdown"> <span class="narrow_description rendered_markdown single-line-rendered-markdown">
{{#if rendered_narrow_description}} {{#if rendered_narrow_description}}
{{rendered_markdown rendered_narrow_description}} {{rendered_markdown rendered_narrow_description}}

View File

@@ -25,6 +25,9 @@
<button type="button" class="clear_search_button" id="clear_search_stream_name"> <button type="button" class="clear_search_button" id="clear_search_stream_name">
<i class="zulip-icon zulip-icon-close" aria-hidden="true"></i> <i class="zulip-icon zulip-icon-close" aria-hidden="true"></i>
</button> </button>
<div class="stream_settings_filter_container {{#unless realm_has_archived_channels}}hide_filter{{/unless}}">
{{> ../dropdown_widget widget_name="stream_settings_filter"}}
</div>
</div> </div>
<div class="no-streams-to-show"> <div class="no-streams-to-show">
<div class="subscribed_streams_tab_empty_text"> <div class="subscribed_streams_tab_empty_text">

View File

@@ -716,6 +716,7 @@ test("delete_sub", () => {
stream_data.add_sub(canada); stream_data.add_sub(canada);
const num_subscribed_subs = stream_data.num_subscribed_subs(); const num_subscribed_subs = stream_data.num_subscribed_subs();
const archived_subs = stream_data.get_archived_subs();
assert.ok(stream_data.is_subscribed(canada.stream_id)); assert.ok(stream_data.is_subscribed(canada.stream_id));
assert.equal(stream_data.get_sub("Canada").stream_id, canada.stream_id); assert.equal(stream_data.get_sub("Canada").stream_id, canada.stream_id);
@@ -728,6 +729,7 @@ test("delete_sub", () => {
assert.ok(stream_data.get_sub("Canada")); assert.ok(stream_data.get_sub("Canada"));
assert.ok(sub_store.get(canada.stream_id)); assert.ok(sub_store.get(canada.stream_id));
assert.equal(stream_data.num_subscribed_subs(), num_subscribed_subs); assert.equal(stream_data.num_subscribed_subs(), num_subscribed_subs);
assert.equal(stream_data.get_archived_subs().length, archived_subs.length + 1);
blueslip.expect("warn", "Failed to archive stream 99999"); blueslip.expect("warn", "Failed to archive stream 99999");
stream_data.delete_sub(99999); stream_data.delete_sub(99999);

View File

@@ -209,6 +209,11 @@ run_test("redraw_left_panel", ({override, mock_template}) => {
populated_subs = data.subscriptions; populated_subs = data.subscriptions;
}); });
const filters_dropdown_widget = {
render: function render() {},
};
stream_settings_ui.set_filters_for_tests(filters_dropdown_widget);
stream_settings_ui.render_left_panel_superset(); stream_settings_ui.render_left_panel_superset();
const sub_stubs = []; const sub_stubs = [];
@@ -223,6 +228,11 @@ run_test("redraw_left_panel", ({override, mock_template}) => {
$.create("#channels_overlay_container .stream-row", {children: sub_stubs}); $.create("#channels_overlay_container .stream-row", {children: sub_stubs});
const $no_streams_message = $(".no-streams-to-show");
const $child_element = $(".subscribed_streams_tab_empty_text");
$no_streams_message.children = () => $child_element;
$child_element.hide = () => [];
let ui_called = false; let ui_called = false;
scroll_util.reset_scrollbar = ($elem) => { scroll_util.reset_scrollbar = ($elem) => {
ui_called = true; ui_called = true;
@@ -360,6 +370,7 @@ run_test("redraw_left_panel", ({override, mock_template}) => {
test_filter({input: "d", show_subscribed: true}, [poland]); test_filter({input: "d", show_subscribed: true}, [poland]);
assert.ok($(".stream-row-denmark").hasClass("active")); assert.ok($(".stream-row-denmark").hasClass("active"));
$(".stream-row.active").attr("data-stream-id", 101);
stream_settings_ui.switch_stream_tab("subscribed"); stream_settings_ui.switch_stream_tab("subscribed");
assert.ok(!$(".stream-row-denmark").hasClass("active")); assert.ok(!$(".stream-row-denmark").hasClass("active"));
assert.ok(!$(".right .settings").visible()); assert.ok(!$(".right .settings").visible());