fix: P0 post-deploy bug fixes from live testing
- EditHistoryModal: use raw fetch with /_matrix/client/v1/ path for relations API — Synapse only supports relations at v1, not v3 (fixes Sentry JAVASCRIPT-REACT-K / 404 error) - RoomViewHeader: remove useReportRoomSupported spec-version gate — Synapse 1.114+ has the endpoint but only advertises spec v1.12; button now always shows for non-creator, non-server-notice rooms - ReportRoomModal: handle M_UNRECOGNIZED/404 with "not supported by your homeserver" message - RoomNavItem: add isServerNotice guard to Room Settings in sidebar context menu (was only guarded in header three-dots menu) - initMatrix: bump setMaxListeners from 50 → 150 to prevent MaxListenersExceededWarning with large room lists - RoomProfile: save topic with formatted_body + format when markdown syntax is detected; add markdown hint below topic textarea Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,32 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
|||||||
import { useAlive } from '../../../hooks/useAlive';
|
import { useAlive } from '../../../hooks/useAlive';
|
||||||
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
|
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
|
||||||
|
|
||||||
|
const MARKDOWN_PATTERN = /(\*\*|__|\*|_|~~|`|\[.+?\]\(.+?\))/;
|
||||||
|
|
||||||
|
function topicMarkdownToHtml(text: string): string {
|
||||||
|
return text
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
.replace(/__(.+?)__/g, '<strong>$1</strong>')
|
||||||
|
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||||
|
.replace(/_(.+?)_/g, '<em>$1</em>')
|
||||||
|
.replace(/~~(.+?)~~/g, '<del>$1</del>')
|
||||||
|
.replace(/`(.+?)`/g, '<code>$1</code>')
|
||||||
|
.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>')
|
||||||
|
.replace(/\n/g, '<br>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTopicContent(topic: string): Record<string, string> {
|
||||||
|
if (!MARKDOWN_PATTERN.test(topic)) return { topic };
|
||||||
|
return {
|
||||||
|
topic,
|
||||||
|
format: 'org.matrix.custom.html',
|
||||||
|
formatted_body: topicMarkdownToHtml(topic),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
type RoomProfileEditProps = {
|
type RoomProfileEditProps = {
|
||||||
canEditAvatar: boolean;
|
canEditAvatar: boolean;
|
||||||
canEditName: boolean;
|
canEditName: boolean;
|
||||||
@@ -101,7 +127,8 @@ export function RoomProfileEdit({
|
|||||||
await mx.sendStateEvent(room.roomId, StateEvent.RoomName as any, { name: roomName });
|
await mx.sendStateEvent(room.roomId, StateEvent.RoomName as any, { name: roomName });
|
||||||
}
|
}
|
||||||
if (roomTopic !== undefined) {
|
if (roomTopic !== undefined) {
|
||||||
await mx.sendStateEvent(room.roomId, StateEvent.RoomTopic as any, { topic: roomTopic });
|
const topicContent = buildTopicContent(roomTopic);
|
||||||
|
await mx.sendStateEvent(room.roomId, StateEvent.RoomTopic as any, topicContent);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[mx, room.roomId],
|
[mx, room.roomId],
|
||||||
@@ -224,10 +251,16 @@ export function RoomProfileEdit({
|
|||||||
<TextArea
|
<TextArea
|
||||||
name="topicTextArea"
|
name="topicTextArea"
|
||||||
defaultValue={topic}
|
defaultValue={topic}
|
||||||
|
placeholder="Describe this room. Markdown supported: **bold**, *italic*, [link](url)"
|
||||||
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 }}>
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
|
|||||||
const space = useSpaceOptionally();
|
const space = useSpaceOptionally();
|
||||||
|
|
||||||
const [invitePrompt, setInvitePrompt] = useState(false);
|
const [invitePrompt, setInvitePrompt] = useState(false);
|
||||||
|
const isServerNotice = room.getType() === 'm.server_notice';
|
||||||
|
|
||||||
const handleMarkAsRead = () => {
|
const handleMarkAsRead = () => {
|
||||||
markAsRead(mx, room.roomId, hideActivity);
|
markAsRead(mx, room.roomId, hideActivity);
|
||||||
@@ -320,16 +321,18 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
|
|||||||
Rename for me…
|
Rename for me…
|
||||||
</Text>
|
</Text>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
{!isServerNotice && (
|
||||||
onClick={handleRoomSettings}
|
<MenuItem
|
||||||
size="300"
|
onClick={handleRoomSettings}
|
||||||
after={<Icon size="100" src={Icons.Setting} />}
|
size="300"
|
||||||
radii="300"
|
after={<Icon size="100" src={Icons.Setting} />}
|
||||||
>
|
radii="300"
|
||||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
>
|
||||||
Room Settings
|
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||||
</Text>
|
Room Settings
|
||||||
</MenuItem>
|
</Text>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Line variant="Surface" size="300" />
|
<Line variant="Surface" size="300" />
|
||||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||||
|
|||||||
@@ -67,16 +67,19 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
|||||||
submitReport(fullReason);
|
submitReport(fullReason);
|
||||||
};
|
};
|
||||||
|
|
||||||
const errcode =
|
const reportError =
|
||||||
reportState.status === AsyncStatus.Error
|
reportState.status === AsyncStatus.Error
|
||||||
? (reportState.error as { errcode?: string })?.errcode
|
? (reportState.error as { errcode?: string; httpStatus?: number })
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const errcode = reportError?.errcode;
|
||||||
const errorMsg =
|
const errorMsg =
|
||||||
errcode === 'M_LIMIT_EXCEEDED'
|
errcode === 'M_LIMIT_EXCEEDED'
|
||||||
? 'You are being rate limited. Please wait before reporting again.'
|
? 'You are being rate limited. Please wait before reporting again.'
|
||||||
: errcode === 'M_FORBIDDEN'
|
: errcode === 'M_FORBIDDEN'
|
||||||
? 'You cannot report this room.'
|
? 'You cannot report this room.'
|
||||||
: 'Failed to submit report. Please try again.';
|
: errcode === 'M_UNRECOGNIZED' || reportError?.httpStatus === 404
|
||||||
|
? 'Room reporting is not supported by your homeserver.'
|
||||||
|
: 'Failed to submit report. Please try again.';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import { roomToUnreadAtom } from '../../state/room/roomToUnread';
|
|||||||
import { copyToClipboard } from '../../utils/dom';
|
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 { useReportRoomSupported } from '../../hooks/useReportRoomSupported';
|
|
||||||
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 { getMatrixToRoom } from '../../plugins/matrix-to';
|
||||||
@@ -90,7 +89,6 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
|||||||
|
|
||||||
const permissions = useRoomPermissions(creators, powerLevels);
|
const permissions = useRoomPermissions(creators, powerLevels);
|
||||||
const canInvite = permissions.action('invite', mx.getSafeUserId());
|
const canInvite = permissions.action('invite', mx.getSafeUserId());
|
||||||
const reportRoomSupported = useReportRoomSupported();
|
|
||||||
const isServerNotice = room.getType() === 'm.server_notice';
|
const isServerNotice = room.getType() === 'm.server_notice';
|
||||||
const isCreator = creators.has(mx.getSafeUserId());
|
const isCreator = creators.has(mx.getSafeUserId());
|
||||||
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
||||||
@@ -247,7 +245,7 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
|||||||
</Box>
|
</Box>
|
||||||
<Line variant="Surface" size="300" />
|
<Line variant="Surface" size="300" />
|
||||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||||
{reportRoomSupported && !isServerNotice && !isCreator && (
|
{!isServerNotice && !isCreator && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => setReportRoomOpen(true)}
|
onClick={() => setReportRoomOpen(true)}
|
||||||
variant="Critical"
|
variant="Critical"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
config,
|
config,
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import { MatrixEvent, Method, Room } from 'matrix-js-sdk';
|
import { MatrixEvent, Room } from 'matrix-js-sdk';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
@@ -77,10 +77,13 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
|||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
if (!eventId) return { events: [], hasMore: false };
|
if (!eventId) return { events: [], hasMore: false };
|
||||||
|
|
||||||
const path = `/rooms/${encodeURIComponent(roomId)}/relations/${encodeURIComponent(eventId)}/m.replace`;
|
// Relations API lives at /_matrix/client/v1/ (not v3); use raw fetch to avoid SDK prefix
|
||||||
const res = await mx.http.authedRequest<EditHistoryResponse>(Method.Get, path, {
|
const token = mx.getAccessToken();
|
||||||
limit: '50',
|
const baseUrl = mx.getHomeserverUrl();
|
||||||
});
|
const url = `${baseUrl}/_matrix/client/v1/rooms/${encodeURIComponent(roomId)}/relations/${encodeURIComponent(eventId)}/m.replace?limit=50`;
|
||||||
|
const fetchRes = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
||||||
|
if (!fetchRes.ok) throw new Error(`HTTP ${fetchRes.status}`);
|
||||||
|
const res = (await fetchRes.json()) as EditHistoryResponse;
|
||||||
const rawEvents = res.chunk ?? [];
|
const rawEvents = res.chunk ?? [];
|
||||||
const events = rawEvents
|
const events = rawEvents
|
||||||
.filter(isRawEditEvent)
|
.filter(isRawEditEvent)
|
||||||
|
|||||||
@@ -47,9 +47,8 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
|
|||||||
}
|
}
|
||||||
await mx.initRustCrypto();
|
await mx.initRustCrypto();
|
||||||
|
|
||||||
mx.setMaxListeners(50);
|
mx.setMaxListeners(150);
|
||||||
// Each RoomNavItem subscribes to session_started/session_ended; one listener per visible room.
|
mx.matrixRTC.setMaxListeners(150);
|
||||||
mx.matrixRTC.setMaxListeners(100);
|
|
||||||
|
|
||||||
return mx;
|
return mx;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user