Widen the media panel, support skinny full-page mode, keep jump-to-latest and return-to-previous reliable, and stop same-room event jumps from remounting the outlet or yanking the sidebar.
221 lines
6.8 KiB
TypeScript
221 lines
6.8 KiB
TypeScript
import { generatePath, Path } from 'react-router-dom';
|
|
import {
|
|
DIRECT_CREATE_PATH,
|
|
DIRECT_PATH,
|
|
DIRECT_ROOM_PATH,
|
|
DIRECT_SEARCH_PATH,
|
|
DIRECT_NOTIFICATIONS_PATH,
|
|
DIRECT_INVITES_PATH,
|
|
EXPLORE_FEATURED_PATH,
|
|
EXPLORE_PATH,
|
|
EXPLORE_SERVER_PATH,
|
|
HOME_CREATE_PATH,
|
|
HOME_JOIN_PATH,
|
|
HOME_PATH,
|
|
HOME_ROOM_PATH,
|
|
HOME_SEARCH_PATH,
|
|
LOGIN_PATH,
|
|
INBOX_INVITES_PATH,
|
|
INBOX_NOTIFICATIONS_PATH,
|
|
INBOX_PATH,
|
|
REGISTER_PATH,
|
|
RESET_PASSWORD_PATH,
|
|
ROOT_PATH,
|
|
SPACE_FEED_PATH,
|
|
SPACE_LOBBY_PATH,
|
|
SPACE_PATH,
|
|
SPACE_ROOM_PATH,
|
|
SPACE_SEARCH_PATH,
|
|
CREATE_PATH,
|
|
} from './paths';
|
|
import { trimLeadingSlash, trimTrailingSlash } from '../utils/common';
|
|
import { tryDecodeURIComponent } from '../utils/dom';
|
|
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 withSearchParam = <T extends Record<string, string>>(
|
|
path: string,
|
|
searchParam: T
|
|
): string => {
|
|
const params = new URLSearchParams(searchParam);
|
|
|
|
return `${path}?${params}`;
|
|
};
|
|
export const encodeSearchParamValueArray = (ids: string[]): string => ids.join(',');
|
|
export const decodeSearchParamValueArray = (idsParam: string): string[] => idsParam.split(',');
|
|
|
|
/** Decode route params (React Router 7 encodes in generatePath — do not pre-encode). */
|
|
export const decodeRouteParam = (param: string | undefined): string | undefined => {
|
|
if (!param) return param;
|
|
let decoded = param;
|
|
let next = tryDecodeURIComponent(decoded);
|
|
while (next !== decoded) {
|
|
decoded = next;
|
|
next = tryDecodeURIComponent(decoded);
|
|
}
|
|
return decoded;
|
|
};
|
|
|
|
export const getOriginBaseUrl = (hashRouterConfig?: HashRouterConfig): string => {
|
|
const baseUrl = `${trimTrailingSlash(window.location.origin)}${import.meta.env.BASE_URL}`;
|
|
|
|
if (hashRouterConfig?.enabled) {
|
|
return `${trimTrailingSlash(baseUrl)}/#${hashRouterConfig.basename}`;
|
|
}
|
|
|
|
return baseUrl;
|
|
};
|
|
|
|
export const withOriginBaseUrl = (baseUrl: string, path: string): string =>
|
|
`${trimTrailingSlash(baseUrl)}${path}`;
|
|
|
|
export const getAppPathFromHref = (baseUrl: string, href: string): string => {
|
|
// if hash is in baseUrl means we are using hashRouter
|
|
const baseHashIndex = baseUrl.indexOf('#');
|
|
if (baseHashIndex > -1) {
|
|
const hrefHashIndex = href.indexOf('#');
|
|
// href may/not have "/" around "#"
|
|
// we need to take care of this when extracting app path
|
|
const trimmedBaseUrl = trimLeadingSlash(baseUrl.slice(baseHashIndex + 1));
|
|
const trimmedHref = trimLeadingSlash(href.slice(hrefHashIndex + 1));
|
|
|
|
const appPath = trimmedHref.slice(trimmedBaseUrl.length);
|
|
return `/${trimLeadingSlash(appPath)}`;
|
|
}
|
|
|
|
return href.slice(trimTrailingSlash(baseUrl).length);
|
|
};
|
|
|
|
export const getRootPath = (): string => ROOT_PATH;
|
|
|
|
export const getLoginPath = (server?: string): string => {
|
|
const params = server ? { server } : undefined;
|
|
return generatePath(LOGIN_PATH, params);
|
|
};
|
|
|
|
export const getRegisterPath = (server?: string): string => {
|
|
const params = server ? { server } : undefined;
|
|
return generatePath(REGISTER_PATH, params);
|
|
};
|
|
|
|
export const getResetPasswordPath = (server?: string): string => {
|
|
const params = server ? { server } : undefined;
|
|
return generatePath(RESET_PASSWORD_PATH, params);
|
|
};
|
|
|
|
export const getHomePath = (): string => HOME_PATH;
|
|
export const getHomeCreatePath = (): string => HOME_CREATE_PATH;
|
|
export const getHomeJoinPath = (): string => HOME_JOIN_PATH;
|
|
export const getHomeSearchPath = (): string => HOME_SEARCH_PATH;
|
|
export const getHomeRoomPath = (roomIdOrAlias: string, eventId?: string): string => {
|
|
const params = {
|
|
roomIdOrAlias,
|
|
eventId: eventId ?? null,
|
|
};
|
|
|
|
return generatePath(HOME_ROOM_PATH, params);
|
|
};
|
|
|
|
export const getDirectPath = (): string => DIRECT_PATH;
|
|
export const getDirectCreatePath = (): string => DIRECT_CREATE_PATH;
|
|
export const getDirectSearchPath = (): string => DIRECT_SEARCH_PATH;
|
|
export const getDirectRoomPath = (roomIdOrAlias: string, eventId?: string): string => {
|
|
const params = {
|
|
roomIdOrAlias,
|
|
eventId: eventId ?? null,
|
|
};
|
|
|
|
return generatePath(DIRECT_ROOM_PATH, params);
|
|
};
|
|
export const getDirectNotificationsPath = (): string => DIRECT_NOTIFICATIONS_PATH;
|
|
export const getDirectInvitesPath = (): string => DIRECT_INVITES_PATH;
|
|
|
|
export const getSpacePath = (spaceIdOrAlias: string): string => {
|
|
const params = {
|
|
spaceIdOrAlias,
|
|
};
|
|
|
|
return generatePath(SPACE_PATH, params);
|
|
};
|
|
export const getSpaceLobbyPath = (spaceIdOrAlias: string): string => {
|
|
const params = {
|
|
spaceIdOrAlias,
|
|
};
|
|
return generatePath(SPACE_LOBBY_PATH, params);
|
|
};
|
|
export const getSpaceFeedPath = (spaceIdOrAlias: string): string => {
|
|
const params = {
|
|
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 => {
|
|
const params = {
|
|
spaceIdOrAlias,
|
|
};
|
|
return generatePath(SPACE_SEARCH_PATH, params);
|
|
};
|
|
export const getSpaceRoomPath = (
|
|
spaceIdOrAlias: string,
|
|
roomIdOrAlias: string,
|
|
eventId?: string
|
|
): string => {
|
|
const params = {
|
|
spaceIdOrAlias,
|
|
roomIdOrAlias,
|
|
eventId: eventId ?? null,
|
|
};
|
|
|
|
return generatePath(SPACE_ROOM_PATH, params);
|
|
};
|
|
|
|
/**
|
|
* Keep the current room URL (home / direct / space) and only add, replace, or clear
|
|
* the optional `$eventId` segment. Used so same-room jumps (media, permalinks) don't
|
|
* re-home the room into a different sidebar section.
|
|
*/
|
|
export const withRoomEventId = (pathname: string, eventId?: string): string => {
|
|
const parts = pathname.split('/').filter(Boolean);
|
|
|
|
if (parts.length > 0) {
|
|
const lastDecoded = decodeRouteParam(parts[parts.length - 1]);
|
|
if (lastDecoded?.startsWith('$')) {
|
|
parts.pop();
|
|
}
|
|
}
|
|
|
|
if (eventId) {
|
|
parts.push(encodeURIComponent(eventId));
|
|
}
|
|
|
|
return `/${parts.join('/')}/`;
|
|
};
|
|
|
|
export const getExplorePath = (): string => EXPLORE_PATH;
|
|
export const getExploreFeaturedPath = (): string => EXPLORE_FEATURED_PATH;
|
|
export const getExploreServerPath = (server: string): string => {
|
|
const params = {
|
|
server,
|
|
};
|
|
return generatePath(EXPLORE_SERVER_PATH, params);
|
|
};
|
|
|
|
export const getCreatePath = (): string => CREATE_PATH;
|
|
|
|
export const getInboxPath = (): string => INBOX_PATH;
|
|
export const getInboxNotificationsPath = (): string => INBOX_NOTIFICATIONS_PATH;
|
|
export const getInboxInvitesPath = (): string => INBOX_INVITES_PATH;
|