fix: allow disabling member lists

This commit is contained in:
Hampus Kraft
2026-02-18 21:42:30 +00:00
parent 67267d509d
commit f1bfd080e2
5 changed files with 141 additions and 21 deletions

View File

@@ -20,6 +20,7 @@
import styles from '@app/components/channel/ChannelMembers.module.css';
import {MemberListContainer} from '@app/components/channel/MemberListContainer';
import {MemberListItem} from '@app/components/channel/MemberListItem';
import {MemberListUnavailableFallback} from '@app/components/channel/shared/MemberListUnavailableFallback';
import {OutlineFrame} from '@app/components/layout/OutlineFrame';
import {resolveMemberListPresence} from '@app/hooks/useMemberListPresence';
import {useMemberListSubscription} from '@app/hooks/useMemberListSubscription';
@@ -34,6 +35,7 @@ import type {GroupDMMemberGroup} from '@app/utils/MemberListUtils';
import * as MemberListUtils from '@app/utils/MemberListUtils';
import * as NicknameUtils from '@app/utils/NicknameUtils';
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
import {GuildOperations} from '@fluxer/constants/src/GuildConstants';
import {isOfflineStatus} from '@fluxer/constants/src/StatusConstants';
import {useLingui} from '@lingui/react/macro';
import clsx from 'clsx';
@@ -152,11 +154,12 @@ interface LazyMemberListProps {
const LazyMemberList = observer(function LazyMemberList({guild, channel}: LazyMemberListProps) {
const [subscribedRange, setSubscribedRange] = useState<[number, number]>(INITIAL_MEMBER_RANGE);
const memberListUpdatesDisabled = (guild.disabledOperations & GuildOperations.MEMBER_LIST_UPDATES) !== 0;
const {subscribe} = useMemberListSubscription({
guildId: guild.id,
channelId: channel.id,
enabled: true,
enabled: !memberListUpdatesDisabled,
allowInitialUnfocusedLoad: true,
});
@@ -181,6 +184,14 @@ const LazyMemberList = observer(function LazyMemberList({guild, channel}: LazyMe
[subscribedRange, subscribe],
);
if (memberListUpdatesDisabled) {
return (
<MemberListContainer channelId={channel.id}>
<MemberListUnavailableFallback />
</MemberListContainer>
);
}
if (isLoading) {
return (
<MemberListContainer channelId={channel.id}>

View File

@@ -17,10 +17,14 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {GuildRecord} from '@app/records/GuildRecord';
import GuildMemberStore from '@app/stores/GuildMemberStore';
import GuildStore from '@app/stores/GuildStore';
import MemberSidebarStore from '@app/stores/MemberSidebarStore';
import {buildMemberListLayout} from '@app/utils/MemberListLayout';
import {GuildOperations} from '@fluxer/constants/src/GuildConstants';
import type {GuildMemberData} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
import type {Guild} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
import type {UserPartialResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
import {beforeEach, describe, expect, test} from 'vitest';
@@ -60,10 +64,25 @@ function seedMembers(guildId: string, members: Array<{id: string; name: string}>
}
}
function createGuild(guildId: string, disabledOperations = 0): GuildRecord {
const guild: Guild = {
id: guildId,
name: `Guild ${guildId}`,
icon: null,
vanity_url_code: null,
owner_id: 'owner-1',
system_channel_id: null,
features: [],
disabled_operations: disabledOperations,
};
return new GuildRecord(guild);
}
describe('MemberSidebarStore', () => {
beforeEach(() => {
MemberSidebarStore.handleSessionInvalidated();
GuildMemberStore.handleConnectionOpen([]);
GuildStore.guilds = {};
});
test('stores members by member index when sync includes group entries', () => {
@@ -357,4 +376,39 @@ describe('MemberSidebarStore', () => {
expect(Array.from(listState?.rows.keys() ?? [])).toEqual([0, 1, 2]);
expect(Array.from(listState?.items.values() ?? []).map((item) => item.data.user.id)).toEqual(['u-1', 'u-2']);
});
test('ignores list updates when member list updates are disabled for the guild', () => {
const guildId = 'guild-disabled-updates';
const listId = 'list-disabled-updates';
GuildStore.guilds[guildId] = createGuild(guildId, GuildOperations.MEMBER_LIST_UPDATES);
seedMembers(guildId, [{id: 'u-1', name: 'Alpha'}]);
MemberSidebarStore.handleListUpdate({
guildId,
listId,
memberCount: 1,
onlineCount: 1,
groups: [{id: 'online', count: 1}],
ops: [
{
op: 'SYNC',
range: [0, 1],
items: [{group: {id: 'online', count: 1}}, {member: {user: {id: 'u-1'}}}],
},
],
});
expect(MemberSidebarStore.getList(guildId, listId)).toBeUndefined();
});
test('treats subscribe attempts as no-op when member list updates are disabled for the guild', () => {
const guildId = 'guild-disabled-subscribe';
const channelId = 'channel-disabled-subscribe';
GuildStore.guilds[guildId] = createGuild(guildId, GuildOperations.MEMBER_LIST_UPDATES);
MemberSidebarStore.subscribeToChannel(guildId, channelId, [[0, 99]]);
expect(MemberSidebarStore.getList(guildId, channelId)).toBeUndefined();
expect(MemberSidebarStore.getSubscribedRanges(guildId, channelId)).toEqual([]);
});
});

View File

@@ -23,6 +23,7 @@ import {CustomStatusEmitter} from '@app/lib/CustomStatusEmitter';
import {Logger} from '@app/lib/Logger';
import type {GuildMemberRecord} from '@app/records/GuildMemberRecord';
import GuildMemberStore from '@app/stores/GuildMemberStore';
import GuildStore from '@app/stores/GuildStore';
import GatewayConnectionStore from '@app/stores/gateway/GatewayConnectionStore';
import WindowStore from '@app/stores/WindowStore';
import {
@@ -31,6 +32,7 @@ import {
getTotalMemberCount,
getTotalRowsFromLayout,
} from '@app/utils/MemberListLayout';
import {GuildOperations} from '@fluxer/constants/src/GuildConstants';
import type {StatusType} from '@fluxer/constants/src/StatusConstants';
import {StatusTypes} from '@fluxer/constants/src/StatusConstants';
import {makeAutoObservable} from 'mobx';
@@ -165,6 +167,10 @@ class MemberSidebarStore {
ops: Array<MemberListOperation>;
}): void {
const {guildId, listId, channelId, memberCount, onlineCount, groups, ops} = params;
if (this.isMemberListUpdatesDisabled(guildId)) {
return;
}
const storageKey = listId;
const existingGuildLists = this.lists[guildId] ?? {};
const guildLists: Record<string, MemberListState> = {...existingGuildLists};
@@ -445,6 +451,10 @@ class MemberSidebarStore {
}
subscribeToChannel(guildId: string, channelId: string, ranges: Array<[number, number]>): void {
if (this.isMemberListUpdatesDisabled(guildId)) {
return;
}
const storageKey = this.resolveListKey(guildId, channelId);
const socket = GatewayConnectionStore.socket;
@@ -564,6 +574,14 @@ class MemberSidebarStore {
return listState.customStatuses.get(userId) ?? null;
}
private isMemberListUpdatesDisabled(guildId: string): boolean {
const guild = GuildStore.getGuild(guildId);
if (!guild) {
return false;
}
return (guild.disabledOperations & GuildOperations.MEMBER_LIST_UPDATES) !== 0;
}
private touchList(guildId: string, listId: string): void {
const now = Date.now();
if (!this.lastAccess[guildId]) {