mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(app): pick default channel when guild channels arrive late (#2475)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import {Routes} from '@app/app/Routes';
|
||||
import {GuildLayout} from '@app/features/app/components/layout/GuildLayout';
|
||||
import Channels from '@app/features/channel/state/Channels';
|
||||
import {compareChannelPosition, filterViewableChannels} from '@app/features/messaging/utils/ChannelShared';
|
||||
import {pickDefaultGuildChannelId} from '@app/features/messaging/utils/ChannelShared';
|
||||
import * as NavigationCommands from '@app/features/navigation/commands/NavigationCommands';
|
||||
import SelectedChannel from '@app/features/navigation/state/SelectedChannel';
|
||||
import {useLocation} from '@app/features/platform/components/router/RouterReact';
|
||||
@@ -13,47 +13,33 @@ import {observer} from 'mobx-react-lite';
|
||||
import type React from 'react';
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function isGuildRootPath(pathname: string, guildId: string): boolean {
|
||||
if (guildId === ME || pathname === Routes.ME || pathname.startsWith(Routes.ME)) {
|
||||
return false;
|
||||
}
|
||||
if (!pathname.startsWith('/channels/')) {
|
||||
return false;
|
||||
}
|
||||
const segments = pathname.split('/');
|
||||
return segments.length === 3 && segments[2] === guildId;
|
||||
}
|
||||
|
||||
export const GuildChannelRouter = observer<{guildId: string; children: React.ReactNode}>(({guildId, children}) => {
|
||||
const location = useLocation();
|
||||
const needsDefaultChannel = !MobileLayout.enabled && isGuildRootPath(location.pathname, guildId);
|
||||
const defaultChannelId = needsDefaultChannel
|
||||
? pickDefaultGuildChannelId({
|
||||
guildId,
|
||||
channels: Channels.getGuildChannels(guildId),
|
||||
selectedChannelId: SelectedChannel.selectedChannelIds.get(guildId),
|
||||
})
|
||||
: null;
|
||||
useEffect(() => {
|
||||
if (guildId === ME || location.pathname === Routes.ME) {
|
||||
if (!defaultChannelId) {
|
||||
return;
|
||||
}
|
||||
if (MobileLayout.enabled) {
|
||||
return;
|
||||
}
|
||||
if (location.pathname.startsWith('/channels/') && !location.pathname.startsWith(Routes.ME)) {
|
||||
if (location.pathname.split('/').length === 3) {
|
||||
const pathSegments = location.pathname.split('/');
|
||||
const currentGuildId = pathSegments[2];
|
||||
if (currentGuildId !== guildId) {
|
||||
return;
|
||||
}
|
||||
const selectedChannelId = SelectedChannel.selectedChannelIds.get(guildId);
|
||||
if (selectedChannelId) {
|
||||
const channel = Channels.getChannel(selectedChannelId);
|
||||
const isViewableChannel = channel ? filterViewableChannels([channel]).length > 0 : false;
|
||||
if (channel && channel.guildId === guildId && isViewableChannel) {
|
||||
NavigationCommands.selectChannel(guildId, selectedChannelId, undefined, 'replace');
|
||||
} else {
|
||||
const channels = Channels.getGuildChannels(guildId);
|
||||
const viewableChannels = filterViewableChannels(channels).sort(compareChannelPosition);
|
||||
if (viewableChannels.length > 0) {
|
||||
const firstChannel = viewableChannels[0];
|
||||
NavigationCommands.selectChannel(guildId, firstChannel.id, undefined, 'replace');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const channels = Channels.getGuildChannels(guildId);
|
||||
const viewableChannels = filterViewableChannels(channels).sort(compareChannelPosition);
|
||||
if (viewableChannels.length > 0) {
|
||||
const firstChannel = viewableChannels[0];
|
||||
NavigationCommands.selectChannel(guildId, firstChannel.id, undefined, 'replace');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [guildId, location.pathname, MobileLayout.enabled]);
|
||||
NavigationCommands.selectChannel(guildId, defaultChannelId, undefined, 'replace');
|
||||
}, [guildId, defaultChannelId]);
|
||||
if (guildId === ME || location.pathname === Routes.ME) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {filterViewableChannels} from './ChannelShared';
|
||||
import {filterViewableChannels, pickDefaultGuildChannelId} from './ChannelShared';
|
||||
|
||||
describe('filterViewableChannels', () => {
|
||||
it('excludes link channels from default navigation fallbacks', () => {
|
||||
@@ -15,3 +15,41 @@ describe('filterViewableChannels', () => {
|
||||
expect(filterViewableChannels(channels).map((channel) => channel.id)).toEqual(['text', 'voice']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pickDefaultGuildChannelId', () => {
|
||||
const channels = [
|
||||
{id: 'category', type: ChannelTypes.GUILD_CATEGORY, position: 1, guildId: 'guild'},
|
||||
{id: 'link', type: ChannelTypes.GUILD_LINK, position: 2, guildId: 'guild'},
|
||||
{id: 'rules', type: ChannelTypes.GUILD_TEXT, position: 3, guildId: 'guild'},
|
||||
{id: 'general', type: ChannelTypes.GUILD_TEXT, position: 4, guildId: 'guild'},
|
||||
{id: 'voice', type: ChannelTypes.GUILD_VOICE, position: 5, guildId: 'guild'},
|
||||
];
|
||||
|
||||
it('returns null while the guild has no channels yet', () => {
|
||||
expect(pickDefaultGuildChannelId({guildId: 'guild', channels: []})).toBeNull();
|
||||
});
|
||||
|
||||
it('picks the first channel by position, skipping categories and links', () => {
|
||||
expect(pickDefaultGuildChannelId({guildId: 'guild', channels})).toBe('rules');
|
||||
});
|
||||
|
||||
it('keeps the remembered channel for the guild', () => {
|
||||
expect(pickDefaultGuildChannelId({guildId: 'guild', channels, selectedChannelId: 'general'})).toBe('general');
|
||||
});
|
||||
|
||||
it('falls back when the remembered channel is gone', () => {
|
||||
expect(pickDefaultGuildChannelId({guildId: 'guild', channels, selectedChannelId: 'deleted'})).toBe('rules');
|
||||
});
|
||||
|
||||
it('falls back when the remembered channel is not navigable', () => {
|
||||
expect(pickDefaultGuildChannelId({guildId: 'guild', channels, selectedChannelId: 'link'})).toBe('rules');
|
||||
expect(pickDefaultGuildChannelId({guildId: 'guild', channels, selectedChannelId: 'category'})).toBe('rules');
|
||||
});
|
||||
|
||||
it('falls back when the remembered channel belongs to another guild', () => {
|
||||
const withForeign = [...channels, {id: 'other', type: ChannelTypes.GUILD_TEXT, position: 0, guildId: 'other'}];
|
||||
expect(pickDefaultGuildChannelId({guildId: 'guild', channels: withForeign, selectedChannelId: 'other'})).toBe(
|
||||
'rules',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,28 +20,15 @@ export function pickDefaultGuildChannelId({
|
||||
guildId,
|
||||
channels,
|
||||
selectedChannelId,
|
||||
systemChannelId,
|
||||
rulesChannelId,
|
||||
}: {
|
||||
guildId: string;
|
||||
channels: ReadonlyArray<MinimalChannel>;
|
||||
selectedChannelId?: string | null;
|
||||
systemChannelId?: string | null;
|
||||
rulesChannelId?: string | null;
|
||||
}): string | null {
|
||||
if (!channels.length) return null;
|
||||
const channelById = new Map(channels.map((channel) => [channel.id, channel]));
|
||||
const isChannelInGuild = (channelId?: string | null) =>
|
||||
channelId ? channelById.get(channelId)?.guildId === guildId : false;
|
||||
if (isChannelInGuild(selectedChannelId)) {
|
||||
return selectedChannelId!;
|
||||
const viewable = filterViewableChannels(channels.filter((channel) => channel.guildId === guildId));
|
||||
if (!viewable.length) return null;
|
||||
if (selectedChannelId && viewable.some((channel) => channel.id === selectedChannelId)) {
|
||||
return selectedChannelId;
|
||||
}
|
||||
if (isChannelInGuild(systemChannelId)) {
|
||||
return systemChannelId!;
|
||||
}
|
||||
if (isChannelInGuild(rulesChannelId)) {
|
||||
return rulesChannelId!;
|
||||
}
|
||||
const viewable = [...filterViewableChannels(channels)].sort(compareChannelPosition);
|
||||
return viewable[0]?.id ?? null;
|
||||
return viewable.sort(compareChannelPosition)[0].id;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user