diff --git a/web/src/admin.js b/web/src/admin.js index 56ec22063d..3ac52520ec 100644 --- a/web/src/admin.js +++ b/web/src/admin.js @@ -173,6 +173,8 @@ export function build_page() { web_mark_read_on_scroll_policy_values: settings_config.web_mark_read_on_scroll_policy_values, user_list_style_values: settings_config.user_list_style_values, + web_stream_unreads_count_display_policy_values: + settings_config.web_stream_unreads_count_display_policy_values, color_scheme_values: settings_config.color_scheme_values, default_view_values: settings_config.default_view_values, settings_object: realm_user_settings_defaults, diff --git a/web/src/realm_user_settings_defaults.ts b/web/src/realm_user_settings_defaults.ts index 2caed43c4b..fc7e53b2f4 100644 --- a/web/src/realm_user_settings_defaults.ts +++ b/web/src/realm_user_settings_defaults.ts @@ -39,6 +39,7 @@ export type RealmDefaultSettings = { translate_emoticons: boolean; twenty_four_hour_time: boolean; user_list_style: boolean; + web_stream_unreads_count_display_policy: number; web_mark_read_on_scroll_policy: number; wildcard_mentions_notify: boolean; }; diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index f6516e2e1c..6a78965beb 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -689,6 +689,7 @@ export function dispatch_normal_event(event) { "translate_emoticons", "display_emoji_reaction_users", "user_list_style", + "web_stream_unreads_count_display_policy", "starred_message_counts", "send_stream_typing_notifications", "send_private_typing_notifications", @@ -735,6 +736,9 @@ export function dispatch_normal_event(event) { stream_list.update_streams_sidebar(); stream_list_sort.set_filter_out_inactives(); } + if (event.property === "web_stream_unreads_count_display_policy") { + stream_list.update_dom_unread_counts_visibility(); + } if (event.property === "user_list_style") { settings_display.report_user_list_style_change( settings_display.user_settings_panel, diff --git a/web/src/settings.js b/web/src/settings.js index 2ffbd6db79..7a89121ed6 100644 --- a/web/src/settings.js +++ b/web/src/settings.js @@ -99,6 +99,8 @@ export function build_page() { web_mark_read_on_scroll_policy_values: settings_config.web_mark_read_on_scroll_policy_values, user_list_style_values: settings_config.user_list_style_values, + web_stream_unreads_count_display_policy_values: + settings_config.web_stream_unreads_count_display_policy_values, color_scheme_values: settings_config.color_scheme_values, default_view_values: settings_config.default_view_values, twenty_four_hour_time_values: settings_config.twenty_four_hour_time_values, diff --git a/web/src/settings_config.ts b/web/src/settings_config.ts index d24e66877c..7240b98c91 100644 --- a/web/src/settings_config.ts +++ b/web/src/settings_config.ts @@ -71,6 +71,21 @@ export const user_list_style_values = { // }, }; +export const web_stream_unreads_count_display_policy_values = { + all_streams: { + code: 1, + description: $t({defaultMessage: "All streams"}), + }, + unmuted_streams: { + code: 2, + description: $t({defaultMessage: "Unmuted streams"}), + }, + no_streams: { + code: 3, + description: $t({defaultMessage: "No streams"}), + }, +}; + export const default_view_values = { recent_topics: { code: "recent_topics", diff --git a/web/src/settings_data.ts b/web/src/settings_data.ts index c417460614..e5659450a8 100644 --- a/web/src/settings_data.ts +++ b/web/src/settings_data.ts @@ -195,6 +195,24 @@ export function user_can_delete_own_message(): boolean { return user_has_permission(page_params.realm_delete_own_message_policy); } +export function should_mask_unread_count(sub_muted: boolean): boolean { + if ( + user_settings.web_stream_unreads_count_display_policy === + settings_config.web_stream_unreads_count_display_policy_values.no_streams.code + ) { + return true; + } + + if ( + user_settings.web_stream_unreads_count_display_policy === + settings_config.web_stream_unreads_count_display_policy_values.unmuted_streams.code + ) { + return sub_muted; + } + + return false; +} + export function using_dark_theme(): boolean { if (user_settings.color_scheme === settings_config.color_scheme_values.night.code) { return true; diff --git a/web/src/settings_display.js b/web/src/settings_display.js index 76dcbe1784..4d9da7af00 100644 --- a/web/src/settings_display.js +++ b/web/src/settings_display.js @@ -180,6 +180,10 @@ export function set_up(settings_panel) { .find(`.setting_user_list_style_choice[value=${settings_object.user_list_style}]`) .prop("checked", true); + $container + .find(".setting_web_stream_unreads_count_display_policy") + .val(settings_object.web_stream_unreads_count_display_policy); + if (for_realm_settings) { // For the realm-level defaults page, we use the common // settings_org.js handlers, so we can return early here. diff --git a/web/src/stream_list.js b/web/src/stream_list.js index 9185a25f27..02254d7845 100644 --- a/web/src/stream_list.js +++ b/web/src/stream_list.js @@ -373,15 +373,17 @@ export function set_in_home_view(stream_id, in_home) { function build_stream_sidebar_li(sub) { const name = sub.name; + const is_muted = stream_data.is_muted(sub.stream_id); const args = { name, id: sub.stream_id, url: hash_util.by_stream_url(sub.stream_id), - is_muted: stream_data.is_muted(sub.stream_id) === true, + is_muted, invite_only: sub.invite_only, is_web_public: sub.is_web_public, color: sub.color, pin_to_top: sub.pin_to_top, + hide_unread_count: settings_data.should_mask_unread_count(is_muted), }; const $list_item = $(render_stream_sidebar_row(args)); return $list_item; @@ -535,6 +537,21 @@ export function update_dom_with_unread_counts(counts) { } } +export function update_dom_unread_counts_visibility() { + for (const stream of stream_sidebar.rows.values()) { + const $subscription_block = stream.get_li().find(".subscription_block"); + + const is_muted = stream_data.is_muted(stream.sub.stream_id); + const hide_count = settings_data.should_mask_unread_count(is_muted); + + if (hide_count) { + $subscription_block.addClass("hide_unread_counts"); + } else { + $subscription_block.removeClass("hide_unread_counts"); + } + } +} + export function rename_stream(sub) { // The sub object is expected to already have the updated name build_stream_sidebar_row(sub); diff --git a/web/src/user_settings.ts b/web/src/user_settings.ts index 5326e12379..be3a9c29d8 100644 --- a/web/src/user_settings.ts +++ b/web/src/user_settings.ts @@ -48,6 +48,7 @@ export type UserSettings = (StreamNotificationSettings & presence_enabled: boolean; realm_name_in_email_notifications_policy: number; user_list_style: number; + web_stream_unreads_count_display_policy: number; starred_message_counts: boolean; translate_emoticons: boolean; display_emoji_reaction_users: boolean; diff --git a/web/styles/app_components.css b/web/styles/app_components.css index 3078ae2399..8d8dbff820 100644 --- a/web/styles/app_components.css +++ b/web/styles/app_components.css @@ -589,6 +589,15 @@ div.overlay { opacity: 0.7; } +.masked_unread_count { + float: right; + width: 8px; + height: 8px; + border-radius: 50%; + background-color: hsl(0deg 0% 80%); + display: none; +} + /* Implement the web app's default-hidden convention for alert elements. Portico pages lack this CSS and thus show them by default. */ diff --git a/web/styles/left_sidebar.css b/web/styles/left_sidebar.css index eec750e705..95dccc6b49 100644 --- a/web/styles/left_sidebar.css +++ b/web/styles/left_sidebar.css @@ -141,10 +141,6 @@ li.show-more-topics { } } - .subscription_block .unread_count { - margin-right: 15px; - } - .subscription_block { margin-right: 25px; margin-left: $far_left_gutter_size; @@ -152,11 +148,67 @@ li.show-more-topics { align-items: center; white-space: nowrap; + & .unread_count { + margin-right: 15px; + } + + & .masked_unread_count { + /* TODO: In theory this should be 19, to make it center aligned with + unread_count span which has a padding of 4px + horizontally in addition to 15px margin. But 18px looks + better centered. */ + margin-right: 18px; + } + &.stream-with-count { margin-right: 15px; } } + .stream-with-count.hide_unread_counts { + .masked_unread_count { + display: inline; + } + + .unread_count { + display: none; + } + } + + .narrow-filter + > .bottom_left_row:hover + > .stream-with-count.hide_unread_counts { + .masked_unread_count { + display: none; + } + + .unread_count { + display: inline; + } + } + + .stream-expanded { + .stream-with-count.hide_unread_counts { + .masked_unread_count { + display: none; + } + + .unread_count { + display: inline; + } + } + } + + .has-unmuted-unreads.hide_unread_counts { + .masked_unread_count { + display: none; + } + + .unread_count { + display: inline; + } + } + .inactive_stream:not(.active-filter) { opacity: 0.5; } @@ -390,6 +442,10 @@ ul.filters { opacity: 0.5; } + & .masked_unread_count { + opacity: 0.5; + } + .has-unmuted-unreads { .unread_count { opacity: 1; @@ -473,7 +529,6 @@ li.active-sub-filter { .top_left_row .unread_count { margin-top: 2px; - display: none; } .top_left_starred_messages .unread_count, diff --git a/web/templates/settings/display_settings.hbs b/web/templates/settings/display_settings.hbs index 79ed3956f4..0f1da9548c 100644 --- a/web/templates/settings/display_settings.hbs +++ b/web/templates/settings/display_settings.hbs @@ -141,6 +141,13 @@ +
+ + +
+ {{#each display_settings.settings.user_display_settings}} {{> settings_checkbox setting_name=this diff --git a/web/templates/stream_sidebar_row.hbs b/web/templates/stream_sidebar_row.hbs index bc9bc936df..678c05bad8 100644 --- a/web/templates/stream_sidebar_row.hbs +++ b/web/templates/stream_sidebar_row.hbs @@ -2,7 +2,7 @@
  • -
    +
    {{> stream_privacy }} @@ -12,6 +12,7 @@ +
    diff --git a/web/tests/dispatch.test.js b/web/tests/dispatch.test.js index 9b362834c8..339285aa97 100644 --- a/web/tests/dispatch.test.js +++ b/web/tests/dispatch.test.js @@ -966,6 +966,16 @@ run_test("user_settings", ({override}) => { assert_same(user_settings.demote_inactive_streams, 2); } + { + const stub = make_stub(); + event = event_fixtures.user_settings__web_stream_unreads_count_display_policy; + override(stream_list, "update_dom_unread_counts_visibility", stub.f); + user_settings.web_stream_unreads_count_display_policy = 1; + dispatch(event); + assert.equal(stub.num_calls, 1); + assert_same(user_settings.web_stream_unreads_count_display_policy, 2); + } + { const stub = make_stub(); event = event_fixtures.user_settings__user_list_style; diff --git a/web/tests/lib/events.js b/web/tests/lib/events.js index 3fdb588cb0..9632d4b20f 100644 --- a/web/tests/lib/events.js +++ b/web/tests/lib/events.js @@ -1031,6 +1031,13 @@ exports.fixtures = { value: 1, }, + user_settings__web_stream_unreads_count_display_policy: { + type: "user_settings", + op: "update", + property: "web_stream_unreads_count_display_policy", + value: 2, + }, + user_status__set_status_emoji: { type: "user_status", user_id: test_user.user_id, diff --git a/web/tests/stream_list.test.js b/web/tests/stream_list.test.js index 4423cda67c..011253676e 100644 --- a/web/tests/stream_list.test.js +++ b/web/tests/stream_list.test.js @@ -648,6 +648,8 @@ test_ui("separators_only_pinned", () => { }); test_ui("rename_stream", ({mock_template}) => { + user_settings.web_stream_unreads_count_display_policy = 3; + create_stream_subheader({mock_template}); initialize_stream_data(); @@ -664,11 +666,12 @@ test_ui("rename_stream", ({mock_template}) => { name: "Development", id: 1000, url: "#narrow/stream/1000-Development", - is_muted: false, + is_muted: undefined, invite_only: undefined, is_web_public: undefined, color: payload.color, pin_to_top: true, + hide_unread_count: true, }); return {to_$: () => $li_stub}; }); @@ -722,6 +725,7 @@ test_ui("refresh_pin", ({override, override_rewire, mock_template}) => { }); test_ui("create_initial_sidebar_rows", ({override, override_rewire, mock_template}) => { + user_settings.web_stream_unreads_count_display_policy = 2; // Test coverage for this setting. initialize_stream_data(); const html_dict = new Map();