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; };