fix: location NaN guard, PiP drag unmount cleanup, README v4.12.1

This commit is contained in:
root
2026-05-15 15:56:43 -04:00
parent 5418b9e188
commit ab9b02a243
3 changed files with 11 additions and 4 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# Lotus Chat
A Matrix client for [Lotus Guild](https://lotusguild.org) — forked from [Cinny](https://github.com/cinnyapp/cinny) v4.11.1.
A Matrix client for [Lotus Guild](https://lotusguild.org) — forked from [Cinny](https://github.com/cinnyapp/cinny) v4.12.1.
Deployed at [chat.lotusguild.org](https://chat.lotusguild.org).
@@ -58,7 +58,7 @@ A full custom theme engine layered on top of Cinny's vanilla-extract theming:
- Listens on both main window and EC iframe `contentWindow` for reliable key capture
- Implemented via `CallControl.setMicrophone()` public method on the widget bridge
- **Noise suppression toggle**: Settings > General > Calls — passes `noiseSuppression` URL parameter to the embedded Element Call widget
- **Call button scoping**: The upstream Cinny 4.12.1 call button (voice + video dropdown) is restricted to DMs and invite-only rooms only (`join_rule: invite`). Public rooms and space-restricted channels are excluded to prevent accidental mass-notifications in large communities. `Room.tsx` switches to CallView layout when a call embed is active in the current room.
- **Call button scoping**: The upstream Cinny 4.12.1 call button (voice + video dropdown) is restricted to DMs and private group chats only. Specifically: direct messages, or invite-only rooms that have no `m.space.parent` state event (i.e. not a space/guild text channel). Public rooms and space channels are excluded to prevent accidental mass-notifications. `Room.tsx` switches to CallView layout when a call embed is active in the current room.
- **Poll display**: `m.poll.start` events (both stable Matrix 1.7 `m.poll` content key and MSC3381 unstable `org.matrix.msc3381.poll.start`) render as read-only poll cards inside the standard message bubble — question and answer options shown. Registered as top-level event renderers AND inside the `EncryptedContent` callback so encrypted polls also display after decryption. "Open in Element to vote" note displayed. Implemented in `PollContent.tsx`.
- **Deleted message placeholder**: Redacted `m.room.message`, `m.room.encrypted`, and `m.sticker` events no longer disappear from the timeline. Instead they reach the existing `RedactedContent` component (trash icon + italic "This message has been deleted" with reason if provided), matching Element, FluffyChat, Commet, and Nheko behaviour. One-line change in the `eventRenderer` filter in `RoomTimeline.tsx`.
- **Picture-in-picture (PiP)**: When navigating away from a call room while in an active call, the call embed shrinks to a 280x158px floating window in the bottom-right corner. The PiP window is **draggable** — drag it anywhere on screen to move it out of the way. Clicking (without dragging) navigates back to the call room. Drag vs click distinguished by a 5px movement threshold; touch drag supported. Imperative style overrides on `callEmbedRef.current` via `useEffect` — a wrapper div cannot be used because `useCallEmbedPlacementSync` writes `top/left/width/height` directly onto that element.
+8 -2
View File
@@ -398,6 +398,8 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
const pipDragRef = React.useRef<{
startX: number; startY: number; origLeft: number; origTop: number; dragged: boolean;
} | null>(null);
const activeDragCleanupRef = React.useRef<(() => void) | null>(null);
React.useEffect(() => () => { activeDragCleanupRef.current?.(); }, []);
React.useEffect(() => {
const el = callEmbedRef.current;
@@ -429,7 +431,8 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
el.style.right = 'auto'; el.style.bottom = 'auto';
}
};
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); document.body.style.cursor = ''; document.body.style.userSelect = ''; setTimeout(() => { if (pipDragRef.current) pipDragRef.current.dragged = false; }, 0); };
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); document.body.style.cursor = ''; document.body.style.userSelect = ''; activeDragCleanupRef.current = null; setTimeout(() => { if (pipDragRef.current) pipDragRef.current.dragged = false; }, 0); };
activeDragCleanupRef.current = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); document.body.style.cursor = ''; document.body.style.userSelect = ''; };
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
};
@@ -455,8 +458,10 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
const onTouchEnd = () => {
document.removeEventListener('touchmove', onTouchMove);
document.removeEventListener('touchend', onTouchEnd);
activeDragCleanupRef.current = null;
setTimeout(() => { if (pipDragRef.current) pipDragRef.current.dragged = false; }, 0);
};
activeDragCleanupRef.current = () => { document.removeEventListener('touchmove', onTouchMove); document.removeEventListener('touchend', onTouchEnd); };
document.addEventListener('touchmove', onTouchMove, { passive: false });
document.addEventListener('touchend', onTouchEnd);
};
@@ -477,7 +482,8 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
l=Math.max(0,Math.min(l,window.innerWidth-PIP_MIN_W)); t=Math.max(0,Math.min(t,window.innerHeight-PIP_MIN_H));
el.style.width=`${w}px`; el.style.height=`${h}px`; el.style.left=`${l}px`; el.style.top=`${t}px`;
};
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); document.body.style.cursor=''; document.body.style.userSelect=''; };
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); document.body.style.cursor=''; document.body.style.userSelect=''; activeDragCleanupRef.current = null; };
activeDragCleanupRef.current = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); document.body.style.cursor=''; document.body.style.userSelect=''; };
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
};
@@ -393,6 +393,7 @@ export function MLocation({ content }: MLocationProps) {
const lat = parseFloat(location.latitude);
const lon = parseFloat(location.longitude);
if (!isFinite(lat) || !isFinite(lon)) return <BrokenContent />;
const mapSrc = `https://www.openstreetmap.org/export/embed.html?bbox=${lon - 0.007},${lat - 0.004},${lon + 0.007},${lat + 0.004}&layer=mapnik&marker=${lat},${lon}`;
return (