Security, performance, bug fixes, and TDS improvements
Security: - HIGH-1: Validate hex color format before CSS interpolation in sanitize.ts - HIGH-5: Add sandbox attribute to OpenStreetMap iframe - MED-1: Fix permissive URL scheme regex in LINKIFY_OPTS - MED-3/HIGH-4: Add .js.map blocking + CSP header to nginx config - LOW-2: Validate OIDC authUrl scheme before window.open - Accessibility: Remove maximum-scale=1.0 from viewport meta (WCAG 1.4.4) Performance: - O(1) Map index in computePositions (was O(M×T) findIndex per member) - Add RoomMemberEvent.Membership subscription so positions update on join/leave - Fix uncleaned 2000ms setTimeout in RoomTimeline useLayoutEffect Bug fixes: - BUG-5: Add QUEUED/CANCELLED cases to DeliveryStatus component - BUG-6: Guard DeliveryStatus against state events via isState() check - BUG-10: Clamp PiP position on window resize - BUG-14: Separate runLotusBootSequence into dedicated useEffect([lotusTerminal]) - Fix aria-live on typing indicator (WCAG 4.1.3) - Add aria-label + aria-multiline to message editor TDS (Lotus Terminal Design System): - Add reaction chip styles (dark + light mode) - Add GIF picker CSS via globalStyle instead of runtime injection - Add URL preview styles (dark + light mode) - Add complete GIF picker light-mode TDS block (was missing)
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
import { Room, RoomEvent, MatrixEvent } from 'matrix-js-sdk';
|
||||
import { Room, RoomEvent, RoomMemberEvent, MatrixEvent } from 'matrix-js-sdk';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { reactionOrEditEvent } from '../utils/room';
|
||||
|
||||
// Receipts can land on reaction/edit events which RoomTimeline skips (renders null).
|
||||
// Walk backwards from the receipt event to find the nearest event that IS rendered.
|
||||
function nearestRenderableId(liveEvents: MatrixEvent[], evtId: string): string | null {
|
||||
const idx = liveEvents.findIndex(e => e.getId() === evtId);
|
||||
function nearestRenderableId(
|
||||
liveEvents: MatrixEvent[],
|
||||
eventIndex: Map<string, number>,
|
||||
evtId: string
|
||||
): string | null {
|
||||
const idx = eventIndex.get(evtId) ?? -1;
|
||||
if (idx === -1) return null;
|
||||
for (let i = idx; i >= 0; i--) {
|
||||
const e = liveEvents[i];
|
||||
@@ -18,11 +22,15 @@ function nearestRenderableId(liveEvents: MatrixEvent[], evtId: string): string |
|
||||
function computePositions(room: Room, myUserId: string): Map<string, string[]> {
|
||||
const map = new Map<string, string[]>();
|
||||
const liveEvents = room.getLiveTimeline().getEvents();
|
||||
// Build O(1) index once instead of O(T) findIndex per member
|
||||
const eventIndex = new Map<string, number>(
|
||||
liveEvents.map((e, i) => [e.getId() ?? '', i])
|
||||
);
|
||||
for (const member of room.getJoinedMembers()) {
|
||||
if (member.userId === myUserId) continue;
|
||||
const evtId = room.getEventReadUpTo(member.userId);
|
||||
if (!evtId) continue;
|
||||
const targetId = nearestRenderableId(liveEvents, evtId);
|
||||
const targetId = nearestRenderableId(liveEvents, eventIndex, evtId);
|
||||
if (!targetId) continue;
|
||||
const arr = map.get(targetId);
|
||||
if (arr) arr.push(member.userId);
|
||||
@@ -46,10 +54,13 @@ export function useRoomReadPositions(room: Room): Map<string, string[]> {
|
||||
debounceTimer = null;
|
||||
}, 150);
|
||||
};
|
||||
const onMembership = (): void => setPositions(computePositions(room, myUserId));
|
||||
room.on(RoomEvent.Receipt, onReceipt);
|
||||
room.on(RoomMemberEvent.Membership, onMembership);
|
||||
return () => {
|
||||
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
||||
room.removeListener(RoomEvent.Receipt, onReceipt);
|
||||
room.removeListener(RoomMemberEvent.Membership, onMembership);
|
||||
};
|
||||
}, [room, myUserId]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user