left_sidebar: Hide inactive channels in channel folders.

This commit is contained in:
Evy Kassirer
2025-07-01 12:05:00 -07:00
committed by Tim Abbott
parent e6fecfc8eb
commit e63ee026fe
6 changed files with 181 additions and 10 deletions

View File

@@ -88,8 +88,12 @@ function should_mask_header_unread_count(
}
type SectionUnreadCount = {
// These both include inactive unreads as well.
unmuted: number;
muted: number;
// These are used for the "+ n inactive channels" button.
inactive_unmuted: number;
inactive_muted: number;
};
export function update_dom_with_unread_counts(
@@ -110,15 +114,25 @@ export function update_dom_with_unread_counts(
const pinned_unread_counts: SectionUnreadCount = {
unmuted: 0,
muted: 0,
// Not used for the pinned section, but included here to make typing easier
inactive_unmuted: 0,
inactive_muted: 0,
};
const folder_unread_counts = new Map<number, SectionUnreadCount>();
// TODO: In an upcoming commit, the normal and inactive sections will be
// merged. For this commit, the normal section has no inactive channels
// and the inactive section has no active channels.
const normal_section_unread_counts: SectionUnreadCount = {
unmuted: 0,
muted: 0,
inactive_unmuted: 0,
inactive_muted: 0,
};
const inactive_section_unread_counts: SectionUnreadCount = {
unmuted: 0,
muted: 0,
inactive_unmuted: 0,
inactive_muted: 0,
};
for (const [stream_id, stream_count_info] of counts.stream_count.entries()) {
@@ -131,12 +145,18 @@ export function update_dom_with_unread_counts(
const unread_counts = folder_unread_counts.get(sub.folder_id) ?? {
unmuted: 0,
muted: 0,
inactive_unmuted: 0,
inactive_muted: 0,
};
if (!folder_unread_counts.has(sub.folder_id)) {
folder_unread_counts.set(sub.folder_id, unread_counts);
}
unread_counts.unmuted += stream_count_info.unmuted_count;
unread_counts.muted += stream_count_info.muted_count;
if (!stream_list_sort.has_recent_activity(sub)) {
unread_counts.inactive_unmuted += stream_count_info.unmuted_count;
unread_counts.inactive_muted += stream_count_info.muted_count;
}
} else if (stream_list_sort.has_recent_activity(sub)) {
normal_section_unread_counts.unmuted += stream_count_info.unmuted_count;
normal_section_unread_counts.muted += stream_count_info.muted_count;
@@ -186,6 +206,11 @@ export function update_dom_with_unread_counts(
unread_counts?.unmuted ?? 0,
unread_counts?.muted ?? 0,
);
update_section_unread_count(
$(`#stream-list-${folder_id}-container .show-inactive-channels`),
unread_counts?.inactive_unmuted ?? 0,
unread_counts?.inactive_muted ?? 0,
);
}
if (!skip_animations) {

View File

@@ -6,6 +6,7 @@ import * as tippy from "tippy.js";
import render_filter_topics from "../templates/filter_topics.hbs";
import render_go_to_channel_feed_tooltip from "../templates/go_to_channel_feed_tooltip.hbs";
import render_go_to_channel_list_of_topics_tooltip from "../templates/go_to_channel_list_of_topics_tooltip.hbs";
import render_show_inactive_channels from "../templates/show_inactive_channels.hbs";
import render_stream_list_section_container from "../templates/stream_list_section_container.hbs";
import render_stream_privacy from "../templates/stream_privacy.hbs";
import render_stream_sidebar_row from "../templates/stream_sidebar_row.hbs";
@@ -60,6 +61,7 @@ export function rewire_stream_cursor(value: typeof stream_cursor): void {
let has_scrolled = false;
const collapsed_sections = new Set<string>();
const sections_showing_inactive = new Set<string>();
export function is_zoomed_in(): boolean {
return zoomed_in;
@@ -305,11 +307,19 @@ export function build_stream_list(force_rerender: boolean): void {
return;
}
function add_sidebar_li(stream_id: number, $list: JQuery): void {
function add_sidebar_li(
stream_id: number,
$list: JQuery,
inactive_in_channel_folder = false,
): void {
const sidebar_row = stream_sidebar.get_row(stream_id);
assert(sidebar_row !== undefined);
sidebar_row.update_whether_active();
$list.append($(sidebar_row.get_li()));
const $li = sidebar_row.get_li();
if (inactive_in_channel_folder) {
$li.addClass("inactive-in-channel-folder");
}
$list.append($li);
}
clear_topics();
@@ -322,17 +332,34 @@ export function build_stream_list(force_rerender: boolean): void {
$("#stream_filters").append(
$(stream_list_section_container_html(section, can_create_streams)),
);
const is_empty = section.streams.length === 0 && section.muted_streams.length === 0;
const is_empty =
section.streams.length === 0 &&
section.muted_streams.length === 0 &&
section.inactive_streams.length === 0;
$(`#stream-list-${section.id}-container`).toggleClass("no-display", is_empty);
for (const stream_id of [...section.streams, ...section.muted_streams]) {
add_sidebar_li(stream_id, $(`#stream-list-${section.id}`));
}
// This should only be relevant for folders
for (const stream_id of section.inactive_streams) {
add_sidebar_li(stream_id, $(`#stream-list-${section.id}`), true);
}
if (section.inactive_streams.length > 0) {
$(`#stream-list-${section.id}`).append(
$(
render_show_inactive_channels({
inactive_count: section.inactive_streams.length,
}),
),
);
}
}
// Rerendering can moving channels between folders and change heading unread counts.
left_sidebar_navigation_area.update_dom_with_unread_counts(unread.get_counts(), false);
sidebar_ui.update_unread_counts_visibility();
collapse_collapsed_sections();
set_sections_states();
$("#streams_list").toggleClass("is_searching", get_search_term() !== "");
}
/* When viewing a channel in a collapsed folder, we show that active
@@ -365,7 +392,7 @@ function toggle_section_collapse($container: JQuery): void {
maybe_hide_topic_bracket(section_id);
}
function collapse_collapsed_sections(): void {
function set_sections_states(): void {
for (const section_id of collapsed_sections) {
const $container = $(`#stream-list-${section_id}-container`);
$container.toggleClass("collapsed", true);
@@ -374,6 +401,9 @@ function collapse_collapsed_sections(): void {
.toggleClass("rotate-icon-down", false)
.toggleClass("rotate-icon-right", true);
}
for (const section_id of sections_showing_inactive) {
$(`#stream-list-${section_id}-container`).toggleClass("showing-inactive", true);
}
}
export function get_stream_li(stream_id: number): JQuery | undefined {
@@ -1141,6 +1171,23 @@ export function set_event_handlers({
e.stopPropagation();
},
);
$("#streams_list").on(
"click",
".stream-list-toggle-inactive-channels",
function (this: HTMLElement, e: JQuery.ClickEvent) {
e.stopPropagation();
const $section_container = $(this).closest(".stream-list-section-container");
$section_container.toggleClass("showing-inactive");
const showing_inactive = $section_container.hasClass("showing-inactive");
const section_id = $section_container.attr("data-section-id")!;
if (showing_inactive) {
sections_showing_inactive.add(section_id);
} else {
sections_showing_inactive.delete(section_id);
}
},
);
}
export function searching(): boolean {

View File

@@ -30,6 +30,7 @@ function current_section_ids_for_streams(): Map<number, StreamListSection> {
for (const stream_id of [
...section.streams,
...section.muted_streams,
...section.inactive_streams,
]) {
map.set(stream_id, section);
}
@@ -94,6 +95,7 @@ export type StreamListSection = {
section_title: string;
streams: number[];
muted_streams: number[]; // Not used for the inactive section
inactive_streams: number[]; // Only used for folder sections
};
type StreamListSortResult = {
@@ -121,18 +123,21 @@ export function sort_groups(stream_ids: number[], search_term: string): StreamLi
section_title: $t({defaultMessage: "PINNED CHANNELS"}),
streams: [],
muted_streams: [],
inactive_streams: [],
};
const normal_section: StreamListSection = {
id: "normal-streams",
section_title: $t({defaultMessage: "OTHER CHANNELS"}),
streams: [],
muted_streams: [],
inactive_streams: [],
};
const dormant_section: StreamListSection = {
id: "dormant-streams",
section_title: $t({defaultMessage: "INACTIVE CHANNELS"}),
streams: [],
muted_streams: [], // Not used for the dormant section
inactive_streams: [],
};
const folder_sections = new Map<number, StreamListSection>();
@@ -158,10 +163,13 @@ export function sort_groups(stream_ids: number[], search_term: string): StreamLi
section_title: folder.name.toUpperCase(),
streams: [],
muted_streams: [],
inactive_streams: [],
};
folder_sections.set(sub.folder_id, section);
}
if (sub.is_muted) {
if (!has_recent_activity(sub)) {
section.inactive_streams.push(stream_id);
} else if (sub.is_muted) {
section.muted_streams.push(stream_id);
} else {
section.streams.push(stream_id);
@@ -196,6 +204,7 @@ export function sort_groups(stream_ids: number[], search_term: string): StreamLi
for (const section of new_sections) {
section.streams.sort(compare_function);
section.muted_streams.sort(compare_function);
section.inactive_streams.sort(compare_function);
}
const same_as_before =
@@ -207,16 +216,18 @@ export function sort_groups(stream_ids: number[], search_term: string): StreamLi
new_section.id === current_section.id &&
new_section.section_title === current_section.section_title &&
util.array_compare(new_section.streams, current_section.streams) &&
util.array_compare(new_section.muted_streams, current_section.muted_streams)
util.array_compare(new_section.muted_streams, current_section.muted_streams) &&
util.array_compare(new_section.inactive_streams, current_section.inactive_streams)
);
});
if (!same_as_before) {
first_render_completed = true;
current_sections = new_sections;
all_streams = new_sections.flatMap((section) => [
all_streams = current_sections.flatMap((section) => [
...section.streams,
...section.muted_streams,
...section.inactive_streams,
]);
}

View File

@@ -88,7 +88,8 @@
}
}
#left-sidebar-navigation-list .selected-home-view {
#left-sidebar-navigation-list .selected-home-view,
.show-inactive-channels {
&.hide-unread-messages-count {
.masked_unread_count {
display: flex;
@@ -108,6 +109,7 @@
}
#left-sidebar-navigation-list .selected-home-view:hover,
.stream-list-toggle-inactive-channels:hover .show-inactive-channels,
.selected-home-view.top-left-active-filter {
&.hide-unread-messages-count {
.masked_unread_count {
@@ -403,6 +405,67 @@
}
}
.stream-list-toggle-inactive-channels {
padding-left: var(--left-sidebar-toggle-width-offset);
.show-inactive-channels,
.hide-inactive-channels {
/* Override the action heading font size, so that em measurements
on child elements are sized properly */
font-size: var(--base-font-size-px);
/* We also want the count text to be the normal font style. */
font-variant: inherit;
padding: 0;
.stream-list-toggle-inactive-channels-text {
font-size: var(--font-size-sidebar-action-heading);
font-variant: var(--font-variant-sidebar-action-heading);
overflow-x: hidden;
text-overflow: ellipsis;
}
}
}
.stream-list-section-container:not(.showing-inactive) {
.inactive-in-channel-folder {
display: none;
}
.stream-list-toggle-inactive-channels {
.hide-inactive-channels {
display: none;
}
}
}
#streams_list.is_searching {
.show-inactive-channels,
.hide-inactive-channels {
display: none;
}
.inactive-in-channel-folder {
display: block;
}
}
.stream-list-section-container.showing-inactive {
.stream-list-toggle-inactive-channels {
.show-inactive-channels {
display: none;
}
}
}
.show-inactive-channels,
.hide-inactive-channels {
display: grid;
grid-template:
"content markers-and-unreads three-dot-placeholder" auto
/ minmax(0, 1fr) minmax(0, max-content) var(--left-sidebar-vdots-width);
align-items: center;
}
.stream-list-section {
margin: 0;
}
@@ -413,6 +476,7 @@
.stream-list-section-container.collapsed {
.narrow-filter:not(.stream-expanded),
.stream-list-toggle-inactive-channels,
.topic-list-item:not(.active-sub-filter),
&.hide-topic-bracket ul.topic-list.topic-list-has-topics::before,
&.hide-topic-bracket ul.topic-list.topic-list-has-topics::after {
@@ -1545,7 +1609,8 @@ li.top_left_scheduled_messages {
.dm-markers-and-unreads,
.stream-markers-and-unreads,
.topic-markers-and-unreads {
.topic-markers-and-unreads,
.show-inactive-channels .markers-and-unreads {
grid-area: markers-and-unreads;
display: flex;
/* Present a uniform space between icons */

View File

@@ -0,0 +1,20 @@
<div class="stream-list-toggle-inactive-channels bottom_left_row zoom-in-hide">
<div class="show-inactive-channels sidebar-topic-action-heading">
<div class="stream-list-toggle-inactive-channels-text">
{{#tr}}
+ {inactive_count, plural, =1 {# inactive channel} other {# inactive channels}}
{{/tr}}
</div>
<div class="markers-and-unreads">
<span class="unread_count quiet-count"></span>
<span class="masked_unread_count">
<i class="zulip-icon zulip-icon-masked-unread"></i>
</span>
</div>
</div>
<div class="hide-inactive-channels sidebar-topic-action-heading">
<div class="stream-list-toggle-inactive-channels-text">
{{t "Hide inactive channels"}}
</div>
</div>
</div>

View File

@@ -103,18 +103,21 @@ test("no_subscribed_streams", () => {
sections: [
{
id: "pinned-streams",
inactive_streams: [],
muted_streams: [],
section_title: "translated: PINNED CHANNELS",
streams: [],
},
{
id: "normal-streams",
inactive_streams: [],
muted_streams: [],
section_title: "translated: CHANNELS",
streams: [],
},
{
id: "dormant-streams",
inactive_streams: [],
muted_streams: [],
section_title: "translated: INACTIVE CHANNELS",
streams: [],