diff --git a/src/app/components/message/Reply.tsx b/src/app/components/message/Reply.tsx
index 28096b507..477c66e79 100644
--- a/src/app/components/message/Reply.tsx
+++ b/src/app/components/message/Reply.tsx
@@ -118,11 +118,7 @@ export const Reply = as<'div', ReplyProps>(
data-event-id={replyEventId}
onClick={onClick}
>
- {replyEvent !== undefined ? (
-
- {badEncryption ? : bodyJSX}
-
- ) : (
+ {replyEvent === undefined ? (
(
maxWidth: '100%',
}}
/>
+ ) : replyEvent === null ? (
+
+ Original message not available
+
+ ) : (
+
+ {badEncryption ? : bodyJSX}
+
)}
diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx
index c069a46e0..690a2b5c3 100644
--- a/src/app/features/room/RoomInput.tsx
+++ b/src/app/features/room/RoomInput.tsx
@@ -24,6 +24,7 @@ import {
OverlayCenter,
PopOut,
Scroll,
+ Spinner,
Text,
config,
toRem,
@@ -183,8 +184,13 @@ export const RoomInput = forwardRef(
const [toolbar, setToolbar] = useSetting(settingsAtom, 'editorToolbar');
const [locating, setLocating] = React.useState(false);
+ const [locationError, setLocationError] = React.useState(null);
const handleShareLocation = () => {
- if (!navigator.geolocation) return;
+ if (!navigator.geolocation) {
+ setLocationError('Geolocation not supported.');
+ setTimeout(() => setLocationError(null), 4000);
+ return;
+ }
setLocating(true);
navigator.geolocation.getCurrentPosition(
(pos) => {
@@ -197,7 +203,17 @@ export const RoomInput = forwardRef(
geo_uri: geoUri,
} as any);
},
- () => setLocating(false),
+ (err) => {
+ setLocating(false);
+ const msg =
+ err.code === 1
+ ? 'Location access denied.'
+ : err.code === 3
+ ? 'Location timed out.'
+ : 'Failed to get location.';
+ setLocationError(msg);
+ setTimeout(() => setLocationError(null), 4000);
+ },
{ timeout: 10000 },
);
};
@@ -858,8 +874,22 @@ export const RoomInput = forwardRef(
{gifError}
)}
+ {locationError && (
+
+ {locationError}
+
+ )}
(
title="Share location"
>
{locating ? (
-
- ...
-
+
) : (
)}
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index c736f2c59..1691063a1 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -1189,6 +1189,52 @@ export const Message = React.memo(
)}
+ {(mEvent.status === EventStatus.NOT_SENT ||
+ mEvent.status === EventStatus.CANCELLED) && (
+ <>
+
+
+ }
+ radii="300"
+ onClick={() => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (mx as any).resendEvent(mEvent, room);
+ closeMenu();
+ }}
+ >
+
+ Retry Send
+
+
+ }
+ radii="300"
+ onClick={() => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (mx as any).cancelPendingEvent(mEvent);
+ closeMenu();
+ }}
+ >
+
+ Cancel Message
+
+
+
+ >
+ )}
{((!mEvent.isRedacted() && canDelete) ||
mEvent.getSender() !== mx.getUserId()) && (
<>
diff --git a/src/app/features/room/reaction-viewer/ReactionViewer.tsx b/src/app/features/room/reaction-viewer/ReactionViewer.tsx
index 9561979f9..434599cf7 100644
--- a/src/app/features/room/reaction-viewer/ReactionViewer.tsx
+++ b/src/app/features/room/reaction-viewer/ReactionViewer.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useRef, useState } from 'react';
import classNames from 'classnames';
import {
Avatar,
@@ -51,6 +51,21 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
const defaultReaction = reactions.find((reaction) => typeof reaction[0] === 'string');
return defaultReaction ? defaultReaction[0] : '';
});
+ const sidebarRef = useRef(null);
+
+ const handleSidebarKeyDown = (e: React.KeyboardEvent) => {
+ const keys = reactions.map(([k]) => k).filter((k): k is string => typeof k === 'string');
+ const currentIdx = keys.indexOf(selectedKey);
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ const next = keys[(currentIdx + 1) % keys.length];
+ if (next) setSelectedKey(next);
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ const prev = keys[(currentIdx - 1 + keys.length) % keys.length];
+ if (prev) setSelectedKey(prev);
+ }
+ };
const getName = (member: RoomMember) =>
getMemberDisplayName(room, member.userId) ?? getMxIdLocalPart(member.userId) ?? member.userId;
@@ -74,7 +89,16 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
{...props}
ref={ref}
>
-
+
{reactions.map(([key, evts]) => {
@@ -85,6 +109,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
mx={mx}
reaction={key}
count={evts.size}
+ role="option"
aria-selected={key === selectedKey}
onClick={() => setSelectedKey(key)}
useAuthentication={useAuthentication}