fix: GIFs, topic display, formatting toolbar, Copy Link in Invite, support URL
CI / Build & Quality Checks (push) Successful in 10m32s

- RoomInput: GIF domain check uses endsWith('.giphy.com') — handles all
  Giphy CDN shards; all silent failure paths now show user-facing error
- RoomProfile: topic plain-text field is now HTML-stripped (no markdown
  syntax visible in header); formatting toolbar (B/I/S/code) above the
  textarea wraps selected text with correct markdown syntax
- InviteUserPrompt: Copy Link button added to dialog header with
  'Copied!' confirmation; removed Copy Link from both three-dot menus
- RoomViewHeader/RoomNavItem: unused copy-link imports removed
- nginx (live): support_page URL updated from lotusguild.org →
  matrix.lotusguild.org

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 09:55:04 -04:00
parent c852b6f121
commit 50e4da8ea5
5 changed files with 119 additions and 70 deletions
+16 -13
View File
@@ -538,23 +538,26 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
async (gifUrl: string, w: number, h: number) => {
setGifUploading(true);
try {
// Only fetch from trusted Giphy CDN domains
const allowed = [
'media.giphy.com',
'i.giphy.com',
'media0.giphy.com',
'media1.giphy.com',
'media2.giphy.com',
'media3.giphy.com',
'media4.giphy.com',
];
// Only fetch from trusted Giphy CDN domains (match any *.giphy.com subdomain)
const { hostname } = new URL(gifUrl);
if (!allowed.includes(hostname)) return;
if (!hostname.endsWith('.giphy.com') && hostname !== 'giphy.com') {
setGifError('GIF source not trusted.');
setTimeout(() => setGifError(null), 4000);
return;
}
const res = await fetch(gifUrl);
if (!res.ok) return;
if (!res.ok) {
setGifError('Failed to download GIF from Giphy.');
setTimeout(() => setGifError(null), 4000);
return;
}
const contentType = res.headers.get('content-type') ?? '';
if (!contentType.startsWith('image/')) return;
if (!contentType.startsWith('image/')) {
setGifError('Unexpected GIF format. Please try another.');
setTimeout(() => setGifError(null), 4000);
return;
}
const blob = await res.blob();
if (blob.size > 20 * 1024 * 1024) {
+1 -21
View File
@@ -38,7 +38,7 @@ import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { useSpaceOptionally } from '../../hooks/useSpace';
import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils';
import { getCanonicalAliasOrRoomId, isRoomAlias, mxcUrlToHttp } from '../../utils/matrix';
import { getCanonicalAliasOrRoomId, mxcUrlToHttp } from '../../utils/matrix';
import { getStateEvents } from '../../utils/room';
import { _SearchPathSearchParams } from '../../pages/paths';
import * as css from './RoomViewHeader.css';
@@ -46,13 +46,10 @@ import { useRoomUnread } from '../../state/hooks/unread';
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
import { markAsRead } from '../../utils/notifications';
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
import { copyToClipboard } from '../../utils/dom';
import { LeaveRoomPrompt } from '../../components/leave-room-prompt';
import { useRoomAvatar, useLocalRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { stopPropagation } from '../../utils/keyboard';
import { getMatrixToRoom } from '../../plugins/matrix-to';
import { getViaServers } from '../../plugins/via-servers';
import { BackRouteHandler } from '../../components/BackRouteHandler';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useRoomPinnedEvents } from '../../hooks/useRoomPinnedEvents';
@@ -107,13 +104,6 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
setInvitePrompt(true);
};
const handleCopyLink = () => {
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers));
requestClose();
};
const openSettings = useOpenRoomSettings();
const parentSpace = useSpaceOptionally();
const handleOpenSettings = () => {
@@ -193,16 +183,6 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
</Text>
</MenuItem>
)}
<MenuItem
onClick={handleCopyLink}
size="300"
after={<Icon size="100" src={Icons.Link} />}
radii="300"
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Copy Link
</Text>
</MenuItem>
{!isServerNotice && (
<MenuItem
onClick={handleOpenSettings}