fix: comprehensive P0 quality pass — audit findings resolved
CI / Build & Quality Checks (push) Failing after 5m43s
CI / Build & Quality Checks (push) Failing after 5m43s
- ReportRoomModal: use mx.reportRoom() SDK method, fix undefined CSS vars
(--mx-surface/border → folds color tokens), add role/aria-modal/aria-labelledby,
accessible select/input labels, per-error-code messages, auto-close on success
- About.tsx: clickable matrix_id + email_address links (Text as="a"), AbortController
cleanup, runtime JSON type guard, loading state, role display for all role values,
remove classList theming hack, use mx.getHomeserverUrl()
- RoomViewHeader: useLocalRoomName for header title, useReportRoomSupported gate,
hide Invite/Settings/Report for server notice rooms, isCreator guard on Report,
FocusTrap returnFocusOnDeactivate on topic overlay, Server Notice chip tooltip
- RoomInput: replace raw <div> with folds <Box> for server notice read-only message
- EditHistoryModal: isRawEditEvent type guard, handle next_batch truncation,
getVersionBody handles formatted_body (strips HTML for text display),
role/aria-modal/aria-labelledby accessibility, guard for undefined eventId,
use config.space tokens (remove var(--mx-spacing-*) strings)
- RoomNavItem: remove duplicate getExistingContent (use exported getLocalRoomNamesContent),
maxLength={255} on rename input, fix FocusTrap nesting (renameDialog state moved to
RoomNavItem_, RenameRoomDialog rendered outside menu, menu closes before dialog opens),
pencil icon opacity via config.opacity.P300
- useRoomMeta: export getLocalRoomNamesContent for reuse
- RoomIntro: useLocalRoomName, formatted topic viewer with Overlay/FocusTrap/RoomTopicViewer
- CallRoomName: useLocalRoomName for consistent rename display in call overlay
- General.tsx: fix #980000/#FF6B00 hardcoded hex → color tokens/CSS vars, URL Preview
capitalization, improved encrypted preview warning text + Warning chip, add
description to plain urlPreview setting
- sanitize.ts: fix hex color regex to support 3/4/6/8 digit hex (CSS4 #RGBA, #RRGGBBAA)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { FormEventHandler, useCallback, useState } from 'react';
|
||||
import React, { FormEventHandler, useCallback, useEffect, useState } from 'react';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import {
|
||||
Box,
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
color,
|
||||
Spinner,
|
||||
} from 'folds';
|
||||
import { Method } from 'matrix-js-sdk';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
@@ -42,17 +41,20 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
const [reportState, submitReport] = useAsyncCallback(
|
||||
useCallback(
|
||||
async (reason: string) => {
|
||||
await mx.http.authedRequest(
|
||||
Method.Post,
|
||||
`/rooms/${encodeURIComponent(roomId)}/report`,
|
||||
undefined,
|
||||
{ reason },
|
||||
);
|
||||
await mx.reportRoom(roomId, reason);
|
||||
},
|
||||
[mx, roomId],
|
||||
),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (reportState.status === AsyncStatus.Success) {
|
||||
const timer = setTimeout(onClose, 1500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
return undefined;
|
||||
}, [reportState.status, onClose]);
|
||||
|
||||
const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
|
||||
evt.preventDefault();
|
||||
if (reportState.status === AsyncStatus.Loading || reportState.status === AsyncStatus.Success) {
|
||||
@@ -61,12 +63,20 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
const target = evt.target as HTMLFormElement;
|
||||
const reasonInput = target.elements.namedItem('reasonInput') as HTMLInputElement | null;
|
||||
const reasonText = reasonInput?.value.trim() ?? '';
|
||||
const fullReason = reasonText
|
||||
? `[${CATEGORY_LABELS[category]}] ${reasonText}`
|
||||
: `[${CATEGORY_LABELS[category]}]`;
|
||||
const fullReason = `[${CATEGORY_LABELS[category]}] ${reasonText}`;
|
||||
submitReport(fullReason);
|
||||
};
|
||||
|
||||
const errcode = (reportState.status === AsyncStatus.Error
|
||||
? (reportState.error as { errcode?: string })?.errcode
|
||||
: undefined);
|
||||
const errorMsg =
|
||||
errcode === 'M_LIMIT_EXCEEDED'
|
||||
? 'You are being rate limited. Please wait before reporting again.'
|
||||
: errcode === 'M_FORBIDDEN'
|
||||
? 'You cannot report this room.'
|
||||
: 'Failed to submit report. Please try again.';
|
||||
|
||||
return (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
@@ -80,12 +90,15 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
>
|
||||
<Box
|
||||
as="form"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="report-room-dialog-title"
|
||||
onSubmit={handleSubmit}
|
||||
direction="Column"
|
||||
style={{
|
||||
background: 'var(--mx-surface)',
|
||||
background: color.Surface.Container,
|
||||
borderRadius: config.radii.R400,
|
||||
boxShadow: '0 8px 32px rgba(0,0,0,0.55)',
|
||||
boxShadow: color.Other.Shadow,
|
||||
width: '100%',
|
||||
maxWidth: 420,
|
||||
overflow: 'hidden',
|
||||
@@ -100,7 +113,7 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
size="500"
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">Report Room</Text>
|
||||
<Text id="report-room-dialog-title" size="H4">Report Room</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={onClose} radii="300" aria-label="Close">
|
||||
<Icon src={Icons.Cross} />
|
||||
@@ -113,9 +126,11 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
</Text>
|
||||
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Category</Text>
|
||||
<Text as="label" htmlFor="report-category" size="L400">Category</Text>
|
||||
<Box
|
||||
as="select"
|
||||
id="report-category"
|
||||
aria-label="Report category"
|
||||
value={category}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setCategory(e.target.value as ReportCategory)
|
||||
@@ -123,9 +138,9 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
style={{
|
||||
padding: `${config.space.S200} ${config.space.S300}`,
|
||||
borderRadius: config.radii.R300,
|
||||
border: '1px solid var(--mx-border)',
|
||||
background: 'var(--mx-bg-surface)',
|
||||
color: 'var(--mx-c-surface-on)',
|
||||
border: `1px solid ${color.Surface.ContainerLine}`,
|
||||
background: color.Surface.Container,
|
||||
color: color.Surface.OnContainer,
|
||||
fontSize: 'inherit',
|
||||
fontFamily: 'inherit',
|
||||
width: '100%',
|
||||
@@ -140,11 +155,17 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
</Box>
|
||||
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Reason</Text>
|
||||
<Input name="reasonInput" variant="Background" required />
|
||||
<Text as="label" htmlFor="report-reason-input" size="L400">Reason</Text>
|
||||
<Input
|
||||
id="report-reason-input"
|
||||
name="reasonInput"
|
||||
aria-label="Reason for report"
|
||||
variant="Background"
|
||||
required
|
||||
/>
|
||||
{reportState.status === AsyncStatus.Error && (
|
||||
<Text style={{ color: color.Critical.Main }} size="T300">
|
||||
Failed to submit report. Please try again.
|
||||
{errorMsg}
|
||||
</Text>
|
||||
)}
|
||||
{reportState.status === AsyncStatus.Success && (
|
||||
|
||||
@@ -605,11 +605,17 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
|
||||
if (room.getType() === 'm.server_notice') {
|
||||
return (
|
||||
<div ref={ref} style={{ padding: config.space.S300, textAlign: 'center' }}>
|
||||
<Box
|
||||
ref={ref as React.Ref<HTMLDivElement>}
|
||||
direction="Column"
|
||||
alignItems="Center"
|
||||
justifyContent="Center"
|
||||
style={{ padding: config.space.S300 }}
|
||||
>
|
||||
<Text size="T300" priority="300">
|
||||
This is a server notice room — you cannot send messages here.
|
||||
This room contains system messages from your homeserver. Replies are not permitted.
|
||||
</Text>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@ 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, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
||||
import { useRoomAvatar, useRoomName, useLocalRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
||||
import { useReportRoomSupported } from '../../hooks/useReportRoomSupported';
|
||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
||||
@@ -89,6 +90,9 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
||||
|
||||
const permissions = useRoomPermissions(creators, powerLevels);
|
||||
const canInvite = permissions.action('invite', mx.getSafeUserId());
|
||||
const reportRoomSupported = useReportRoomSupported();
|
||||
const isServerNotice = room.getType() === 'm.server_notice';
|
||||
const isCreator = creators.has(mx.getSafeUserId());
|
||||
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
||||
const notificationMode = getRoomNotificationMode(notificationPreferences, room.roomId);
|
||||
const { navigateRoom } = useRoomNavigate();
|
||||
@@ -175,20 +179,22 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
||||
</Box>
|
||||
<Line variant="Surface" size="300" />
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
<MenuItem
|
||||
onClick={handleInvite}
|
||||
variant="Primary"
|
||||
fill="None"
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.UserPlus} />}
|
||||
radii="300"
|
||||
aria-pressed={invitePrompt}
|
||||
disabled={!canInvite}
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Invite
|
||||
</Text>
|
||||
</MenuItem>
|
||||
{!isServerNotice && (
|
||||
<MenuItem
|
||||
onClick={handleInvite}
|
||||
variant="Primary"
|
||||
fill="None"
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.UserPlus} />}
|
||||
radii="300"
|
||||
aria-pressed={invitePrompt}
|
||||
disabled={!canInvite}
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Invite
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
onClick={handleCopyLink}
|
||||
size="300"
|
||||
@@ -199,16 +205,18 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
||||
Copy Link
|
||||
</Text>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handleOpenSettings}
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Setting} />}
|
||||
radii="300"
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Room Settings
|
||||
</Text>
|
||||
</MenuItem>
|
||||
{!isServerNotice && (
|
||||
<MenuItem
|
||||
onClick={handleOpenSettings}
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Setting} />}
|
||||
radii="300"
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Room Settings
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
<UseStateProvider initial={false}>
|
||||
{(promptJump, setPromptJump) => (
|
||||
<>
|
||||
@@ -239,19 +247,21 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
||||
</Box>
|
||||
<Line variant="Surface" size="300" />
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
<MenuItem
|
||||
onClick={() => setReportRoomOpen(true)}
|
||||
variant="Critical"
|
||||
fill="None"
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Warning} />}
|
||||
radii="300"
|
||||
aria-pressed={reportRoomOpen}
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Report Room
|
||||
</Text>
|
||||
</MenuItem>
|
||||
{reportRoomSupported && !isServerNotice && !isCreator && (
|
||||
<MenuItem
|
||||
onClick={() => setReportRoomOpen(true)}
|
||||
variant="Critical"
|
||||
fill="None"
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Warning} />}
|
||||
radii="300"
|
||||
aria-pressed={reportRoomOpen}
|
||||
>
|
||||
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
|
||||
Report Room
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
<UseStateProvider initial={false}>
|
||||
{(promptLeave, setPromptLeave) => (
|
||||
<>
|
||||
@@ -436,7 +446,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
|
||||
const encryptionEvent = useStateEvent(room, StateEvent.RoomEncryption);
|
||||
const encryptedRoom = !!encryptionEvent;
|
||||
const avatarMxc = useRoomAvatar(room, direct);
|
||||
const name = useRoomName(room);
|
||||
const name = useLocalRoomName(room);
|
||||
const topic = useRoomTopic(room);
|
||||
const avatarUrl = avatarMxc
|
||||
? (mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined)
|
||||
@@ -508,9 +518,21 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
|
||||
{name}
|
||||
</Text>
|
||||
{room.getType() === 'm.server_notice' && (
|
||||
<Chip size="400" variant="Warning" radii="Pill" outlined>
|
||||
<Text size="T200">Server Notice</Text>
|
||||
</Chip>
|
||||
<TooltipProvider
|
||||
position="Bottom"
|
||||
offset={4}
|
||||
tooltip={
|
||||
<Tooltip>
|
||||
<Text>System messages from your homeserver administrator.</Text>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
{(triggerRef) => (
|
||||
<Chip ref={triggerRef} size="400" variant="Warning" radii="Pill" outlined>
|
||||
<Text size="T200">Server Notice</Text>
|
||||
</Chip>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</Box>
|
||||
{topic && (
|
||||
@@ -522,6 +544,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
returnFocusOnDeactivate: false,
|
||||
clickOutsideDeactivates: true,
|
||||
onDeactivate: () => setViewTopic(false),
|
||||
escapeDeactivates: stopPropagation,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Scroll,
|
||||
Spinner,
|
||||
Text,
|
||||
config,
|
||||
} from 'folds';
|
||||
import { MatrixEvent, Method, Room } from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
@@ -22,17 +23,48 @@ import { timeDayMonYear, timeHourMinute } from '../../../utils/time';
|
||||
import { useSetting } from '../../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../../state/settings';
|
||||
|
||||
type RawEditEvent = {
|
||||
type: string;
|
||||
content: Record<string, unknown>;
|
||||
origin_server_ts: number;
|
||||
event_id: string;
|
||||
};
|
||||
|
||||
type EditHistoryResponse = {
|
||||
chunk: Array<Record<string, unknown>>;
|
||||
next_batch?: string;
|
||||
};
|
||||
|
||||
type EditHistoryData = {
|
||||
events: MatrixEvent[];
|
||||
hasMore: boolean;
|
||||
};
|
||||
|
||||
type EditHistoryModalProps = {
|
||||
room: Room;
|
||||
mEvent: MatrixEvent;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function isRawEditEvent(raw: unknown): raw is RawEditEvent {
|
||||
if (typeof raw !== 'object' || raw === null) return false;
|
||||
const r = raw as Record<string, unknown>;
|
||||
return typeof r.event_id === 'string' && typeof r.origin_server_ts === 'number';
|
||||
}
|
||||
|
||||
function getVersionBody(evt: MatrixEvent): string {
|
||||
const content = evt.getContent();
|
||||
const newContent = content['m.new_content'] as Record<string, unknown> | undefined;
|
||||
const source = newContent ?? content;
|
||||
|
||||
const formattedBody = source.formatted_body;
|
||||
if (typeof formattedBody === 'string') {
|
||||
return formattedBody.replace(/<[^>]+>/g, '').trim() || '(no text)';
|
||||
}
|
||||
const body = source.body;
|
||||
return typeof body === 'string' ? body : '(no text)';
|
||||
}
|
||||
|
||||
export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProps) {
|
||||
const mx = useMatrixClient();
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
@@ -41,43 +73,32 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
const eventId = mEvent.getId();
|
||||
const roomId = room.roomId;
|
||||
|
||||
const [historyState, fetchHistory] = useAsyncCallback<MatrixEvent[], unknown, []>(
|
||||
const [historyState, fetchHistory] = useAsyncCallback<EditHistoryData, unknown, []>(
|
||||
useCallback(async () => {
|
||||
const path = `/rooms/${encodeURIComponent(roomId)}/relations/${encodeURIComponent(eventId ?? '')}/m.replace`;
|
||||
if (!eventId) return { events: [], hasMore: false };
|
||||
|
||||
const path = `/rooms/${encodeURIComponent(roomId)}/relations/${encodeURIComponent(eventId)}/m.replace`;
|
||||
const res = await mx.http.authedRequest<EditHistoryResponse>(Method.Get, path, {
|
||||
limit: '50',
|
||||
});
|
||||
const rawEvents = res.chunk ?? [];
|
||||
// Sort oldest first
|
||||
const events = rawEvents
|
||||
.map(
|
||||
(raw) =>
|
||||
// Build a lightweight representation for display; we just need content + ts
|
||||
raw as {
|
||||
type: string;
|
||||
content: Record<string, unknown>;
|
||||
origin_server_ts: number;
|
||||
event_id: string;
|
||||
},
|
||||
)
|
||||
.sort((a, b) => a.origin_server_ts - b.origin_server_ts);
|
||||
|
||||
// Convert to MatrixEvent-like objects using the raw data
|
||||
// We use MatrixEvent if available from the timeline, otherwise parse the raw data
|
||||
return events.map((raw) => {
|
||||
const existing = room.findEventById(raw.event_id);
|
||||
if (existing) return existing;
|
||||
// Create a minimal event wrapper
|
||||
const evt = new MatrixEvent({
|
||||
type: raw.type,
|
||||
content: raw.content,
|
||||
origin_server_ts: raw.origin_server_ts,
|
||||
event_id: raw.event_id,
|
||||
room_id: roomId,
|
||||
sender: mEvent.getSender() ?? '',
|
||||
.filter(isRawEditEvent)
|
||||
.sort((a, b) => a.origin_server_ts - b.origin_server_ts)
|
||||
.map((raw) => {
|
||||
const existing = room.findEventById(raw.event_id);
|
||||
if (existing) return existing;
|
||||
return new MatrixEvent({
|
||||
type: raw.type,
|
||||
content: raw.content,
|
||||
origin_server_ts: raw.origin_server_ts,
|
||||
event_id: raw.event_id,
|
||||
room_id: roomId,
|
||||
sender: mEvent.getSender() ?? '',
|
||||
});
|
||||
});
|
||||
return evt;
|
||||
});
|
||||
|
||||
return { events, hasMore: !!res.next_batch };
|
||||
}, [mx, roomId, eventId, room, mEvent]),
|
||||
);
|
||||
|
||||
@@ -91,14 +112,6 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
return `${date} at ${time}`;
|
||||
};
|
||||
|
||||
const getVersionBody = (evt: MatrixEvent): string => {
|
||||
const content = evt.getContent();
|
||||
const newContent = content['m.new_content'] as Record<string, unknown> | undefined;
|
||||
const body = newContent?.body ?? content.body;
|
||||
return typeof body === 'string' ? body : '(no text)';
|
||||
};
|
||||
|
||||
// The original message body (before any edits)
|
||||
const originalBody = (() => {
|
||||
const content = mEvent.getContent();
|
||||
const body = content.body;
|
||||
@@ -118,14 +131,20 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Modal variant="Surface" size="500">
|
||||
<Modal
|
||||
variant="Surface"
|
||||
size="500"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="edit-history-title"
|
||||
>
|
||||
<Header
|
||||
variant="Surface"
|
||||
size="500"
|
||||
style={{ padding: '0 var(--mx-spacing-s200) 0 var(--mx-spacing-s400)' }}
|
||||
style={{ padding: `0 ${config.space.S200} 0 ${config.space.S400}` }}
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text as="h2" size="H4" truncate>
|
||||
<Text as="h2" id="edit-history-title" size="H4" truncate>
|
||||
Edit History
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -139,15 +158,15 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
direction="Column"
|
||||
gap="200"
|
||||
style={{
|
||||
padding: 'var(--mx-spacing-s400)',
|
||||
paddingBottom: 'var(--mx-spacing-s700)',
|
||||
padding: config.space.S400,
|
||||
paddingBottom: config.space.S700,
|
||||
}}
|
||||
>
|
||||
{historyState.status === AsyncStatus.Loading && (
|
||||
<Box
|
||||
justifyContent="Center"
|
||||
alignItems="Center"
|
||||
style={{ padding: 'var(--mx-spacing-s400)' }}
|
||||
style={{ padding: config.space.S400 }}
|
||||
>
|
||||
<Spinner size="200" />
|
||||
</Box>
|
||||
@@ -159,7 +178,6 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
)}
|
||||
{historyState.status === AsyncStatus.Success && (
|
||||
<Box direction="Column" gap="300">
|
||||
{/* Original message always shown first */}
|
||||
<Box direction="Column" gap="100">
|
||||
<Box gap="200" alignItems="Center">
|
||||
<Text size="L400">Original</Text>
|
||||
@@ -172,11 +190,11 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{historyState.data.map((editEvt, index) => (
|
||||
{historyState.data.events.map((editEvt, index) => (
|
||||
<Box key={editEvt.getId() ?? index} direction="Column" gap="100">
|
||||
<Box gap="200" alignItems="Center">
|
||||
<Text size="L400">
|
||||
{index === historyState.data.length - 1
|
||||
{index === historyState.data.events.length - 1
|
||||
? `Edit ${index + 1} (current)`
|
||||
: `Edit ${index + 1}`}
|
||||
</Text>
|
||||
@@ -193,11 +211,19 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{historyState.data.length === 0 && (
|
||||
{historyState.data.events.length === 0 && (
|
||||
<Text size="T300" priority="300">
|
||||
No edit history found.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{historyState.data.hasMore && (
|
||||
<Box justifyContent="Center" style={{ padding: config.space.S200 }}>
|
||||
<Text size="T200" priority="300">
|
||||
Showing the 50 most recent edits
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user