feat: Implement forum feed layout and refactor space navigation logic to support new routing structure
This commit is contained in:
@@ -1,15 +1,31 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { PageRoot } from '../../components/page';
|
import { PageRoot } from '../../components/page';
|
||||||
import { AnimatedOutlet } from '../../components/AnimatedOutlet';
|
import { AnimatedOutlet } from '../../components/AnimatedOutlet';
|
||||||
import { MobileFriendlyPageNav } from '../../pages/MobileFriendly';
|
import { MobileFriendlyPageNav } from '../../pages/MobileFriendly';
|
||||||
import { SPACE_PATH } from '../../pages/paths';
|
import { SPACE_PATH } from '../../pages/paths';
|
||||||
import { Space } from '../../pages/client/space/Space';
|
import { Space } from '../../pages/client/space/Space';
|
||||||
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
|
||||||
|
import { useSpace } from '../../hooks/useSpace';
|
||||||
|
import { isForumContainer, shouldShowForumLobby } from '../../utils/room';
|
||||||
|
import { ForumBoardProvider } from './ForumBoardContext';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Space sidebar + outlet. Forum spaces render ForumFeedSidebar in Space.tsx.
|
* Space sidebar + outlet. Forum spaces render ForumFeedSidebar in Space.tsx.
|
||||||
*/
|
*/
|
||||||
export function ForumAwareSpaceLayout() {
|
export function ForumAwareSpaceLayout() {
|
||||||
return (
|
const mx = useMatrixClient();
|
||||||
|
const space = useSpace();
|
||||||
|
const selectedRoomId = useSelectedRoom();
|
||||||
|
|
||||||
|
const scope = useMemo(() => {
|
||||||
|
const room = selectedRoomId ? mx.getRoom(selectedRoomId) : null;
|
||||||
|
const topicRoomId =
|
||||||
|
room && room.roomId !== space.roomId && !isForumContainer(room) ? room.roomId : undefined;
|
||||||
|
return { forumSpaceId: space.roomId, topicRoomId };
|
||||||
|
}, [mx, selectedRoomId, space.roomId]);
|
||||||
|
|
||||||
|
const layout = (
|
||||||
<PageRoot
|
<PageRoot
|
||||||
nav={
|
nav={
|
||||||
<MobileFriendlyPageNav path={SPACE_PATH}>
|
<MobileFriendlyPageNav path={SPACE_PATH}>
|
||||||
@@ -20,4 +36,14 @@ export function ForumAwareSpaceLayout() {
|
|||||||
<AnimatedOutlet />
|
<AnimatedOutlet />
|
||||||
</PageRoot>
|
</PageRoot>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!shouldShowForumLobby(space)) {
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ForumBoardProvider forumSpace={space} scope={scope}>
|
||||||
|
{layout}
|
||||||
|
</ForumBoardProvider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/app/features/forum/ForumFeedPage.tsx
Normal file
6
src/app/features/forum/ForumFeedPage.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { ForumBoardDetail } from './ForumBoardDetail';
|
||||||
|
|
||||||
|
/** Forum post feed in the main pane (sidebar holds filters + post list). */
|
||||||
|
export function ForumFeedPage() {
|
||||||
|
return <ForumBoardDetail />;
|
||||||
|
}
|
||||||
@@ -1,30 +1,8 @@
|
|||||||
import React, { ReactNode, useMemo } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
||||||
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
|
|
||||||
import { useSpace } from '../../hooks/useSpace';
|
|
||||||
import { isForumContainer, shouldShowForumLobby } from '../../utils/room';
|
|
||||||
import { ForumBoardProvider } from './ForumBoardContext';
|
|
||||||
|
|
||||||
/** Shares forum board state between space sidebar and lobby/room outlet. */
|
/**
|
||||||
|
* @deprecated Forum board context now lives in ForumAwareSpaceLayout.
|
||||||
|
*/
|
||||||
export function ForumSpaceLayout({ children }: { children: ReactNode }) {
|
export function ForumSpaceLayout({ children }: { children: ReactNode }) {
|
||||||
const mx = useMatrixClient();
|
return children;
|
||||||
const space = useSpace();
|
|
||||||
const selectedRoomId = useSelectedRoom();
|
|
||||||
|
|
||||||
const scope = useMemo(() => {
|
|
||||||
const room = selectedRoomId ? mx.getRoom(selectedRoomId) : null;
|
|
||||||
const topicRoomId =
|
|
||||||
room && room.roomId !== space.roomId && !isForumContainer(room) ? room.roomId : undefined;
|
|
||||||
return { forumSpaceId: space.roomId, topicRoomId };
|
|
||||||
}, [mx, selectedRoomId, space.roomId]);
|
|
||||||
|
|
||||||
if (!shouldShowForumLobby(space)) {
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ForumBoardProvider forumSpace={space} scope={scope}>
|
|
||||||
{children}
|
|
||||||
</ForumBoardProvider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export { ForumBoardDetail } from './ForumBoardDetail';
|
|||||||
export { ForumSortIcons } from './forumLucideIcons';
|
export { ForumSortIcons } from './forumLucideIcons';
|
||||||
export { ForumSpaceShell } from './ForumSpaceShell';
|
export { ForumSpaceShell } from './ForumSpaceShell';
|
||||||
export { ForumThreadDetail } from './ForumThreadDetail';
|
export { ForumThreadDetail } from './ForumThreadDetail';
|
||||||
|
export { ForumFeedPage } from './ForumFeedPage';
|
||||||
export { ForumAwareSpaceLayout } from './ForumAwareSpaceLayout';
|
export { ForumAwareSpaceLayout } from './ForumAwareSpaceLayout';
|
||||||
export { ForumSpaceLayout } from './ForumSpaceLayout';
|
export { ForumSpaceLayout } from './ForumSpaceLayout';
|
||||||
export type { ForumBoardScope } from './useForumBoard';
|
export type { ForumBoardScope } from './useForumBoard';
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ import { getSpaceRoomPath } from '../../pages/pathUtils';
|
|||||||
import { StateEvent } from '../../../types/matrix/room';
|
import { StateEvent } from '../../../types/matrix/room';
|
||||||
import { CanDropCallback, useDnDMonitor } from './DnD';
|
import { CanDropCallback, useDnDMonitor } from './DnD';
|
||||||
import { ASCIILexicalTable, orderKeys } from '../../utils/ASCIILexicalTable';
|
import { ASCIILexicalTable, orderKeys } from '../../utils/ASCIILexicalTable';
|
||||||
import { getStateEvent, shouldShowForumLobby } from '../../utils/room';
|
import { getStateEvent } from '../../utils/room';
|
||||||
import { ForumBoardDetail } from '../forum/ForumBoardDetail';
|
|
||||||
import { useClosedLobbyCategoriesAtom } from '../../state/hooks/closedLobbyCategories';
|
import { useClosedLobbyCategoriesAtom } from '../../state/hooks/closedLobbyCategories';
|
||||||
import {
|
import {
|
||||||
makeCinnySpacesContent,
|
makeCinnySpacesContent,
|
||||||
@@ -151,15 +150,7 @@ const useCanDropLobbyItem = (
|
|||||||
return canDrop;
|
return canDrop;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ForumSpaceLobby() {
|
|
||||||
return <ForumBoardDetail />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Lobby() {
|
export function Lobby() {
|
||||||
const space = useSpace();
|
|
||||||
if (shouldShowForumLobby(space)) {
|
|
||||||
return <ForumSpaceLobby />;
|
|
||||||
}
|
|
||||||
return <SpaceCardLobby />;
|
return <SpaceCardLobby />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,12 @@ import { AddExistingModal } from '../add-existing';
|
|||||||
import { SpeechBubble } from '../../components/speech-bubble/SpeechBubble';
|
import { SpeechBubble } from '../../components/speech-bubble/SpeechBubble';
|
||||||
import { SpaceOptionsMenu, type OpenAddExistingOptions } from '../space/SpaceOptionsMenu';
|
import { SpaceOptionsMenu, type OpenAddExistingOptions } from '../space/SpaceOptionsMenu';
|
||||||
import type { HierarchyItemSpace } from '../../hooks/useSpaceHierarchy';
|
import type { HierarchyItemSpace } from '../../hooks/useSpaceHierarchy';
|
||||||
import { StateEvent } from '../../../types/matrix/room';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { getCanonicalAliasOrRoomId } from '../../utils/matrix';
|
||||||
|
import { getSpaceFeedPath } from '../../pages/pathUtils';
|
||||||
|
import { shouldShowForumLobby } from '../../utils/room';
|
||||||
import { useForumBoardContextOptionally } from '../forum/ForumBoardContext';
|
import { useForumBoardContextOptionally } from '../forum/ForumBoardContext';
|
||||||
|
import { StateEvent } from '../../../types/matrix/room';
|
||||||
|
|
||||||
type LobbyMenuProps = {
|
type LobbyMenuProps = {
|
||||||
powerLevels: IPowerLevels;
|
powerLevels: IPowerLevels;
|
||||||
@@ -37,7 +41,9 @@ type LobbyMenuProps = {
|
|||||||
const LobbyMenu = forwardRef<HTMLDivElement, LobbyMenuProps>(
|
const LobbyMenu = forwardRef<HTMLDivElement, LobbyMenuProps>(
|
||||||
({ powerLevels, requestClose }, ref) => {
|
({ powerLevels, requestClose }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const navigate = useNavigate();
|
||||||
const space = useSpace();
|
const space = useSpace();
|
||||||
|
const isForum = shouldShowForumLobby(space);
|
||||||
const creators = useRoomCreators(space);
|
const creators = useRoomCreators(space);
|
||||||
|
|
||||||
const permissions = useRoomPermissions(creators, powerLevels);
|
const permissions = useRoomPermissions(creators, powerLevels);
|
||||||
@@ -55,6 +61,11 @@ const LobbyMenu = forwardRef<HTMLDivElement, LobbyMenuProps>(
|
|||||||
requestClose();
|
requestClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenFeed = () => {
|
||||||
|
navigate(getSpaceFeedPath(getCanonicalAliasOrRoomId(mx, space.roomId)));
|
||||||
|
requestClose();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
|
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
|
||||||
{invitePrompt && (
|
{invitePrompt && (
|
||||||
@@ -67,6 +78,18 @@ const LobbyMenu = forwardRef<HTMLDivElement, LobbyMenuProps>(
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||||
|
{isForum && (
|
||||||
|
<MenuItem
|
||||||
|
onClick={handleOpenFeed}
|
||||||
|
size="300"
|
||||||
|
after={<Icon size="100" src={Icons.Thread} />}
|
||||||
|
radii="300"
|
||||||
|
>
|
||||||
|
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||||
|
Feed
|
||||||
|
</Text>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={handleInvite}
|
onClick={handleInvite}
|
||||||
variant="Primary"
|
variant="Primary"
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ import { copyToClipboard } from '../../utils/dom';
|
|||||||
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
|
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
|
||||||
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
||||||
import { getViaServers } from '../../plugins/via-servers';
|
import { getViaServers } from '../../plugins/via-servers';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { getSpaceFeedPath, getSpaceLobbyPath } from '../../pages/pathUtils';
|
||||||
|
import { shouldShowForumLobby } from '../../utils/room';
|
||||||
|
import { useSpaceFeedSelected } from '../../hooks/router/useSelectedSpace';
|
||||||
|
|
||||||
type ForumAddOptions = {
|
type ForumAddOptions = {
|
||||||
item: HierarchyItem;
|
item: HierarchyItem;
|
||||||
@@ -50,6 +54,9 @@ type SpaceOptionsMenuProps = {
|
|||||||
export const SpaceOptionsMenu = forwardRef<HTMLDivElement, SpaceOptionsMenuProps>(
|
export const SpaceOptionsMenu = forwardRef<HTMLDivElement, SpaceOptionsMenuProps>(
|
||||||
({ room, requestClose, forumAdd, onOpenAddExisting }, ref) => {
|
({ room, requestClose, forumAdd, onOpenAddExisting }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const onFeed = useSpaceFeedSelected();
|
||||||
|
const isForum = shouldShowForumLobby(room);
|
||||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||||
const [developerTools] = useSetting(settingsAtom, 'developerTools');
|
const [developerTools] = useSetting(settingsAtom, 'developerTools');
|
||||||
const roomToParents = useAtomValue(roomToParentsAtom);
|
const roomToParents = useAtomValue(roomToParentsAtom);
|
||||||
@@ -101,6 +108,16 @@ export const SpaceOptionsMenu = forwardRef<HTMLDivElement, SpaceOptionsMenuProps
|
|||||||
requestClose();
|
requestClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenFeed = () => {
|
||||||
|
navigate(getSpaceFeedPath(getCanonicalAliasOrRoomId(mx, room.roomId)));
|
||||||
|
requestClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenManage = () => {
|
||||||
|
navigate(getSpaceLobbyPath(getCanonicalAliasOrRoomId(mx, room.roomId)));
|
||||||
|
requestClose();
|
||||||
|
};
|
||||||
|
|
||||||
const handleAddTopic = () => {
|
const handleAddTopic = () => {
|
||||||
if (!forumAdd) return;
|
if (!forumAdd) return;
|
||||||
openCreateRoomModal(forumAdd.item.roomId);
|
openCreateRoomModal(forumAdd.item.roomId);
|
||||||
@@ -200,6 +217,30 @@ export const SpaceOptionsMenu = forwardRef<HTMLDivElement, SpaceOptionsMenuProps
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isForum && !onFeed && (
|
||||||
|
<MenuItem
|
||||||
|
onClick={handleOpenFeed}
|
||||||
|
size="300"
|
||||||
|
after={<Icon size="100" src={Icons.Thread} />}
|
||||||
|
radii="300"
|
||||||
|
>
|
||||||
|
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||||
|
Feed
|
||||||
|
</Text>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{isForum && onFeed && (
|
||||||
|
<MenuItem
|
||||||
|
onClick={handleOpenManage}
|
||||||
|
size="300"
|
||||||
|
after={<Icon size="100" src={Icons.Flag} />}
|
||||||
|
radii="300"
|
||||||
|
>
|
||||||
|
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||||
|
Manage
|
||||||
|
</Text>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={handleMarkAsRead}
|
onClick={handleMarkAsRead}
|
||||||
size="300"
|
size="300"
|
||||||
|
|||||||
@@ -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 { getCanonicalAliasRoomId, isRoomAlias } from '../../utils/matrix';
|
||||||
import { useMatrixClient } from '../useMatrixClient';
|
import { useMatrixClient } from '../useMatrixClient';
|
||||||
import { getSpaceLobbyPath, getSpaceSearchPath } from '../../pages/pathUtils';
|
|
||||||
|
|
||||||
export const useSelectedSpace = (): string | undefined => {
|
export const useSelectedSpace = (): string | undefined => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
@@ -16,22 +15,18 @@ export const useSelectedSpace = (): string | undefined => {
|
|||||||
return spaceId;
|
return spaceId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSpaceLobbySelected = (spaceIdOrAlias: string): boolean => {
|
/** True when the current URL is the forum feed route. */
|
||||||
const match = useMatch({
|
export const useSpaceFeedSelected = (): boolean => {
|
||||||
path: getSpaceLobbyPath(spaceIdOrAlias),
|
const { pathname } = useLocation();
|
||||||
caseSensitive: true,
|
return /\/feed\/?$/.test(pathname);
|
||||||
end: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return !!match;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSpaceSearchSelected = (spaceIdOrAlias: string): boolean => {
|
export const useSpaceLobbySelected = (_spaceIdOrAlias?: string): boolean => {
|
||||||
const match = useMatch({
|
const { pathname } = useLocation();
|
||||||
path: getSpaceSearchPath(spaceIdOrAlias),
|
return /\/lobby\/?$/.test(pathname);
|
||||||
caseSensitive: true,
|
};
|
||||||
end: false,
|
|
||||||
});
|
export const useSpaceSearchSelected = (_spaceIdOrAlias?: string): boolean => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
return !!match;
|
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 { MatrixClient, Room, RoomEvent } from 'matrix-js-sdk';
|
||||||
import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces';
|
import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
RoomNotificationMode,
|
RoomNotificationMode,
|
||||||
} from './useRoomsNotificationPreferences';
|
} from './useRoomsNotificationPreferences';
|
||||||
import { updateJoiningProgressAtom } from '../state/joiningProgress';
|
import { updateJoiningProgressAtom } from '../state/joiningProgress';
|
||||||
|
import { useMatrixClient } from './useMatrixClient';
|
||||||
|
|
||||||
const PER_PAGE_COUNT = 100;
|
const PER_PAGE_COUNT = 100;
|
||||||
const MAX_PAGES = 50;
|
const MAX_PAGES = 50;
|
||||||
@@ -18,6 +19,8 @@ const JOIN_DELAY_MS = 500;
|
|||||||
/** Join rules that allow automatic joining */
|
/** Join rules that allow automatic joining */
|
||||||
const AUTO_JOINABLE_RULES = ['public', 'restricted', 'knock_restricted'];
|
const AUTO_JOINABLE_RULES = ['public', 'restricted', 'knock_restricted'];
|
||||||
|
|
||||||
|
const processingSpaces = new Set<string>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the server name from a Matrix room ID
|
* Extracts the server name from a Matrix room ID
|
||||||
* @param roomId - The room ID (e.g., "!abc123:matrix.org")
|
* @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
|
* @param hierarchyRoom - The room info from the space hierarchy
|
||||||
* @returns Whether the item is a space
|
* @returns Whether the item is a space
|
||||||
*/
|
*/
|
||||||
function isHierarchySpace(hierarchyRoom: IHierarchyRoom): boolean {
|
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
|
* Hook that automatically joins all accessible rooms when joining a space
|
||||||
* @param mx - Matrix client instance
|
* @param mx - Matrix client instance
|
||||||
@@ -184,7 +210,6 @@ async function joinRoomsSequentially(
|
|||||||
export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
|
export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
|
||||||
const settings = useAtomValue(settingsAtom);
|
const settings = useAtomValue(settingsAtom);
|
||||||
const setJoiningProgress = useSetAtom(updateJoiningProgressAtom);
|
const setJoiningProgress = useSetAtom(updateJoiningProgressAtom);
|
||||||
const processingSpaces = useRef<Set<string>>(new Set());
|
|
||||||
|
|
||||||
const handleSpaceJoin = useCallback(
|
const handleSpaceJoin = useCallback(
|
||||||
async (room: Room) => {
|
async (room: Room) => {
|
||||||
@@ -192,7 +217,7 @@ export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only process spaces
|
// Only process spaces (includes m.forum containers)
|
||||||
if (!isSpace(room)) {
|
if (!isSpace(room)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -204,27 +229,11 @@ export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
|
|||||||
|
|
||||||
const spaceId = room.roomId;
|
const spaceId = room.roomId;
|
||||||
|
|
||||||
// Prevent duplicate processing
|
|
||||||
if (processingSpaces.current.has(spaceId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
processingSpaces.current.add(spaceId);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch the space hierarchy
|
await autoJoinSpaceChildren(mx, spaceId, (current, total) => {
|
||||||
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) => {
|
|
||||||
setJoiningProgress({ spaceId, progress: { current, total } });
|
setJoiningProgress({ spaceId, progress: { current, total } });
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
processingSpaces.current.delete(spaceId);
|
|
||||||
// Clear progress when done
|
|
||||||
setJoiningProgress({ spaceId, progress: null });
|
setJoiningProgress({ spaceId, progress: null });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -245,3 +254,45 @@ export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
|
|||||||
};
|
};
|
||||||
}, [mx, handleSpaceJoin]);
|
}, [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]);
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
_FEATURED_PATH,
|
_FEATURED_PATH,
|
||||||
_INVITES_PATH,
|
_INVITES_PATH,
|
||||||
_JOIN_PATH,
|
_JOIN_PATH,
|
||||||
|
_FEED_PATH,
|
||||||
_LOBBY_PATH,
|
_LOBBY_PATH,
|
||||||
_NOTIFICATIONS_PATH,
|
_NOTIFICATIONS_PATH,
|
||||||
_ROOM_PATH,
|
_ROOM_PATH,
|
||||||
@@ -37,14 +38,13 @@ import {
|
|||||||
getInboxNotificationsPath,
|
getInboxNotificationsPath,
|
||||||
getLoginPath,
|
getLoginPath,
|
||||||
getOriginBaseUrl,
|
getOriginBaseUrl,
|
||||||
getSpaceLobbyPath,
|
|
||||||
} from './pathUtils';
|
} from './pathUtils';
|
||||||
import { ClientBindAtoms, ClientLayout, ClientRoot } from './client';
|
import { ClientBindAtoms, ClientLayout, ClientRoot } from './client';
|
||||||
import { Home, HomeRouteRoomProvider, HomeSearch } from './client/home';
|
import { Home, HomeRouteRoomProvider, HomeSearch } from './client/home';
|
||||||
import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct';
|
import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct';
|
||||||
import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space';
|
import { RouteSpaceProvider, SpaceRouteRoomProvider, SpaceSearch, SpaceIndexRedirect } from './client/space';
|
||||||
import { ForumAwareSpaceLayout } from '../features/forum/ForumAwareSpaceLayout';
|
import { ForumAwareSpaceLayout } from '../features/forum/ForumAwareSpaceLayout';
|
||||||
import { ForumSpaceLayout } from '../features/forum/ForumSpaceLayout';
|
import { ForumFeedPage } from '../features/forum/ForumFeedPage';
|
||||||
import { Explore, FeaturedRooms, PublicRooms } from './client/explore';
|
import { Explore, FeaturedRooms, PublicRooms } from './client/explore';
|
||||||
import { Notifications, Inbox, Invites } from './client/inbox';
|
import { Notifications, Inbox, Invites } from './client/inbox';
|
||||||
import { setAfterLoginRedirectPath } from './afterLoginRedirectPath';
|
import { setAfterLoginRedirectPath } from './afterLoginRedirectPath';
|
||||||
@@ -238,25 +238,12 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||||||
path={SPACE_PATH}
|
path={SPACE_PATH}
|
||||||
element={
|
element={
|
||||||
<RouteSpaceProvider>
|
<RouteSpaceProvider>
|
||||||
<ForumSpaceLayout>
|
<ForumAwareSpaceLayout />
|
||||||
<ForumAwareSpaceLayout />
|
|
||||||
</ForumSpaceLayout>
|
|
||||||
</RouteSpaceProvider>
|
</RouteSpaceProvider>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{mobile ? null : (
|
<Route index element={<SpaceIndexRedirect />} />
|
||||||
<Route
|
<Route path={_FEED_PATH} element={<ForumFeedPage />} />
|
||||||
index
|
|
||||||
loader={({ params }) => {
|
|
||||||
const { spaceIdOrAlias } = params;
|
|
||||||
if (spaceIdOrAlias) {
|
|
||||||
return redirect(getSpaceLobbyPath(spaceIdOrAlias));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}}
|
|
||||||
element={<WelcomePage />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Route path={_LOBBY_PATH} element={<Lobby />} />
|
<Route path={_LOBBY_PATH} element={<Lobby />} />
|
||||||
<Route path={_SEARCH_PATH} element={<SpaceSearch />} />
|
<Route path={_SEARCH_PATH} element={<SpaceSearch />} />
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
|||||||
import { mDirectAtom } from '../../../state/mDirectList';
|
import { mDirectAtom } from '../../../state/mDirectList';
|
||||||
import { roomToParentsAtom } from '../../../state/room/roomToParents';
|
import { roomToParentsAtom } from '../../../state/room/roomToParents';
|
||||||
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
||||||
import { getHomePath, getSpaceLobbyPath, getSpacePath, joinPathComponent } from '../../pathUtils';
|
import { getHomePath, getSpaceDefaultPath, getSpacePath, joinPathComponent } from '../../pathUtils';
|
||||||
import {
|
import {
|
||||||
SidebarAvatar,
|
SidebarAvatar,
|
||||||
SidebarItem,
|
SidebarItem,
|
||||||
@@ -1067,7 +1067,7 @@ export function SpaceTabs({ scrollRef }: SpaceTabsProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate(getSpaceLobbyPath(getCanonicalAliasOrRoomId(mx, targetSpaceId)));
|
navigate(getSpaceDefaultPath(mx, targetSpaceId));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFolderToggle: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
const handleFolderToggle: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||||
@@ -1206,7 +1206,7 @@ export function HiddenSpacesTabs() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate(getSpaceLobbyPath(getCanonicalAliasOrRoomId(mx, targetSpaceId)));
|
navigate(getSpaceDefaultPath(mx, targetSpaceId));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHomeClick = () => {
|
const handleHomeClick = () => {
|
||||||
|
|||||||
@@ -335,7 +335,12 @@ export function Space() {
|
|||||||
const getToLink = (roomId: string) =>
|
const getToLink = (roomId: string) =>
|
||||||
getSpaceRoomPath(spaceIdOrAlias, getCanonicalAliasOrRoomId(mx, roomId));
|
getSpaceRoomPath(spaceIdOrAlias, getCanonicalAliasOrRoomId(mx, roomId));
|
||||||
|
|
||||||
if (shouldShowForumLobby(space)) {
|
const isForum = shouldShowForumLobby(space);
|
||||||
|
// Forum feed unless user opened manage, search, or a room (not tied to URL alias encoding).
|
||||||
|
const feedActive = isForum && !lobbySelected && !searchSelected && !selectedRoomId;
|
||||||
|
const showForumFeedSidebar = feedActive;
|
||||||
|
|
||||||
|
if (showForumFeedSidebar) {
|
||||||
return (
|
return (
|
||||||
<PageNav className={forumBoardCss.ForumPageNav}>
|
<PageNav className={forumBoardCss.ForumPageNav}>
|
||||||
<SpaceHeader />
|
<SpaceHeader />
|
||||||
@@ -346,6 +351,8 @@ export function Space() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const spacePath = getCanonicalAliasOrRoomId(mx, space.roomId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageNav>
|
<PageNav>
|
||||||
<SpaceHeader />
|
<SpaceHeader />
|
||||||
@@ -358,38 +365,42 @@ export function Space() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<NavCategory>
|
<NavCategory>
|
||||||
<NavItem variant="Background" radii="400" aria-selected={lobbySelected}>
|
{!isForum && (
|
||||||
<NavLink to={getSpaceLobbyPath(getCanonicalAliasOrRoomId(mx, space.roomId))}>
|
<>
|
||||||
<NavItemContent>
|
<NavItem variant="Background" radii="400" aria-selected={lobbySelected}>
|
||||||
<Box as="span" grow="Yes" alignItems="Center" gap="200">
|
<NavLink to={getSpaceLobbyPath(spacePath)}>
|
||||||
<Avatar size="200" radii="400">
|
<NavItemContent>
|
||||||
<Icon src={Icons.Flag} size="100" filled={lobbySelected} />
|
<Box as="span" grow="Yes" alignItems="Center" gap="200">
|
||||||
</Avatar>
|
<Avatar size="200" radii="400">
|
||||||
<Box as="span" grow="Yes">
|
<Icon src={Icons.Flag} size="100" filled={lobbySelected} />
|
||||||
<Text as="span" size="Inherit" truncate>
|
</Avatar>
|
||||||
Lobby
|
<Box as="span" grow="Yes">
|
||||||
</Text>
|
<Text as="span" size="Inherit" truncate>
|
||||||
</Box>
|
Lobby
|
||||||
</Box>
|
</Text>
|
||||||
</NavItemContent>
|
</Box>
|
||||||
</NavLink>
|
</Box>
|
||||||
</NavItem>
|
</NavItemContent>
|
||||||
<NavItem variant="Background" radii="400" aria-selected={searchSelected}>
|
</NavLink>
|
||||||
<NavLink to={getSpaceSearchPath(getCanonicalAliasOrRoomId(mx, space.roomId))}>
|
</NavItem>
|
||||||
<NavItemContent>
|
<NavItem variant="Background" radii="400" aria-selected={searchSelected}>
|
||||||
<Box as="span" grow="Yes" alignItems="Center" gap="200">
|
<NavLink to={getSpaceSearchPath(spacePath)}>
|
||||||
<Avatar size="200" radii="400">
|
<NavItemContent>
|
||||||
<Icon src={Icons.Search} size="100" filled={searchSelected} />
|
<Box as="span" grow="Yes" alignItems="Center" gap="200">
|
||||||
</Avatar>
|
<Avatar size="200" radii="400">
|
||||||
<Box as="span" grow="Yes">
|
<Icon src={Icons.Search} size="100" filled={searchSelected} />
|
||||||
<Text as="span" size="Inherit" truncate>
|
</Avatar>
|
||||||
Message Search
|
<Box as="span" grow="Yes">
|
||||||
</Text>
|
<Text as="span" size="Inherit" truncate>
|
||||||
</Box>
|
Message Search
|
||||||
</Box>
|
</Text>
|
||||||
</NavItemContent>
|
</Box>
|
||||||
</NavLink>
|
</Box>
|
||||||
</NavItem>
|
</NavItemContent>
|
||||||
|
</NavLink>
|
||||||
|
</NavItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<PluginNavSlot location="channel-list" />
|
<PluginNavSlot location="channel-list" />
|
||||||
</NavCategory>
|
</NavCategory>
|
||||||
<NavCategory
|
<NavCategory
|
||||||
|
|||||||
26
src/app/pages/client/space/SpaceIndexRedirect.tsx
Normal file
26
src/app/pages/client/space/SpaceIndexRedirect.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
|
import { useSpace } from '../../../hooks/useSpace';
|
||||||
|
import { shouldShowForumLobby } from '../../../utils/room';
|
||||||
|
import { getSpaceDefaultPath } from '../../pathUtils';
|
||||||
|
import { ForumFeedPage } from '../../../features/forum/ForumFeedPage';
|
||||||
|
|
||||||
|
/** Sends /:spaceId/ to the forum feed or space lobby. */
|
||||||
|
export function SpaceIndexRedirect() {
|
||||||
|
const mx = useMatrixClient();
|
||||||
|
const space = useSpace();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const isForum = shouldShowForumLobby(space);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
navigate(getSpaceDefaultPath(mx, space.roomId), { replace: true });
|
||||||
|
}, [mx, navigate, space.roomId]);
|
||||||
|
|
||||||
|
// Avoid a blank main pane while redirecting forum spaces to /feed/.
|
||||||
|
if (isForum) {
|
||||||
|
return <ForumFeedPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
|
import { useAutoJoinOnSpaceVisit } from '../../../hooks/useAutoJoinSpaceRooms';
|
||||||
import { useSpaces } from '../../../state/hooks/roomList';
|
import { useSpaces } from '../../../state/hooks/roomList';
|
||||||
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
||||||
import { useSelectedSpace } from '../../../hooks/router/useSelectedSpace';
|
import { useSelectedSpace } from '../../../hooks/router/useSelectedSpace';
|
||||||
@@ -21,6 +22,8 @@ export function RouteSpaceProvider({ children }: RouteSpaceProviderProps) {
|
|||||||
const selectedSpaceId = useSelectedSpace();
|
const selectedSpaceId = useSelectedSpace();
|
||||||
const space = mx.getRoom(selectedSpaceId);
|
const space = mx.getRoom(selectedSpaceId);
|
||||||
|
|
||||||
|
useAutoJoinOnSpaceVisit(space ?? undefined);
|
||||||
|
|
||||||
if (!space || !joinedSpaces.includes(space.roomId)) {
|
if (!space || !joinedSpaces.includes(space.roomId)) {
|
||||||
return <JoinBeforeNavigate roomIdOrAlias={spaceIdOrAlias ?? ''} viaServers={viaServers} />;
|
return <JoinBeforeNavigate roomIdOrAlias={spaceIdOrAlias ?? ''} viaServers={viaServers} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from './SpaceIndexRedirect';
|
||||||
export * from './SpaceProvider';
|
export * from './SpaceProvider';
|
||||||
export * from './Space';
|
export * from './Space';
|
||||||
export * from './Search';
|
export * from './Search';
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
REGISTER_PATH,
|
REGISTER_PATH,
|
||||||
RESET_PASSWORD_PATH,
|
RESET_PASSWORD_PATH,
|
||||||
ROOT_PATH,
|
ROOT_PATH,
|
||||||
|
SPACE_FEED_PATH,
|
||||||
SPACE_LOBBY_PATH,
|
SPACE_LOBBY_PATH,
|
||||||
SPACE_PATH,
|
SPACE_PATH,
|
||||||
SPACE_ROOM_PATH,
|
SPACE_ROOM_PATH,
|
||||||
@@ -28,6 +29,9 @@ import {
|
|||||||
} from './paths';
|
} from './paths';
|
||||||
import { trimLeadingSlash, trimTrailingSlash } from '../utils/common';
|
import { trimLeadingSlash, trimTrailingSlash } from '../utils/common';
|
||||||
import { HashRouterConfig } from '../hooks/useClientConfig';
|
import { HashRouterConfig } from '../hooks/useClientConfig';
|
||||||
|
import type { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
import { getCanonicalAliasOrRoomId } from '../utils/matrix';
|
||||||
|
import { shouldShowForumLobby } from '../utils/room';
|
||||||
|
|
||||||
export const joinPathComponent = (path: Path): string => path.pathname + path.search + path.hash;
|
export const joinPathComponent = (path: Path): string => path.pathname + path.search + path.hash;
|
||||||
|
|
||||||
@@ -128,6 +132,21 @@ export const getSpaceLobbyPath = (spaceIdOrAlias: string): string => {
|
|||||||
};
|
};
|
||||||
return generatePath(SPACE_LOBBY_PATH, params);
|
return generatePath(SPACE_LOBBY_PATH, params);
|
||||||
};
|
};
|
||||||
|
export const getSpaceFeedPath = (spaceIdOrAlias: string): string => {
|
||||||
|
const params = {
|
||||||
|
spaceIdOrAlias: encodeURIComponent(spaceIdOrAlias),
|
||||||
|
};
|
||||||
|
return generatePath(SPACE_FEED_PATH, params);
|
||||||
|
};
|
||||||
|
/** Forum spaces open the post feed; other spaces open the lobby. */
|
||||||
|
export const getSpaceDefaultPath = (mx: MatrixClient, spaceId: string): string => {
|
||||||
|
const spaceIdOrAlias = getCanonicalAliasOrRoomId(mx, spaceId);
|
||||||
|
const space = mx.getRoom(spaceId);
|
||||||
|
if (space && shouldShowForumLobby(space)) {
|
||||||
|
return getSpaceFeedPath(spaceIdOrAlias);
|
||||||
|
}
|
||||||
|
return getSpaceLobbyPath(spaceIdOrAlias);
|
||||||
|
};
|
||||||
export const getSpaceSearchPath = (spaceIdOrAlias: string): string => {
|
export const getSpaceSearchPath = (spaceIdOrAlias: string): string => {
|
||||||
const params = {
|
const params = {
|
||||||
spaceIdOrAlias: encodeURIComponent(spaceIdOrAlias),
|
spaceIdOrAlias: encodeURIComponent(spaceIdOrAlias),
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const RESET_PASSWORD_PATH = '/reset-password/:server?/';
|
|||||||
export const _CREATE_PATH = 'create/';
|
export const _CREATE_PATH = 'create/';
|
||||||
export const _JOIN_PATH = 'join/';
|
export const _JOIN_PATH = 'join/';
|
||||||
export const _LOBBY_PATH = 'lobby/';
|
export const _LOBBY_PATH = 'lobby/';
|
||||||
|
export const _FEED_PATH = 'feed/';
|
||||||
/**
|
/**
|
||||||
* array of rooms and senders mxId assigned
|
* array of rooms and senders mxId assigned
|
||||||
* to search param as string should be "," separated
|
* to search param as string should be "," separated
|
||||||
@@ -57,6 +58,7 @@ export const DIRECT_ROOM_PATH = `/direct/${_ROOM_PATH}`;
|
|||||||
|
|
||||||
export const SPACE_PATH = '/:spaceIdOrAlias/';
|
export const SPACE_PATH = '/:spaceIdOrAlias/';
|
||||||
export const SPACE_LOBBY_PATH = `/:spaceIdOrAlias/${_LOBBY_PATH}`;
|
export const SPACE_LOBBY_PATH = `/:spaceIdOrAlias/${_LOBBY_PATH}`;
|
||||||
|
export const SPACE_FEED_PATH = `/:spaceIdOrAlias/${_FEED_PATH}`;
|
||||||
export const SPACE_SEARCH_PATH = `/:spaceIdOrAlias/${_SEARCH_PATH}`;
|
export const SPACE_SEARCH_PATH = `/:spaceIdOrAlias/${_SEARCH_PATH}`;
|
||||||
export const SPACE_ROOM_PATH = `/:spaceIdOrAlias/${_ROOM_PATH}`;
|
export const SPACE_ROOM_PATH = `/:spaceIdOrAlias/${_ROOM_PATH}`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user