fix: location NaN guard, PiP drag unmount cleanup, README v4.12.1
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Lotus Chat
|
# 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).
|
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
|
- Listens on both main window and EC iframe `contentWindow` for reliable key capture
|
||||||
- Implemented via `CallControl.setMicrophone()` public method on the widget bridge
|
- 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
|
- **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`.
|
- **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`.
|
- **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.
|
- **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.
|
||||||
|
|||||||
@@ -398,6 +398,8 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
const pipDragRef = React.useRef<{
|
const pipDragRef = React.useRef<{
|
||||||
startX: number; startY: number; origLeft: number; origTop: number; dragged: boolean;
|
startX: number; startY: number; origLeft: number; origTop: number; dragged: boolean;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
const activeDragCleanupRef = React.useRef<(() => void) | null>(null);
|
||||||
|
React.useEffect(() => () => { activeDragCleanupRef.current?.(); }, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const el = callEmbedRef.current;
|
const el = callEmbedRef.current;
|
||||||
@@ -429,7 +431,8 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
el.style.right = 'auto'; el.style.bottom = 'auto';
|
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);
|
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -455,8 +458,10 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
const onTouchEnd = () => {
|
const onTouchEnd = () => {
|
||||||
document.removeEventListener('touchmove', onTouchMove);
|
document.removeEventListener('touchmove', onTouchMove);
|
||||||
document.removeEventListener('touchend', onTouchEnd);
|
document.removeEventListener('touchend', onTouchEnd);
|
||||||
|
activeDragCleanupRef.current = null;
|
||||||
setTimeout(() => { if (pipDragRef.current) pipDragRef.current.dragged = false; }, 0);
|
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('touchmove', onTouchMove, { passive: false });
|
||||||
document.addEventListener('touchend', onTouchEnd);
|
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));
|
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`;
|
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);
|
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -393,6 +393,7 @@ export function MLocation({ content }: MLocationProps) {
|
|||||||
|
|
||||||
const lat = parseFloat(location.latitude);
|
const lat = parseFloat(location.latitude);
|
||||||
const lon = parseFloat(location.longitude);
|
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}`;
|
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 (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user