fix(app): hide unread channels in muted collapsed categories (#2752)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-13 23:59:28 +02:00
1 parent a9cc04d277
commit a9dc74a520
3 files changed
+57 -14

No files matched your search

@@ -23,7 +23,10 @@ import {
} from '@app/features/app/components/layout/utils/ChannelListVisibility'; } from '@app/features/app/components/layout/utils/ChannelListVisibility';
import {createChannelMoveOperation} from '@app/features/app/components/layout/utils/ChannelMoveOperation'; import {createChannelMoveOperation} from '@app/features/app/components/layout/utils/ChannelMoveOperation';
import {organizeChannels} from '@app/features/app/components/layout/utils/ChannelOrganization'; import {organizeChannels} from '@app/features/app/components/layout/utils/ChannelOrganization';
import {getChannelUnreadState} from '@app/features/app/components/layout/utils/ChannelUnreadState'; import {
type ChannelUnreadState,
getChannelUnreadState,
} from '@app/features/app/components/layout/utils/ChannelUnreadState';
import {VoiceParticipantsList} from '@app/features/app/components/layout/VoiceParticipantsList'; import {VoiceParticipantsList} from '@app/features/app/components/layout/VoiceParticipantsList';
import { import {
type RememberedSkeletonGuildChannelGroup, type RememberedSkeletonGuildChannelGroup,
@@ -334,7 +337,7 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr
}, },
[guild], [guild],
); );
const hasVisibleUnreadInChannel = (channelId: string): boolean => { const getUnreadStateInChannel = (channelId: string): ChannelUnreadState => {
const unreadCount = ReadStates.getUnreadCount(channelId); const unreadCount = ReadStates.getUnreadCount(channelId);
const hasUnread = ReadStates.hasUnread(channelId); const hasUnread = ReadStates.hasUnread(channelId);
const mentionCount = ReadStates.getMentionCount(channelId); const mentionCount = ReadStates.getMentionCount(channelId);
@@ -348,7 +351,7 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr
type: channel.type, type: channel.type,
}) })
: null; : null;
const unreadState = getChannelUnreadState({ return getChannelUnreadState({
hasUnread, hasUnread,
unreadCount, unreadCount,
mentionCount, mentionCount,
@@ -356,16 +359,14 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr
showFadedUnreadOnMutedChannels, showFadedUnreadOnMutedChannels,
unreadBadgesLevel, unreadBadgesLevel,
}); });
return unreadState.hasVisibleUnread;
}; };
const resolvedGroups: Array<ResolvedChannelGroup> = []; const resolvedGroups: Array<ResolvedChannelGroup> = [];
for (const group of channelGroups) { for (const group of channelGroups) {
const isCollapsed = group.category ? (collapsedCategories?.has(group.category.id) ?? false) : false; const isCollapsed = group.category ? (collapsedCategories?.has(group.category.id) ?? false) : false;
const isNullSpace = !group.category; const isNullSpace = !group.category;
const isCategoryMuted = const isCategoryMuted = group.category
hideMutedChannels && group.category ? UserGuildSettings.isChannelDirectlyMuted(guild.id, group.category.id)
? UserGuildSettings.isChannelDirectlyMuted(guild.id, group.category.id) : false;
: false;
let filteredTextChannels: Array<Channel>; let filteredTextChannels: Array<Channel>;
let filteredVoiceChannels: Array<Channel>; let filteredVoiceChannels: Array<Channel>;
if (hideMutedChannels) { if (hideMutedChannels) {
@@ -379,7 +380,7 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr
isChannelMuted, isChannelMuted,
isSelected: ch.id === selectedChannelInGuildId, isSelected: ch.id === selectedChannelInGuildId,
isConnected: false, isConnected: false,
hasVisibleUnread: isMuted && hasVisibleUnreadInChannel(ch.id), hasVisibleUnread: isMuted && getUnreadStateInChannel(ch.id).hasVisibleUnread,
}) })
) { ) {
filteredTextChannels.push(ch); filteredTextChannels.push(ch);
@@ -395,7 +396,7 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr
isChannelMuted, isChannelMuted,
isSelected: ch.id === selectedChannelInGuildId, isSelected: ch.id === selectedChannelInGuildId,
isConnected: ch.id === connectedChannelId, isConnected: ch.id === connectedChannelId,
hasVisibleUnread: isMuted && hasVisibleUnreadInChannel(ch.id), hasVisibleUnread: isMuted && getUnreadStateInChannel(ch.id).hasVisibleUnread,
}) })
) { ) {
filteredVoiceChannels.push(ch); filteredVoiceChannels.push(ch);
@@ -412,11 +413,14 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr
const showTextSelected = selectedChannelInGuildId; const showTextSelected = selectedChannelInGuildId;
const showSet = new Set<string>(); const showSet = new Set<string>();
for (const ch of filteredTextChannels) { for (const ch of filteredTextChannels) {
const unreadState = getUnreadStateInChannel(ch.id);
if ( if (
shouldShowChannelInCollapsedCategory({ shouldShowChannelInCollapsedCategory({
isCategoryMuted,
isSelected: ch.id === showTextSelected, isSelected: ch.id === showTextSelected,
isConnected: false, isConnected: false,
hasVisibleUnread: hasVisibleUnreadInChannel(ch.id), hasVisibleUnread: unreadState.hasVisibleUnread,
hasMentions: unreadState.hasMentions,
}) })
) { ) {
showSet.add(ch.id); showSet.add(ch.id);
@@ -443,11 +447,14 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr
} }
} }
for (const ch of filteredVoiceChannels) { for (const ch of filteredVoiceChannels) {
const unreadState = getUnreadStateInChannel(ch.id);
if ( if (
shouldShowChannelInCollapsedCategory({ shouldShowChannelInCollapsedCategory({
isCategoryMuted,
isSelected: ch.id === selectedChannelInGuildId, isSelected: ch.id === selectedChannelInGuildId,
isConnected: ch.id === connectedChannelId, isConnected: ch.id === connectedChannelId,
hasVisibleUnread: hasVisibleUnreadInChannel(ch.id), hasVisibleUnread: unreadState.hasVisibleUnread,
hasMentions: unreadState.hasMentions,
}) })
) { ) {
voiceSet.add(ch.id); voiceSet.add(ch.id);
@@ -123,26 +123,32 @@ describe('shouldShowChannelInCollapsedCategory', () => {
it('keeps visible unread channels reachable in collapsed categories', () => { it('keeps visible unread channels reachable in collapsed categories', () => {
expect( expect(
shouldShowChannelInCollapsedCategory({ shouldShowChannelInCollapsedCategory({
isCategoryMuted: false,
isSelected: false, isSelected: false,
isConnected: false, isConnected: false,
hasVisibleUnread: true, hasVisibleUnread: true,
hasMentions: false,
}), }),
).toBe(true); ).toBe(true);
}); });
it('keeps selected and connected channels visible without unread state', () => { it('keeps selected and connected channels visible without unread state, even in muted categories', () => {
expect( expect(
shouldShowChannelInCollapsedCategory({ shouldShowChannelInCollapsedCategory({
isCategoryMuted: true,
isSelected: true, isSelected: true,
isConnected: false, isConnected: false,
hasVisibleUnread: false, hasVisibleUnread: false,
hasMentions: false,
}), }),
).toBe(true); ).toBe(true);
expect( expect(
shouldShowChannelInCollapsedCategory({ shouldShowChannelInCollapsedCategory({
isCategoryMuted: true,
isSelected: false, isSelected: false,
isConnected: true, isConnected: true,
hasVisibleUnread: false, hasVisibleUnread: false,
hasMentions: false,
}), }),
).toBe(true); ).toBe(true);
}); });
@@ -150,10 +156,36 @@ describe('shouldShowChannelInCollapsedCategory', () => {
it('hides read channels in collapsed categories', () => { it('hides read channels in collapsed categories', () => {
expect( expect(
shouldShowChannelInCollapsedCategory({ shouldShowChannelInCollapsedCategory({
isCategoryMuted: false,
isSelected: false, isSelected: false,
isConnected: false, isConnected: false,
hasVisibleUnread: false, hasVisibleUnread: false,
hasMentions: false,
}), }),
).toBe(false); ).toBe(false);
}); });
it('hides unread channels without mentions in muted collapsed categories', () => {
expect(
shouldShowChannelInCollapsedCategory({
isCategoryMuted: true,
isSelected: false,
isConnected: false,
hasVisibleUnread: true,
hasMentions: false,
}),
).toBe(false);
});
it('keeps mentioned channels reachable in muted collapsed categories', () => {
expect(
shouldShowChannelInCollapsedCategory({
isCategoryMuted: true,
isSelected: false,
isConnected: false,
hasVisibleUnread: true,
hasMentions: true,
}),
).toBe(true);
});
}); });
@@ -31,15 +31,19 @@ export function shouldShowCategoryWhenHidingMutedChannels({
} }
export interface CollapsedCategoryChannelVisibilityInput { export interface CollapsedCategoryChannelVisibilityInput {
isCategoryMuted: boolean;
isSelected: boolean; isSelected: boolean;
isConnected: boolean; isConnected: boolean;
hasVisibleUnread: boolean; hasVisibleUnread: boolean;
hasMentions: boolean;
} }
export function shouldShowChannelInCollapsedCategory({ export function shouldShowChannelInCollapsedCategory({
isCategoryMuted,
isSelected, isSelected,
isConnected, isConnected,
hasVisibleUnread, hasVisibleUnread,
hasMentions,
}: CollapsedCategoryChannelVisibilityInput): boolean { }: CollapsedCategoryChannelVisibilityInput): boolean {
return isSelected || isConnected || hasVisibleUnread; return isSelected || isConnected || (isCategoryMuted ? hasMentions : hasVisibleUnread);
} }