From 243a1c78c152e98515a6114b6634899d081e8112 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 15 May 2026 14:13:41 -0400 Subject: [PATCH] Post-merge fixes: remove duplicate IncomingCallNotification, restore PiP touch drag + grip dots, show redacted message content - Router.tsx: remove IncomingCallNotification (CallEmbedProvider.IncomingCallListener now handles all calls) - CallEmbedProvider: restore touch drag (handlePipTouchStart), grip dots on resize handles, fix normaliseToTopLeft width/height - FallbackContent/MsgTypeRenderers: add originalBody prop to show struck-through original text on deleted messages - RoomTimeline: cache text message bodies so they can be shown after redaction --- src/app/components/CallEmbedProvider.tsx | 49 +++++++++++++++++-- .../components/message/MsgTypeRenderers.tsx | 5 +- .../message/content/FallbackContent.tsx | 8 +-- src/app/features/room/RoomTimeline.tsx | 15 +++++- src/app/pages/Router.tsx | 2 - 5 files changed, 67 insertions(+), 12 deletions(-) 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>(new Map()); + const [focusItem, setFocusItem] = useState< | { index: number; @@ -1035,6 +1037,14 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli const getContent = (() => editedEvent?.getContent()['m.new_content'] ?? mEvent.getContent()) as GetContentCallback; + // Cache body before it can be stripped by redaction + if (!mEvent.isRedacted()) { + const c = mEvent.getContent(); + if (c.body && ['m.text', 'm.notice', 'm.emote'].includes(c.msgtype ?? '')) { + redactedBodyCache.current.set(mEventId, c.body); + } + } + const senderId = mEvent.getSender() ?? ''; const senderDisplayName = getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId; @@ -1096,7 +1106,10 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli dateFormatString={dateFormatString} > {mEvent.isRedacted() ? ( - + ) : ( { @@ -138,7 +137,6 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) -