diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx
index b6b5bf885..8565cde99 100644
--- a/src/app/components/CallEmbedProvider.tsx
+++ b/src/app/components/CallEmbedProvider.tsx
@@ -68,6 +68,8 @@ function normaliseToTopLeft(el: HTMLElement) {
const rect = el.getBoundingClientRect();
el.style.left = `${rect.left}px`;
el.style.top = `${rect.top}px`;
+ el.style.width = `${rect.width}px`;
+ el.style.height = `${rect.height}px`;
el.style.right = 'auto';
el.style.bottom = 'auto';
}
@@ -431,6 +433,34 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
};
+ const handlePipTouchStart = (e: React.TouchEvent) => {
+ const el = callEmbedRef.current;
+ if (!el || e.touches.length !== 1) return;
+ const touch = e.touches[0];
+ const rect = el.getBoundingClientRect();
+ pipDragRef.current = { startX: touch.clientX, startY: touch.clientY, origLeft: rect.left, origTop: rect.top, dragged: false };
+ const onTouchMove = (ev: TouchEvent) => {
+ if (!pipDragRef.current || !el || ev.touches.length !== 1) return;
+ ev.preventDefault();
+ const t = ev.touches[0];
+ const dx = t.clientX - pipDragRef.current.startX;
+ const dy = t.clientY - pipDragRef.current.startY;
+ if (!pipDragRef.current.dragged && Math.abs(dx) + Math.abs(dy) > 5) pipDragRef.current.dragged = true;
+ if (pipDragRef.current.dragged) {
+ el.style.left = `${Math.max(0, Math.min(window.innerWidth - el.offsetWidth, pipDragRef.current.origLeft + dx))}px`;
+ el.style.top = `${Math.max(0, Math.min(window.innerHeight - el.offsetHeight, pipDragRef.current.origTop + dy))}px`;
+ el.style.right = 'auto'; el.style.bottom = 'auto';
+ }
+ };
+ const onTouchEnd = () => {
+ document.removeEventListener('touchmove', onTouchMove);
+ document.removeEventListener('touchend', onTouchEnd);
+ setTimeout(() => { if (pipDragRef.current) pipDragRef.current.dragged = false; }, 0);
+ };
+ document.addEventListener('touchmove', onTouchMove, { passive: false });
+ document.addEventListener('touchend', onTouchEnd);
+ };
+
const handleResizeMouseDown = (e: React.MouseEvent, corner: Corner) => {
e.stopPropagation(); e.preventDefault();
const el = callEmbedRef.current; if (!el) return;
@@ -477,6 +507,7 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
tabIndex={0}
aria-label="Return to call"
onMouseDown={handlePipMouseDown}
+ onTouchStart={handlePipTouchStart}
onClick={() => { if (!pipDragRef.current?.dragged) navigateRoom(callEmbed.roomId); }}
onKeyDown={(e) => e.key === 'Enter' && navigateRoom(callEmbed.roomId)}
style={{ position:'absolute', inset:0, zIndex:1, background:'transparent', cursor:'grab', display:'flex', alignItems:'flex-start', justifyContent:'flex-end', padding:'6px' }}
@@ -485,10 +516,20 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
↗ Return to call
- {(['se','sw','ne','nw'] as Corner[]).map((corner) => (
-
handleResizeMouseDown(e, corner)} onClick={(e) => e.stopPropagation()}
- style={{ position:'absolute', width:'18px', height:'18px', [corner.includes('s')?'bottom':'top']:0, [corner.includes('e')?'right':'left']:0, cursor:`${corner}-resize`, zIndex:2 }} />
- ))}
+ {(['se','sw','ne','nw'] as Corner[]).map((corner) => {
+ const s = corner.includes('s'); const e2 = corner.includes('e');
+ const dots = [[2,2],[2,7],[7,2]].map(([a,b]) => ({
+ position:'absolute' as const, width:4, height:4, borderRadius:'50%',
+ background:'rgba(255,255,255,0.45)',
+ [s?'bottom':'top']:a, [e2?'right':'left']:b,
+ }));
+ return (
+
handleResizeMouseDown(ev, corner)} onClick={(ev) => ev.stopPropagation()}
+ style={{ position:'absolute', width:'18px', height:'18px', [s?'bottom':'top']:0, [e2?'right':'left']:0, cursor:`${corner}-resize`, zIndex:2 }}>
+ {dots.map((style, i) =>
)}
+
+ );
+ })}
>
)}
diff --git a/src/app/components/message/MsgTypeRenderers.tsx b/src/app/components/message/MsgTypeRenderers.tsx
index fe1806018..668073c36 100644
--- a/src/app/components/message/MsgTypeRenderers.tsx
+++ b/src/app/components/message/MsgTypeRenderers.tsx
@@ -40,11 +40,12 @@ export function MBadEncrypted() {
type RedactedContentProps = {
reason?: string;
+ originalBody?: string;
};
-export function RedactedContent({ reason }: RedactedContentProps) {
+export function RedactedContent({ reason, originalBody }: RedactedContentProps) {
return (
-
+
);
}
diff --git a/src/app/components/message/content/FallbackContent.tsx b/src/app/components/message/content/FallbackContent.tsx
index 9edb96784..aded32834 100644
--- a/src/app/components/message/content/FallbackContent.tsx
+++ b/src/app/components/message/content/FallbackContent.tsx
@@ -4,11 +4,13 @@ import React from 'react';
const warningStyle = { color: color.Warning.Main, opacity: config.opacity.P300 };
const criticalStyle = { color: color.Critical.Main, opacity: config.opacity.P300 };
-export const MessageDeletedContent = as<'div', { children?: never; reason?: string }>(
- ({ reason, ...props }, ref) => (
+export const MessageDeletedContent = as<'div', { children?: never; reason?: string; originalBody?: string }>(
+ ({ reason, originalBody, ...props }, ref) => (
- {reason ? (
+ {originalBody ? (
+ {originalBody.length > 80 ? `${originalBody.slice(0, 80)}…` : originalBody}
+ ) : reason ? (
This message has been deleted. {reason}
) : (
This message has been deleted
diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx
index 4e29b9908..185175627 100644
--- a/src/app/features/room/RoomTimeline.tsx
+++ b/src/app/features/room/RoomTimeline.tsx
@@ -506,6 +506,8 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
smooth: true,
});
+ const redactedBodyCache = useRef