support matrix.to links (#1849)

* support room via server params and eventId

* change copy link to matrix.to links

* display matrix.to links in messages as pill and stop generating url previews for them

* improve editor mention to include viaServers and eventId

* fix mention custom attributes

* always try to open room in current space

* jump to latest remove target eventId from url

* add create direct search options to open/create dm with url
This commit is contained in:
Ajay Bura 2024-07-30 17:48:59 +05:30 committed by GitHub
parent 74dc76e22e
commit 5058136737
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 781 additions and 476 deletions

View file

@ -0,0 +1,33 @@
import React, { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { WelcomePage } from '../WelcomePage';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getDirectCreateSearchParams } from '../../pathSearchParam';
import { getDirectPath, getDirectRoomPath } from '../../pathUtils';
import { getDMRoomFor } from '../../../utils/matrix';
import { openInviteUser } from '../../../../client/action/navigation';
import { useDirectRooms } from './useDirectRooms';
export function DirectCreate() {
const mx = useMatrixClient();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { userId } = getDirectCreateSearchParams(searchParams);
const directs = useDirectRooms();
useEffect(() => {
if (userId) {
const room = getDMRoomFor(mx, userId);
const { roomId } = room ?? {};
if (roomId && directs.includes(roomId)) {
navigate(getDirectRoomPath(roomId), { replace: true });
} else {
openInviteUser(undefined, userId);
}
} else {
navigate(getDirectPath(), { replace: true });
}
}, [mx, navigate, directs, userId]);
return <WelcomePage />;
}

View file

@ -10,12 +10,12 @@ export function DirectRouteRoomProvider({ children }: { children: ReactNode }) {
const mx = useMatrixClient();
const rooms = useDirectRooms();
const { roomIdOrAlias } = useParams();
const { roomIdOrAlias, eventId } = useParams();
const roomId = useSelectedRoom();
const room = mx.getRoom(roomId);
if (!room || !rooms.includes(room.roomId)) {
return <JoinBeforeNavigate roomIdOrAlias={roomIdOrAlias!} />;
return <JoinBeforeNavigate roomIdOrAlias={roomIdOrAlias!} eventId={eventId} />;
}
return (

View file

@ -1,2 +1,3 @@
export * from './Direct';
export * from './RoomProvider';
export * from './DirectCreate';

View file

@ -5,17 +5,25 @@ import { RoomProvider } from '../../../hooks/useRoom';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { JoinBeforeNavigate } from '../../../features/join-before-navigate';
import { useHomeRooms } from './useHomeRooms';
import { useSearchParamsViaServers } from '../../../hooks/router/useSearchParamsViaServers';
export function HomeRouteRoomProvider({ children }: { children: ReactNode }) {
const mx = useMatrixClient();
const rooms = useHomeRooms();
const { roomIdOrAlias } = useParams();
const { roomIdOrAlias, eventId } = useParams();
const viaServers = useSearchParamsViaServers();
const roomId = useSelectedRoom();
const room = mx.getRoom(roomId);
if (!room || !rooms.includes(room.roomId)) {
return <JoinBeforeNavigate roomIdOrAlias={roomIdOrAlias!} />;
return (
<JoinBeforeNavigate
roomIdOrAlias={roomIdOrAlias!}
eventId={eventId}
viaServers={viaServers}
/>
);
}
return (

View file

@ -24,9 +24,10 @@ import {
} from 'matrix-js-sdk';
import { useVirtualizer } from '@tanstack/react-virtual';
import { HTMLReactParserOptions } from 'html-react-parser';
import { Opts as LinkifyOpts } from 'linkifyjs';
import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getMxIdLocalPart, isRoomId, isUserId } from '../../../utils/matrix';
import { getMxIdLocalPart } from '../../../utils/matrix';
import { InboxNotificationsPathSearchParams } from '../../paths';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { SequenceCard } from '../../../components/sequence-card';
@ -52,8 +53,13 @@ import {
Username,
} from '../../../components/message';
import colorMXID from '../../../../util/colorMXID';
import { getReactCustomHtmlParser } from '../../../plugins/react-custom-html-parser';
import { openJoinAlias, openProfileViewer } from '../../../../client/action/navigation';
import {
factoryRenderLinkifyWithMention,
getReactCustomHtmlParser,
LINKIFY_OPTS,
makeMentionCustomProps,
renderMatrixMention,
} from '../../../plugins/react-custom-html-parser';
import { RenderMessageContent } from '../../../components/RenderMessageContent';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
@ -70,6 +76,8 @@ import { ContainerColor } from '../../../styles/ContainerColor.css';
import { VirtualTile } from '../../../components/virtualizer';
import { UserAvatar } from '../../../components/user-avatar';
import { EncryptedContent } from '../../../features/room/message';
import { useMentionClickHandler } from '../../../hooks/useMentionClickHandler';
import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler';
type RoomNotificationsGroup = {
roomId: string;
@ -181,36 +189,26 @@ function RoomNotificationsGroupComp({
}: RoomNotificationsGroupProps) {
const mx = useMatrixClient();
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
const { navigateRoom, navigateSpace } = useRoomNavigate();
const mentionClickHandler = useMentionClickHandler(room.roomId);
const spoilerClickHandler = useSpoilerClickHandler();
const linkifyOpts = useMemo<LinkifyOpts>(
() => ({
...LINKIFY_OPTS,
render: factoryRenderLinkifyWithMention((href) =>
renderMatrixMention(mx, room.roomId, href, makeMentionCustomProps(mentionClickHandler))
),
}),
[mx, room, mentionClickHandler]
);
const htmlReactParserOptions = useMemo<HTMLReactParserOptions>(
() =>
getReactCustomHtmlParser(mx, room, {
handleSpoilerClick: (evt) => {
const target = evt.currentTarget;
if (target.getAttribute('aria-pressed') === 'true') {
evt.stopPropagation();
target.setAttribute('aria-pressed', 'false');
target.style.cursor = 'initial';
}
},
handleMentionClick: (evt) => {
const target = evt.currentTarget;
const mentionId = target.getAttribute('data-mention-id');
if (typeof mentionId !== 'string') return;
if (isUserId(mentionId)) {
openProfileViewer(mentionId, room.roomId);
return;
}
if (isRoomId(mentionId) && mx.getRoom(mentionId)) {
if (mx.getRoom(mentionId)?.isSpaceRoom()) navigateSpace(mentionId);
else navigateRoom(mentionId);
return;
}
openJoinAlias(mentionId);
},
getReactCustomHtmlParser(mx, room.roomId, {
linkifyOpts,
handleSpoilerClick: spoilerClickHandler,
handleMentionClick: mentionClickHandler,
}),
[mx, room, navigateRoom, navigateSpace]
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler]
);
const renderMatrixEvent = useMatrixEventRenderer<[IRoomEvent, string, GetContentCallback]>(
@ -229,6 +227,7 @@ function RoomNotificationsGroupComp({
mediaAutoLoad={mediaAutoLoad}
urlPreview={urlPreview}
htmlReactParserOptions={htmlReactParserOptions}
linkifyOpts={linkifyOpts}
outlineAttachment
/>
);
@ -287,6 +286,7 @@ function RoomNotificationsGroupComp({
mediaAutoLoad={mediaAutoLoad}
urlPreview={urlPreview}
htmlReactParserOptions={htmlReactParserOptions}
linkifyOpts={linkifyOpts}
/>
);
}

View file

@ -47,13 +47,7 @@ import {
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { roomToParentsAtom } from '../../../state/room/roomToParents';
import { allRoomsAtom } from '../../../state/room-list/roomList';
import {
getOriginBaseUrl,
getSpaceLobbyPath,
getSpacePath,
joinPathComponent,
withOriginBaseUrl,
} from '../../pathUtils';
import { getSpaceLobbyPath, getSpacePath, joinPathComponent } from '../../pathUtils';
import {
SidebarAvatar,
SidebarItem,
@ -67,7 +61,7 @@ import {
import { RoomUnreadProvider, RoomsUnreadProvider } from '../../../components/RoomUnreadProvider';
import { useSelectedSpace } from '../../../hooks/router/useSelectedSpace';
import { UnreadBadge } from '../../../components/unread-badge';
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../../utils/matrix';
import { RoomAvatar } from '../../../components/room-avatar';
import { nameInitials, randomStr } from '../../../utils/common';
import {
@ -83,7 +77,6 @@ import { AccountDataEvent } from '../../../../types/matrix/accountData';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath';
import { useOpenedSidebarFolderAtom } from '../../../state/hooks/openedSidebarFolder';
import { useClientConfig } from '../../../hooks/useClientConfig';
import { usePowerLevels, usePowerLevelsAPI } from '../../../hooks/usePowerLevels';
import { useRoomsUnread } from '../../../state/hooks/unread';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
@ -91,6 +84,8 @@ import { markAsRead } from '../../../../client/action/notifications';
import { copyToClipboard } from '../../../utils/dom';
import { openInviteUser, openSpaceSettings } from '../../../../client/action/navigation';
import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoom } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers';
type SpaceMenuProps = {
room: Room;
@ -100,7 +95,6 @@ type SpaceMenuProps = {
const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
({ room, requestClose, onUnpin }, ref) => {
const mx = useMatrixClient();
const { hashRouter } = useClientConfig();
const roomToParents = useAtomValue(roomToParentsAtom);
const powerLevels = usePowerLevels(room);
const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
@ -124,8 +118,9 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
};
const handleCopyLink = () => {
const spacePath = getSpacePath(getCanonicalAliasOrRoomId(mx, room.roomId));
copyToClipboard(withOriginBaseUrl(getOriginBaseUrl(hashRouter), spacePath));
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers));
requestClose();
};

View file

@ -9,6 +9,7 @@ import { useSpace } from '../../../hooks/useSpace';
import { getAllParents } from '../../../utils/room';
import { roomToParentsAtom } from '../../../state/room/roomToParents';
import { allRoomsAtom } from '../../../state/room-list/roomList';
import { useSearchParamsViaServers } from '../../../hooks/router/useSearchParamsViaServers';
export function SpaceRouteRoomProvider({ children }: { children: ReactNode }) {
const mx = useMatrixClient();
@ -16,7 +17,8 @@ export function SpaceRouteRoomProvider({ children }: { children: ReactNode }) {
const roomToParents = useAtomValue(roomToParentsAtom);
const allRooms = useAtomValue(allRoomsAtom);
const { roomIdOrAlias } = useParams();
const { roomIdOrAlias, eventId } = useParams();
const viaServers = useSearchParamsViaServers();
const roomId = useSelectedRoom();
const room = mx.getRoom(roomId);
@ -26,7 +28,13 @@ export function SpaceRouteRoomProvider({ children }: { children: ReactNode }) {
!allRooms.includes(room.roomId) ||
!getAllParents(roomToParents, room.roomId).has(space.roomId)
) {
return <JoinBeforeNavigate roomIdOrAlias={roomIdOrAlias!} />;
return (
<JoinBeforeNavigate
roomIdOrAlias={roomIdOrAlias!}
eventId={eventId}
viaServers={viaServers}
/>
);
}
return (

View file

@ -34,15 +34,8 @@ import {
NavItemContent,
NavLink,
} from '../../../components/nav';
import {
getOriginBaseUrl,
getSpaceLobbyPath,
getSpacePath,
getSpaceRoomPath,
getSpaceSearchPath,
withOriginBaseUrl,
} from '../../pathUtils';
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
import { getSpaceLobbyPath, getSpaceRoomPath, getSpaceSearchPath } from '../../pathUtils';
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../../utils/matrix';
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
import {
useSpaceLobbySelected,
@ -69,11 +62,12 @@ import { useRoomsUnread } from '../../../state/hooks/unread';
import { UseStateProvider } from '../../../components/UseStateProvider';
import { LeaveSpacePrompt } from '../../../components/leave-space-prompt';
import { copyToClipboard } from '../../../utils/dom';
import { useClientConfig } from '../../../hooks/useClientConfig';
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
import { useStateEvent } from '../../../hooks/useStateEvent';
import { StateEvent } from '../../../../types/matrix/room';
import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoom } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers';
type SpaceMenuProps = {
room: Room;
@ -81,7 +75,6 @@ type SpaceMenuProps = {
};
const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClose }, ref) => {
const mx = useMatrixClient();
const { hashRouter } = useClientConfig();
const roomToParents = useAtomValue(roomToParentsAtom);
const powerLevels = usePowerLevels(room);
const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
@ -100,8 +93,9 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClo
};
const handleCopyLink = () => {
const spacePath = getSpacePath(getCanonicalAliasOrRoomId(mx, room.roomId));
copyToClipboard(withOriginBaseUrl(getOriginBaseUrl(hashRouter), spacePath));
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers));
requestClose();
};

View file

@ -6,6 +6,7 @@ import { allRoomsAtom } from '../../../state/room-list/roomList';
import { useSelectedSpace } from '../../../hooks/router/useSelectedSpace';
import { SpaceProvider } from '../../../hooks/useSpace';
import { JoinBeforeNavigate } from '../../../features/join-before-navigate';
import { useSearchParamsViaServers } from '../../../hooks/router/useSearchParamsViaServers';
type RouteSpaceProviderProps = {
children: ReactNode;
@ -13,13 +14,15 @@ type RouteSpaceProviderProps = {
export function RouteSpaceProvider({ children }: RouteSpaceProviderProps) {
const mx = useMatrixClient();
const joinedSpaces = useSpaces(mx, allRoomsAtom);
const { spaceIdOrAlias } = useParams();
const viaServers = useSearchParamsViaServers();
const selectedSpaceId = useSelectedSpace();
const space = mx.getRoom(selectedSpaceId);
if (!space || !joinedSpaces.includes(space.roomId)) {
return <JoinBeforeNavigate roomIdOrAlias={spaceIdOrAlias ?? ''} />;
return <JoinBeforeNavigate roomIdOrAlias={spaceIdOrAlias ?? ''} viaServers={viaServers} />;
}
return (