051d207079
CI / Build & Quality Checks (push) Failing after 5m43s
- 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>
141 lines
4.3 KiB
TypeScript
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;
|
|
};
|