fix: GIFs, topic display, formatting toolbar, Copy Link in Invite, support URL
- 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:
@@ -34,7 +34,13 @@ import { isKeyHotkey } from 'is-hotkey';
|
|||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { useDirectUsers } from '../../hooks/useDirectUsers';
|
import { useDirectUsers } from '../../hooks/useDirectUsers';
|
||||||
import { getMxIdLocalPart, getMxIdServer, isUserId } from '../../utils/matrix';
|
import {
|
||||||
|
getCanonicalAliasOrRoomId,
|
||||||
|
getMxIdLocalPart,
|
||||||
|
getMxIdServer,
|
||||||
|
isRoomAlias,
|
||||||
|
isUserId,
|
||||||
|
} from '../../utils/matrix';
|
||||||
import { Membership } from '../../../types/matrix/room';
|
import { Membership } from '../../../types/matrix/room';
|
||||||
import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
|
import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
|
||||||
import { highlightText, makeHighlightRegex } from '../../plugins/react-custom-html-parser';
|
import { highlightText, makeHighlightRegex } from '../../plugins/react-custom-html-parser';
|
||||||
@@ -42,6 +48,9 @@ import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
|||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { BreakWord } from '../../styles/Text.css';
|
import { BreakWord } from '../../styles/Text.css';
|
||||||
import { useAlive } from '../../hooks/useAlive';
|
import { useAlive } from '../../hooks/useAlive';
|
||||||
|
import { copyToClipboard } from '../../utils/dom';
|
||||||
|
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
||||||
|
import { getViaServers } from '../../plugins/via-servers';
|
||||||
|
|
||||||
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
|
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
@@ -58,6 +67,15 @@ type InviteUserProps = {
|
|||||||
export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const alive = useAlive();
|
const alive = useAlive();
|
||||||
|
const [linkCopied, setLinkCopied] = useState(false);
|
||||||
|
|
||||||
|
const handleCopyLink = () => {
|
||||||
|
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
|
||||||
|
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
|
||||||
|
copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers));
|
||||||
|
setLinkCopied(true);
|
||||||
|
setTimeout(() => setLinkCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const directUsers = useDirectUsers();
|
const directUsers = useDirectUsers();
|
||||||
@@ -172,7 +190,18 @@ export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
|||||||
Invite
|
Invite
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box shrink="No">
|
<Box shrink="No" gap="100" alignItems="Center">
|
||||||
|
<Button
|
||||||
|
size="300"
|
||||||
|
variant={linkCopied ? 'Success' : 'Secondary'}
|
||||||
|
fill="Soft"
|
||||||
|
radii="300"
|
||||||
|
before={<Icon size="100" src={linkCopied ? Icons.Check : Icons.Link} />}
|
||||||
|
onClick={handleCopyLink}
|
||||||
|
aria-label="Copy room link"
|
||||||
|
>
|
||||||
|
<Text size="B300">{linkCopied ? 'Copied!' : 'Copy Link'}</Text>
|
||||||
|
</Button>
|
||||||
<IconButton size="300" radii="300" onClick={requestClose} aria-label="Close">
|
<IconButton size="300" radii="300" onClick={requestClose} aria-label="Close">
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
color,
|
color,
|
||||||
|
config,
|
||||||
Icon,
|
Icon,
|
||||||
Icons,
|
Icons,
|
||||||
Input,
|
Input,
|
||||||
@@ -11,7 +12,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextArea,
|
TextArea,
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import React, { FormEventHandler, useCallback, useMemo, useState } from 'react';
|
import React, { FormEventHandler, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import Linkify from 'linkify-react';
|
import Linkify from 'linkify-react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -43,6 +44,22 @@ import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
|
|||||||
|
|
||||||
const MARKDOWN_PATTERN = /(\*\*|__|\*|_|~~|`|\[.+?\]\(.+?\))/;
|
const MARKDOWN_PATTERN = /(\*\*|__|\*|_|~~|`|\[.+?\]\(.+?\))/;
|
||||||
|
|
||||||
|
function wrapSelection(textarea: HTMLTextAreaElement, syntax: string, placeholder: string) {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const selected = textarea.value.substring(start, end);
|
||||||
|
const inner = selected || placeholder;
|
||||||
|
const replacement = `${syntax}${inner}${syntax}`;
|
||||||
|
const newValue = textarea.value.substring(0, start) + replacement + textarea.value.substring(end);
|
||||||
|
const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
|
||||||
|
nativeSetter?.call(textarea, newValue);
|
||||||
|
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
const cursorStart = start + syntax.length;
|
||||||
|
const cursorEnd = cursorStart + inner.length;
|
||||||
|
textarea.focus();
|
||||||
|
textarea.setSelectionRange(cursorStart, cursorEnd);
|
||||||
|
}
|
||||||
|
|
||||||
function topicMarkdownToHtml(text: string): string {
|
function topicMarkdownToHtml(text: string): string {
|
||||||
return text
|
return text
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -60,11 +77,11 @@ function topicMarkdownToHtml(text: string): string {
|
|||||||
|
|
||||||
function buildTopicContent(topic: string): Record<string, string> {
|
function buildTopicContent(topic: string): Record<string, string> {
|
||||||
if (!MARKDOWN_PATTERN.test(topic)) return { topic };
|
if (!MARKDOWN_PATTERN.test(topic)) return { topic };
|
||||||
return {
|
const formattedBody = topicMarkdownToHtml(topic);
|
||||||
topic,
|
// Use HTML-stripped text as the plain topic so the header shows clean text, not raw markdown syntax
|
||||||
format: 'org.matrix.custom.html',
|
const plainTopic = formattedBody.replace(/<br>/g, '\n').replace(/<[^>]+>/g, '');
|
||||||
formatted_body: topicMarkdownToHtml(topic),
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
};
|
return { topic: plainTopic, format: 'org.matrix.custom.html', formatted_body: formattedBody };
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoomProfileEditProps = {
|
type RoomProfileEditProps = {
|
||||||
@@ -96,6 +113,7 @@ export function RoomProfileEdit({
|
|||||||
? (mxcUrlToHttp(mx, roomAvatar, useAuthentication) ?? undefined)
|
? (mxcUrlToHttp(mx, roomAvatar, useAuthentication) ?? undefined)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const topicRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const [imageFile, setImageFile] = useState<File>();
|
const [imageFile, setImageFile] = useState<File>();
|
||||||
const avatarFileUrl = useObjectURL(imageFile);
|
const avatarFileUrl = useObjectURL(imageFile);
|
||||||
const uploadingAvatar = avatarFileUrl ? roomAvatar === avatar : false;
|
const uploadingAvatar = avatarFileUrl ? roomAvatar === avatar : false;
|
||||||
@@ -247,20 +265,60 @@ export function RoomProfileEdit({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box direction="Inherit" gap="100">
|
<Box direction="Inherit" gap="100">
|
||||||
<Text size="L400">Topic</Text>
|
<Box alignItems="Center" justifyContent="SpaceBetween">
|
||||||
|
<Text size="L400">Topic</Text>
|
||||||
|
{canEditTopic && !submitting && (
|
||||||
|
<Box gap="100">
|
||||||
|
{(
|
||||||
|
[
|
||||||
|
{ label: 'B', syntax: '**', placeholder: 'bold', title: 'Bold' },
|
||||||
|
{ label: 'I', syntax: '*', placeholder: 'italic', title: 'Italic' },
|
||||||
|
{
|
||||||
|
label: 'S',
|
||||||
|
syntax: '~~',
|
||||||
|
placeholder: 'strikethrough',
|
||||||
|
title: 'Strikethrough',
|
||||||
|
},
|
||||||
|
{ label: '`', syntax: '`', placeholder: 'code', title: 'Inline Code' },
|
||||||
|
] as const
|
||||||
|
).map(({ label, syntax, placeholder, title }) => (
|
||||||
|
<button
|
||||||
|
key={label}
|
||||||
|
type="button"
|
||||||
|
title={title}
|
||||||
|
aria-label={title}
|
||||||
|
onClick={() =>
|
||||||
|
topicRef.current && wrapSelection(topicRef.current, syntax, placeholder)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
padding: `${config.space.S100} ${config.space.S200}`,
|
||||||
|
border: `1px solid ${color.Surface.ContainerLine}`,
|
||||||
|
borderRadius: config.radii.R300,
|
||||||
|
background: color.Surface.Container,
|
||||||
|
color: color.Surface.OnContainer,
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
fontWeight: label === 'B' ? 700 : label === 'I' ? undefined : undefined,
|
||||||
|
fontStyle: label === 'I' ? 'italic' : undefined,
|
||||||
|
fontFamily: label === '`' ? 'monospace' : 'inherit',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
<TextArea
|
<TextArea
|
||||||
|
ref={topicRef}
|
||||||
name="topicTextArea"
|
name="topicTextArea"
|
||||||
defaultValue={topic}
|
defaultValue={topic}
|
||||||
placeholder="Describe this room. Markdown supported: **bold**, *italic*, [link](url)"
|
placeholder="Describe this room… supports **bold**, *italic*, ~~strikethrough~~, `code`"
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
radii="300"
|
radii="300"
|
||||||
readOnly={!canEditTopic || submitting}
|
readOnly={!canEditTopic || submitting}
|
||||||
/>
|
/>
|
||||||
{canEditTopic && (
|
|
||||||
<Text size="T200" priority="300">
|
|
||||||
Supports markdown: **bold**, *italic*, ~~strikethrough~~, `code`, [text](url)
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
{submitState.status === AsyncStatus.Error && (
|
{submitState.status === AsyncStatus.Error && (
|
||||||
<Text size="T200" style={{ color: color.Critical.Main }}>
|
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||||
|
|||||||
@@ -36,16 +36,12 @@ import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|||||||
import { useRoomUnread } from '../../state/hooks/unread';
|
import { useRoomUnread } from '../../state/hooks/unread';
|
||||||
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
|
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
|
||||||
import { getPowersLevelFromMatrixEvent, usePowerLevels } from '../../hooks/usePowerLevels';
|
import { getPowersLevelFromMatrixEvent, usePowerLevels } from '../../hooks/usePowerLevels';
|
||||||
import { copyToClipboard } from '../../utils/dom';
|
|
||||||
import { markAsRead } from '../../utils/notifications';
|
import { markAsRead } from '../../utils/notifications';
|
||||||
import { UseStateProvider } from '../../components/UseStateProvider';
|
import { UseStateProvider } from '../../components/UseStateProvider';
|
||||||
import { LeaveRoomPrompt } from '../../components/leave-room-prompt';
|
import { LeaveRoomPrompt } from '../../components/leave-room-prompt';
|
||||||
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
|
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
|
||||||
import { TypingIndicator } from '../../components/typing-indicator';
|
import { TypingIndicator } from '../../components/typing-indicator';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
|
||||||
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
|
|
||||||
import { getViaServers } from '../../plugins/via-servers';
|
|
||||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
import { useSetting } from '../../state/hooks/settings';
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
import { settingsAtom } from '../../state/settings';
|
import { settingsAtom } from '../../state/settings';
|
||||||
@@ -225,13 +221,6 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
|
|||||||
setInvitePrompt(true);
|
setInvitePrompt(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyLink = () => {
|
|
||||||
const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, room.roomId);
|
|
||||||
const viaServers = isRoomAlias(roomIdOrAlias) ? undefined : getViaServers(room);
|
|
||||||
copyToClipboard(getMatrixToRoom(roomIdOrAlias, viaServers));
|
|
||||||
requestClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRoomSettings = () => {
|
const handleRoomSettings = () => {
|
||||||
openRoomSettings(room.roomId, space?.roomId);
|
openRoomSettings(room.roomId, space?.roomId);
|
||||||
requestClose();
|
requestClose();
|
||||||
@@ -298,16 +287,6 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
|
|||||||
Invite
|
Invite
|
||||||
</Text>
|
</Text>
|
||||||
</MenuItem>
|
</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>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
requestClose();
|
requestClose();
|
||||||
|
|||||||
@@ -538,23 +538,26 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
async (gifUrl: string, w: number, h: number) => {
|
async (gifUrl: string, w: number, h: number) => {
|
||||||
setGifUploading(true);
|
setGifUploading(true);
|
||||||
try {
|
try {
|
||||||
// Only fetch from trusted Giphy CDN domains
|
// Only fetch from trusted Giphy CDN domains (match any *.giphy.com subdomain)
|
||||||
const allowed = [
|
|
||||||
'media.giphy.com',
|
|
||||||
'i.giphy.com',
|
|
||||||
'media0.giphy.com',
|
|
||||||
'media1.giphy.com',
|
|
||||||
'media2.giphy.com',
|
|
||||||
'media3.giphy.com',
|
|
||||||
'media4.giphy.com',
|
|
||||||
];
|
|
||||||
const { hostname } = new URL(gifUrl);
|
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);
|
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') ?? '';
|
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();
|
const blob = await res.blob();
|
||||||
if (blob.size > 20 * 1024 * 1024) {
|
if (blob.size > 20 * 1024 * 1024) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { useSetting } from '../../state/hooks/settings';
|
|||||||
import { settingsAtom } from '../../state/settings';
|
import { settingsAtom } from '../../state/settings';
|
||||||
import { useSpaceOptionally } from '../../hooks/useSpace';
|
import { useSpaceOptionally } from '../../hooks/useSpace';
|
||||||
import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils';
|
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 { getStateEvents } from '../../utils/room';
|
||||||
import { _SearchPathSearchParams } from '../../pages/paths';
|
import { _SearchPathSearchParams } from '../../pages/paths';
|
||||||
import * as css from './RoomViewHeader.css';
|
import * as css from './RoomViewHeader.css';
|
||||||
@@ -46,13 +46,10 @@ import { useRoomUnread } from '../../state/hooks/unread';
|
|||||||
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||||
import { markAsRead } from '../../utils/notifications';
|
import { markAsRead } from '../../utils/notifications';
|
||||||
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
|
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
|
||||||
import { copyToClipboard } from '../../utils/dom';
|
|
||||||
import { LeaveRoomPrompt } from '../../components/leave-room-prompt';
|
import { LeaveRoomPrompt } from '../../components/leave-room-prompt';
|
||||||
import { useRoomAvatar, useLocalRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
import { useRoomAvatar, useLocalRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
|
||||||
import { getViaServers } from '../../plugins/via-servers';
|
|
||||||
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
||||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
import { useRoomPinnedEvents } from '../../hooks/useRoomPinnedEvents';
|
import { useRoomPinnedEvents } from '../../hooks/useRoomPinnedEvents';
|
||||||
@@ -107,13 +104,6 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
|||||||
setInvitePrompt(true);
|
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 openSettings = useOpenRoomSettings();
|
||||||
const parentSpace = useSpaceOptionally();
|
const parentSpace = useSpaceOptionally();
|
||||||
const handleOpenSettings = () => {
|
const handleOpenSettings = () => {
|
||||||
@@ -193,16 +183,6 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
|||||||
</Text>
|
</Text>
|
||||||
</MenuItem>
|
</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 && (
|
{!isServerNotice && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={handleOpenSettings}
|
onClick={handleOpenSettings}
|
||||||
|
|||||||
Reference in New Issue
Block a user