Files
cinny/src/app/hooks/useRoomMeta.ts
T
jared 051d207079
CI / Build & Quality Checks (push) Failing after 5m43s
fix: comprehensive P0 quality pass — audit findings resolved
- ReportRoomModal: use mx.reportRoom() SDK method, fix undefined CSS vars
  (--mx-surface/border → folds color tokens), add role/aria-modal/aria-labelledby,
  accessible select/input labels, per-error-code messages, auto-close on success
- About.tsx: clickable matrix_id + email_address links (Text as="a"), AbortController
  cleanup, runtime JSON type guard, loading state, role display for all role values,
  remove classList theming hack, use mx.getHomeserverUrl()
- RoomViewHeader: useLocalRoomName for header title, useReportRoomSupported gate,
  hide Invite/Settings/Report for server notice rooms, isCreator guard on Report,
  FocusTrap returnFocusOnDeactivate on topic overlay, Server Notice chip tooltip
- RoomInput: replace raw <div> with folds <Box> for server notice read-only message
- EditHistoryModal: isRawEditEvent type guard, handle next_batch truncation,
  getVersionBody handles formatted_body (strips HTML for text display),
  role/aria-modal/aria-labelledby accessibility, guard for undefined eventId,
  use config.space tokens (remove var(--mx-spacing-*) strings)
- RoomNavItem: remove duplicate getExistingContent (use exported getLocalRoomNamesContent),
  maxLength={255} on rename input, fix FocusTrap nesting (renameDialog state moved to
  RoomNavItem_, RenameRoomDialog rendered outside menu, menu closes before dialog opens),
  pencil icon opacity via config.opacity.P300
- useRoomMeta: export getLocalRoomNamesContent for reuse
- RoomIntro: useLocalRoomName, formatted topic viewer with Overlay/FocusTrap/RoomTopicViewer
- CallRoomName: useLocalRoomName for consistent rename display in call overlay
- General.tsx: fix #980000/#FF6B00 hardcoded hex → color tokens/CSS vars, URL Preview
  capitalization, improved encrypted preview warning text + Warning chip, add
  description to plain urlPreview setting
- sanitize.ts: fix hex color regex to support 3/4/6/8 digit hex (CSS4 #RGBA, #RRGGBBAA)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:30:27 -04:00

141 lines
4.3 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types';
import { ClientEvent, MatrixEvent, Room, RoomEvent, RoomEventHandlerMap } from 'matrix-js-sdk';
import { StateEvent } from '../../types/matrix/room';
import { useStateEvent } from './useStateEvent';
import { useMatrixClient } from './useMatrixClient';
export const useRoomAvatar = (room: Room, dm?: boolean): string | undefined => {
const avatarEvent = useStateEvent(room, StateEvent.RoomAvatar);
if (dm) {
return room.getAvatarFallbackMember()?.getMxcAvatarUrl();
}
const content = avatarEvent?.getContent();
const avatarMxc = content && typeof content.url === 'string' ? content.url : undefined;
return avatarMxc;
};
export const useRoomName = (room: Room): string => {
const [name, setName] = useState(room.name);
useEffect(() => {
setName(room.name);
const handleRoomNameChange: RoomEventHandlerMap[RoomEvent.Name] = () => {
setName(room.name);
};
room.on(RoomEvent.Name, handleRoomNameChange);
return () => {
room.removeListener(RoomEvent.Name, handleRoomNameChange);
};
}, [room]);
return name;
};
export const LOCAL_ROOM_NAMES_KEY = 'io.lotus.room_names';
export type LocalRoomNamesContent = { rooms: Record<string, string> };
export function getLocalRoomNamesContent(mx: ReturnType<typeof useMatrixClient>): LocalRoomNamesContent {
// Use any-cast because LOCAL_ROOM_NAMES_KEY is not in matrix-js-sdk AccountDataEvents
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const raw: unknown = (mx as any).getAccountData(LOCAL_ROOM_NAMES_KEY)?.getContent();
if (
raw &&
typeof raw === 'object' &&
'rooms' in raw &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any
typeof (raw as any).rooms === 'object'
) {
return raw as LocalRoomNamesContent;
}
return { rooms: {} };
}
export const useLocalRoomName = (room: Room): string => {
const mx = useMatrixClient();
const getLocalName = useCallback((): string => {
const content = getLocalRoomNamesContent(mx);
return content.rooms[room.roomId] ?? room.name;
}, [mx, room]);
const [name, setName] = useState(getLocalName);
useEffect(() => {
setName(getLocalName());
const handleAccountData = (event: MatrixEvent) => {
if (event.getType() !== LOCAL_ROOM_NAMES_KEY) return;
setName(getLocalName());
};
mx.on(ClientEvent.AccountData, handleAccountData);
const handleRoomNameChange: RoomEventHandlerMap[RoomEvent.Name] = () => {
setName(getLocalName());
};
room.on(RoomEvent.Name, handleRoomNameChange);
return () => {
mx.removeListener(ClientEvent.AccountData, handleAccountData);
room.removeListener(RoomEvent.Name, handleRoomNameChange);
};
}, [mx, room, getLocalName]);
return name;
};
export const useHasLocalRoomName = (roomId: string): boolean => {
const mx = useMatrixClient();
const check = useCallback((): boolean => {
const content = getLocalRoomNamesContent(mx);
return !!content.rooms[roomId];
}, [mx, roomId]);
const [hasLocal, setHasLocal] = useState(check);
useEffect(() => {
setHasLocal(check());
const handleAccountData = (event: MatrixEvent) => {
if (event.getType() !== LOCAL_ROOM_NAMES_KEY) return;
setHasLocal(check());
};
mx.on(ClientEvent.AccountData, handleAccountData);
return () => {
mx.removeListener(ClientEvent.AccountData, handleAccountData);
};
}, [mx, check]);
return hasLocal;
};
export type RoomTopicContent = {
topic: string;
formatted_body?: string;
format?: string;
};
export const useRoomTopic = (room: Room): RoomTopicContent | undefined => {
const topicEvent = useStateEvent(room, StateEvent.RoomTopic);
const content = topicEvent?.getContent();
if (!content || typeof content.topic !== 'string') return undefined;
return {
topic: content.topic,
formatted_body: typeof content.formatted_body === 'string' ? content.formatted_body : undefined,
format: typeof content.format === 'string' ? content.format : undefined,
};
};
export const useRoomJoinRule = (room: Room): RoomJoinRulesEventContent | undefined => {
const mEvent = useStateEvent(room, StateEvent.RoomJoinRules);
const joinRuleContent = mEvent?.getContent<RoomJoinRulesEventContent>();
return joinRuleContent;
};