vojo/src/app/utils/routeParent.ts

98 lines
3.6 KiB
TypeScript

import { matchPath } from 'react-router-dom';
import {
BOTS_PATH,
CHANNELS_PATH,
CHANNELS_ROOM_EVENT_PATH,
CHANNELS_ROOM_PATH,
CHANNELS_SPACE_PATH,
CHANNELS_THREAD_PATH,
DIRECT_PATH,
EXPLORE_PATH,
HOME_PATH,
SPACE_PATH,
} from '../pages/paths';
import {
getBotsPath,
getChannelsPath,
getChannelsRoomPath,
getChannelsSpacePath,
getDirectPath,
getExplorePath,
getHomePath,
getSpacePath,
} from '../pages/pathUtils';
/**
* Returns the section-root parent path for the given pathname, or null
* when pathname is not under a known section or is already at the root
* of its section. Shared by the UI back arrow (BackRouteHandler) and the
* Android hardware back button (useAndroidBackButton) so they can't drift.
*/
export const getRouteSectionParent = (pathname: string): string | null => {
const atRoot = (path: string) =>
matchPath({ path, caseSensitive: true, end: true }, pathname) !== null;
const under = (path: string) =>
matchPath({ path, caseSensitive: true, end: false }, pathname) !== null;
if (under(HOME_PATH)) return atRoot(HOME_PATH) ? null : getHomePath();
if (under(DIRECT_PATH)) return atRoot(DIRECT_PATH) ? null : getDirectPath();
if (under(BOTS_PATH)) return atRoot(BOTS_PATH) ? null : getBotsPath();
if (under(CHANNELS_PATH)) {
// Thread URL collapses to its parent room view (drawer closed) — must
// match before the room-level matcher because thread is a strict
// subpath of room (`/channels/:s/:r/thread/:rootId/`).
const threadMatch = matchPath(
{ path: CHANNELS_THREAD_PATH, caseSensitive: true, end: false },
pathname
);
if (threadMatch?.params.spaceIdOrAlias && threadMatch?.params.roomIdOrAlias) {
return getChannelsRoomPath(
decodeURIComponent(threadMatch.params.spaceIdOrAlias),
decodeURIComponent(threadMatch.params.roomIdOrAlias)
);
}
// Event-anchored permalink collapses to the same parent room view as
// thread does — so back from a search/notification permalink lands on
// the room timeline first, not on the space root.
const eventMatch = matchPath(
{ path: CHANNELS_ROOM_EVENT_PATH, caseSensitive: true, end: false },
pathname
);
if (eventMatch?.params.spaceIdOrAlias && eventMatch?.params.roomIdOrAlias) {
return getChannelsRoomPath(
decodeURIComponent(eventMatch.params.spaceIdOrAlias),
decodeURIComponent(eventMatch.params.roomIdOrAlias)
);
}
const roomMatch = matchPath(
{ path: CHANNELS_ROOM_PATH, caseSensitive: true, end: false },
pathname
);
const roomSpace = roomMatch?.params.spaceIdOrAlias;
if (roomSpace) {
return getChannelsSpacePath(decodeURIComponent(roomSpace));
}
const spaceMatch = matchPath(
{ path: CHANNELS_SPACE_PATH, caseSensitive: true, end: false },
pathname
);
const channelsSpace = spaceMatch?.params.spaceIdOrAlias;
if (channelsSpace) {
const channelsSpacePath = getChannelsSpacePath(decodeURIComponent(channelsSpace));
return pathname === channelsSpacePath ? getChannelsPath() : channelsSpacePath;
}
return atRoot(CHANNELS_PATH) ? null : getChannelsPath();
}
const spaceMatch = matchPath({ path: SPACE_PATH, caseSensitive: true, end: false }, pathname);
const encodedSpaceIdOrAlias = spaceMatch?.params.spaceIdOrAlias;
if (encodedSpaceIdOrAlias) {
const spacePath = getSpacePath(decodeURIComponent(encodedSpaceIdOrAlias));
return pathname === spacePath ? null : spacePath;
}
if (under(EXPLORE_PATH)) return atRoot(EXPLORE_PATH) ? null : getExplorePath();
return null;
};