diff --git a/web/src/stream_list.js b/web/src/stream_list.js index f06e96d35c..c08195042b 100644 --- a/web/src/stream_list.js +++ b/web/src/stream_list.js @@ -37,6 +37,7 @@ export function update_count_in_dom( stream_counts, stream_has_any_unread_mention_messages, stream_has_any_unmuted_unread_mention, + stream_has_only_muted_unread_mention, ) { // The subscription_block properly excludes the topic list, // and it also has sensitive margins related to whether the @@ -50,8 +51,14 @@ export function update_count_in_dom( if (stream_has_any_unmuted_unread_mention) { $subscription_block.addClass("has-unmuted-mentions"); + $subscription_block.removeClass("has-only-muted-mentions"); } else { $subscription_block.removeClass("has-unmuted-mentions"); + if (!stream_counts.stream_is_muted && stream_has_only_muted_unread_mention) { + $subscription_block.addClass("has-only-muted-mentions"); + } else { + $subscription_block.removeClass("has-only-muted-mentions"); + } } // Here we set the count and compute the values of two classes: @@ -64,16 +71,32 @@ export function update_count_in_dom( ui_util.update_unread_count_in_dom($subscription_block, stream_counts.unmuted_count); $subscription_block.addClass("stream-with-count"); $subscription_block.removeClass("has-unmuted-unreads"); + $subscription_block.removeClass("has-only-muted-unreads"); } else if (stream_counts.unmuted_count > 0 && stream_counts.stream_is_muted) { // Muted stream, has unmuted unreads. ui_util.update_unread_count_in_dom($subscription_block, stream_counts.unmuted_count); $subscription_block.addClass("stream-with-count"); $subscription_block.addClass("has-unmuted-unreads"); + $subscription_block.removeClass("has-only-muted-unreads"); } else if (stream_counts.muted_count > 0 && stream_counts.stream_is_muted) { // Muted stream, only muted unreads. ui_util.update_unread_count_in_dom($subscription_block, stream_counts.muted_count); $subscription_block.addClass("stream-with-count"); $subscription_block.removeClass("has-unmuted-unreads"); + $subscription_block.removeClass("has-only-muted-unreads"); + } else if ( + stream_counts.muted_count > 0 && + !stream_counts.stream_is_muted && + stream_has_only_muted_unread_mention + ) { + // Normal stream, only muted unreads, including a mention: + // Display the mention, faded, and a faded unread count too, + // so that we don't weirdly show the mention indication + // without an unread count. + ui_util.update_unread_count_in_dom($subscription_block, stream_counts.muted_count); + $subscription_block.removeClass("has-unmuted-unreads"); + $subscription_block.addClass("stream-with-count"); + $subscription_block.addClass("has-only-muted-unreads"); } else if (stream_counts.muted_count > 0 && !stream_counts.stream_is_muted) { // Normal stream, only muted unreads: display nothing. The // current thinking is displaying those counts with muted @@ -85,6 +108,7 @@ export function update_count_in_dom( // No unreads: display nothing. ui_util.update_unread_count_in_dom($subscription_block, 0); $subscription_block.removeClass("has-unmuted-unreads"); + $subscription_block.removeClass("has-only-muted-unreads"); $subscription_block.removeClass("stream-with-count"); } } @@ -396,11 +420,16 @@ class StreamSidebarRow { const stream_has_any_unmuted_unread_mention = unread.stream_has_any_unmuted_mentions( this.sub.stream_id, ); + const stream_has_only_muted_unread_mentions = + !this.sub.is_muted && + stream_has_any_unread_mention_messages && + !stream_has_any_unmuted_unread_mention; update_count_in_dom( this.$list_item, count, stream_has_any_unread_mention_messages, stream_has_any_unmuted_unread_mention, + stream_has_only_muted_unread_mentions, ); } } @@ -443,6 +472,7 @@ function set_stream_unread_count( count, stream_has_any_unread_mention_messages, stream_has_any_unmuted_unread_mention, + stream_has_only_muted_unread_mentions, ) { const $stream_li = get_stream_li(stream_id); if (!$stream_li) { @@ -456,6 +486,7 @@ function set_stream_unread_count( count, stream_has_any_unread_mention_messages, stream_has_any_unmuted_unread_mention, + stream_has_only_muted_unread_mentions, ); } @@ -492,11 +523,16 @@ export function update_dom_with_unread_counts(counts) { counts.streams_with_mentions.includes(stream_id); const stream_has_any_unmuted_unread_mention = counts.streams_with_unmuted_mentions.includes(stream_id); + const stream_has_only_muted_unread_mentions = + !sub_store.get(stream_id).is_muted && + stream_has_any_unread_mention_messages && + !stream_has_any_unmuted_unread_mention; set_stream_unread_count( stream_id, count, stream_has_any_unread_mention_messages, stream_has_any_unmuted_unread_mention, + stream_has_only_muted_unread_mentions, ); } } diff --git a/web/src/unread.js b/web/src/unread.js index 6407eb1051..67b425e38b 100644 --- a/web/src/unread.js +++ b/web/src/unread.js @@ -403,11 +403,13 @@ class UnreadTopicCounter { } get_streams_with_unread_mentions() { - const streams_with_mentions = new Set(); - // Collect the set of streams containing at least one mention. + // Collect the set of streams containing at least one unread + // mention, without considering muting. + // We can do this efficiently, since unread_mentions_counter // contains all unread message IDs, and we use stream_ids as // bucket keys in our outer bucketer. + const streams_with_mentions = new Set(); for (const message_id of unread_mentions_counter) { const stream_id = this.bucketer.reverse_lookup.get(message_id); @@ -422,9 +424,10 @@ class UnreadTopicCounter { } get_streams_with_unmuted_mentions() { - const streams_with_unmuted_mentions = new Set(); // Collect the set of streams containing at least one mention - // in an unmuted topic within a muted stream. + // that is not in a muted topic or non-unmuted topic in a + // muted stream. + const streams_with_unmuted_mentions = new Set(); for (const message_id of unread_mentions_counter) { const stream_id = this.bucketer.reverse_lookup.get(message_id); if (stream_id === undefined) { @@ -434,8 +437,15 @@ class UnreadTopicCounter { const stream_bucketer = this.bucketer.get_bucket(stream_id); const topic = stream_bucketer.reverse_lookup.get(message_id); - if (user_topics.is_topic_unmuted(stream_id, topic)) { - streams_with_unmuted_mentions.add(stream_id); + const stream_is_muted = sub_store.get(stream_id)?.is_muted; + if (stream_is_muted) { + if (user_topics.is_topic_unmuted(stream_id, topic)) { + streams_with_unmuted_mentions.add(stream_id); + } + } else { + if (!user_topics.is_topic_muted(stream_id, topic)) { + streams_with_unmuted_mentions.add(stream_id); + } } } return streams_with_unmuted_mentions; diff --git a/web/styles/left_sidebar.css b/web/styles/left_sidebar.css index c254c591f9..f795a3857d 100644 --- a/web/styles/left_sidebar.css +++ b/web/styles/left_sidebar.css @@ -353,6 +353,16 @@ ul.filters { margin-bottom: 10px; } + .has-only-muted-unreads { + .unread_count { + opacity: 0.5; + } + } + + .has-only-muted-mentions .unread_mention_info { + opacity: 0.5; + } + /* This is a noop in the current design, because unread counts for muted streams have the same opacity, but the logic is here to be explicit and because the design may change in the future. */ @@ -765,6 +775,10 @@ li.topic-list-item { visibility: hidden; } +.zero-topic-unreads.show-more-topics .topic-box { + margin-right: 30px; +} + .searching-for-more-topics img { height: 16px; margin-left: 6px; diff --git a/web/tests/stream_list.test.js b/web/tests/stream_list.test.js index 7443279221..94718fbf9d 100644 --- a/web/tests/stream_list.test.js +++ b/web/tests/stream_list.test.js @@ -15,7 +15,6 @@ page_params.realm_users = []; // We use this with override. let num_unread_for_stream; let stream_has_any_unread_mentions; -let stream_has_any_unmuted_mentions; const noop = () => {}; mock_esm("../src/narrow_state", { @@ -36,7 +35,7 @@ mock_esm("../src/unread", { muted_count: 0, }), stream_has_any_unread_mentions: () => stream_has_any_unread_mentions, - stream_has_any_unmuted_mentions: () => stream_has_any_unmuted_mentions, + stream_has_any_unmuted_mentions: () => noop, }); const {Filter} = zrequire("../src/filter");