feat: Implement forum feed layout and refactor space navigation logic to support new routing structure
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { useMatch, useParams } from 'react-router-dom';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { getCanonicalAliasRoomId, isRoomAlias } from '../../utils/matrix';
|
||||
import { useMatrixClient } from '../useMatrixClient';
|
||||
import { getSpaceLobbyPath, getSpaceSearchPath } from '../../pages/pathUtils';
|
||||
|
||||
export const useSelectedSpace = (): string | undefined => {
|
||||
const mx = useMatrixClient();
|
||||
@@ -16,22 +15,18 @@ export const useSelectedSpace = (): string | undefined => {
|
||||
return spaceId;
|
||||
};
|
||||
|
||||
export const useSpaceLobbySelected = (spaceIdOrAlias: string): boolean => {
|
||||
const match = useMatch({
|
||||
path: getSpaceLobbyPath(spaceIdOrAlias),
|
||||
caseSensitive: true,
|
||||
end: false,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
/** True when the current URL is the forum feed route. */
|
||||
export const useSpaceFeedSelected = (): boolean => {
|
||||
const { pathname } = useLocation();
|
||||
return /\/feed\/?$/.test(pathname);
|
||||
};
|
||||
|
||||
export const useSpaceSearchSelected = (spaceIdOrAlias: string): boolean => {
|
||||
const match = useMatch({
|
||||
path: getSpaceSearchPath(spaceIdOrAlias),
|
||||
caseSensitive: true,
|
||||
end: false,
|
||||
});
|
||||
|
||||
return !!match;
|
||||
export const useSpaceLobbySelected = (_spaceIdOrAlias?: string): boolean => {
|
||||
const { pathname } = useLocation();
|
||||
return /\/lobby\/?$/.test(pathname);
|
||||
};
|
||||
|
||||
export const useSpaceSearchSelected = (_spaceIdOrAlias?: string): boolean => {
|
||||
const { pathname } = useLocation();
|
||||
return /\/search\/?$/.test(pathname);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { MatrixClient, Room, RoomEvent } from 'matrix-js-sdk';
|
||||
import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
RoomNotificationMode,
|
||||
} from './useRoomsNotificationPreferences';
|
||||
import { updateJoiningProgressAtom } from '../state/joiningProgress';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
|
||||
const PER_PAGE_COUNT = 100;
|
||||
const MAX_PAGES = 50;
|
||||
@@ -18,6 +19,8 @@ const JOIN_DELAY_MS = 500;
|
||||
/** Join rules that allow automatic joining */
|
||||
const AUTO_JOINABLE_RULES = ['public', 'restricted', 'knock_restricted'];
|
||||
|
||||
const processingSpaces = new Set<string>();
|
||||
|
||||
/**
|
||||
* Extracts the server name from a Matrix room ID
|
||||
* @param roomId - The room ID (e.g., "!abc123:matrix.org")
|
||||
@@ -46,12 +49,12 @@ function canAutoJoin(hierarchyRoom: IHierarchyRoom): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a hierarchy item is a space
|
||||
* Checks if a hierarchy item is a space (including m.forum category spaces)
|
||||
* @param hierarchyRoom - The room info from the space hierarchy
|
||||
* @returns Whether the item is a space
|
||||
*/
|
||||
function isHierarchySpace(hierarchyRoom: IHierarchyRoom): boolean {
|
||||
return hierarchyRoom.room_type === 'm.space';
|
||||
return hierarchyRoom.room_type === 'm.space' || hierarchyRoom.room_type === 'm.forum';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,6 +180,29 @@ async function joinRoomsSequentially(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins all auto-joinable children of a space (and nested spaces/rooms).
|
||||
*/
|
||||
export async function autoJoinSpaceChildren(
|
||||
mx: MatrixClient,
|
||||
spaceId: string,
|
||||
onProgress?: ProgressCallback
|
||||
): Promise<void> {
|
||||
if (processingSpaces.has(spaceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
processingSpaces.add(spaceId);
|
||||
|
||||
try {
|
||||
const hierarchyRooms = await fetchSpaceHierarchy(mx, spaceId);
|
||||
const childRooms = hierarchyRooms.filter((r) => r.room_id !== spaceId);
|
||||
await joinRoomsSequentially(mx, childRooms, spaceId, onProgress);
|
||||
} finally {
|
||||
processingSpaces.delete(spaceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that automatically joins all accessible rooms when joining a space
|
||||
* @param mx - Matrix client instance
|
||||
@@ -184,7 +210,6 @@ async function joinRoomsSequentially(
|
||||
export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const setJoiningProgress = useSetAtom(updateJoiningProgressAtom);
|
||||
const processingSpaces = useRef<Set<string>>(new Set());
|
||||
|
||||
const handleSpaceJoin = useCallback(
|
||||
async (room: Room) => {
|
||||
@@ -192,7 +217,7 @@ export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process spaces
|
||||
// Only process spaces (includes m.forum containers)
|
||||
if (!isSpace(room)) {
|
||||
return;
|
||||
}
|
||||
@@ -204,27 +229,11 @@ export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
|
||||
|
||||
const spaceId = room.roomId;
|
||||
|
||||
// Prevent duplicate processing
|
||||
if (processingSpaces.current.has(spaceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
processingSpaces.current.add(spaceId);
|
||||
|
||||
try {
|
||||
// Fetch the space hierarchy
|
||||
const hierarchyRooms = await fetchSpaceHierarchy(mx, spaceId);
|
||||
|
||||
// Filter out the space itself
|
||||
const childRooms = hierarchyRooms.filter((r) => r.room_id !== spaceId);
|
||||
|
||||
// Join all accessible rooms with progress tracking
|
||||
await joinRoomsSequentially(mx, childRooms, spaceId, (current, total) => {
|
||||
await autoJoinSpaceChildren(mx, spaceId, (current, total) => {
|
||||
setJoiningProgress({ spaceId, progress: { current, total } });
|
||||
});
|
||||
} finally {
|
||||
processingSpaces.current.delete(spaceId);
|
||||
// Clear progress when done
|
||||
setJoiningProgress({ spaceId, progress: null });
|
||||
}
|
||||
},
|
||||
@@ -245,3 +254,45 @@ export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
|
||||
};
|
||||
}, [mx, handleSpaceJoin]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-joins space children when visiting an already-joined space (e.g. forum spaces).
|
||||
*/
|
||||
export function useAutoJoinOnSpaceVisit(space: Room | undefined): void {
|
||||
const mx = useMatrixClient();
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const setJoiningProgress = useSetAtom(updateJoiningProgressAtom);
|
||||
|
||||
useEffect(() => {
|
||||
if (!space || !settings.autoJoinSpaceRooms) {
|
||||
return;
|
||||
}
|
||||
if (!isSpace(space)) {
|
||||
return;
|
||||
}
|
||||
if (space.getMyMembership() !== Membership.Join) {
|
||||
return;
|
||||
}
|
||||
|
||||
const spaceId = space.roomId;
|
||||
let cancelled = false;
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
await autoJoinSpaceChildren(mx, spaceId, (current, total) => {
|
||||
if (!cancelled) {
|
||||
setJoiningProgress({ spaceId, progress: { current, total } });
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setJoiningProgress({ spaceId, progress: null });
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [space, settings.autoJoinSpaceRooms, mx, setJoiningProgress]);
|
||||
}
|
||||
Reference in New Issue
Block a user