diff --git a/.changeset/fix-encrypted-message-power-level.md b/.changeset/fix-encrypted-message-power-level.md new file mode 100644 index 000000000..52d20b3bb --- /dev/null +++ b/.changeset/fix-encrypted-message-power-level.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Allow users to send messages in encrypted rooms when they have permission to send `m.room.encrypted` events. diff --git a/.changeset/fix-two-member-channel-name.md b/.changeset/fix-two-member-channel-name.md new file mode 100644 index 000000000..76749d65a --- /dev/null +++ b/.changeset/fix-two-member-channel-name.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Display the explicit room name for non-direct channels with two members. diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index d44751aae..6f888d658 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -322,11 +322,8 @@ export function RoomTimeline({ const powerLevels = usePowerLevelsContext(); const creators = useRoomCreators(room); - const isReadOnly = useMemo(() => { - const myPowerLevel = powerLevels?.users?.[mx.getUserId()!] ?? powerLevels?.users_default ?? 0; - const sendLevel = powerLevels?.events?.['m.room.message'] ?? powerLevels?.events_default ?? 0; - return myPowerLevel < sendLevel; - }, [powerLevels, mx]); + const permissions = useRoomPermissions(creators, powerLevels); + const isReadOnly = !permissions.message(room.hasEncryptionStateEvent(), mx.getSafeUserId()); const settings = useMemo( () => ({ @@ -375,8 +372,6 @@ export function RoomTimeline({ const ignoredUsersSet = useMemo(() => new Set(ignoredUsersList), [ignoredUsersList]); const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels); - const permissions = useRoomPermissions(creators, powerLevels); - const [unreadInfo, setUnreadInfo] = useState(() => getRoomUnreadInfo(room, true)); const readUptoEventIdRef = useRef(undefined); diff --git a/src/app/features/room/RoomView.tsx b/src/app/features/room/RoomView.tsx index c22cce06e..73a35f955 100644 --- a/src/app/features/room/RoomView.tsx +++ b/src/app/features/room/RoomView.tsx @@ -89,7 +89,7 @@ export function RoomView({ eventId }: { eventId?: string }) { const creators = useRoomCreators(room); const permissions = useRoomPermissions(creators, powerLevels); - const canMessage = permissions.event(EventType.RoomMessage, mx.getSafeUserId()); + const canMessage = permissions.message(room.hasEncryptionStateEvent(), mx.getSafeUserId()); const [editorResetKey, setEditorResetKey] = useState(0); const handleResetEditor = useCallback(() => setEditorResetKey((prev) => prev + 1), []); diff --git a/src/app/features/room/ThreadDrawer.tsx b/src/app/features/room/ThreadDrawer.tsx index fdf59c221..a4a85a607 100644 --- a/src/app/features/room/ThreadDrawer.tsx +++ b/src/app/features/room/ThreadDrawer.tsx @@ -217,11 +217,7 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra const canDeleteOwn = permissions.event(EventType.RoomRedaction, mx.getSafeUserId()); const canSendReaction = permissions.event(EventType.Reaction, mx.getSafeUserId()); const canPinEvent = permissions.stateEvent(EventType.RoomPinnedEvents, mx.getSafeUserId()); - const isReadOnly = useMemo(() => { - const myPowerLevel = powerLevels?.users?.[mx.getUserId()!] ?? powerLevels?.users_default ?? 0; - const sendLevel = powerLevels?.events?.['m.room.message'] ?? powerLevels?.events_default ?? 0; - return myPowerLevel < sendLevel; - }, [powerLevels, mx]); + const isReadOnly = !permissions.message(room.hasEncryptionStateEvent(), mx.getSafeUserId()); const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels); const parseMemberEvent = useMemberEventParser(); diff --git a/src/app/hooks/useRoomMeta.ts b/src/app/hooks/useRoomMeta.ts index 486f3ad14..b8055dd76 100644 --- a/src/app/hooks/useRoomMeta.ts +++ b/src/app/hooks/useRoomMeta.ts @@ -7,6 +7,17 @@ import { mDirectAtom } from '$state/mDirectList'; import { useStateEvent } from './useStateEvent'; import { useNickname } from './useNickname'; +export const getRoomDisplayName = ( + roomName: string, + stateName: unknown, + isDmTagged: boolean, + dmNickname?: string +): string => { + if (isDmTagged && dmNickname) return dmNickname; + if (typeof stateName === 'string' && stateName) return stateName; + return roomName; +}; + export const useRoomAvatar = (room: Room, dm?: boolean): string | undefined => { const avatarEvent = useStateEvent(room, EventType.RoomAvatar); @@ -24,6 +35,8 @@ export const useRoomName = (room: Room): string => { const dmNickname = useNickname(dmUserId || ''); const mDirects = useAtomValue(mDirectAtom); const isDmTagged = mDirects.has(room.roomId); + const nameEvent = useStateEvent(room, EventType.RoomName); + const stateName = nameEvent?.getContent().name; const [name, setName] = useState(room.name); useEffect(() => { @@ -32,7 +45,7 @@ export const useRoomName = (room: Room): string => { room.recalculate(); } - const nextName = isDmTagged && dmNickname ? dmNickname : room.name; + const nextName = getRoomDisplayName(room.name, stateName, isDmTagged, dmNickname); setName((prev) => (prev !== nextName ? nextName : prev)); }; @@ -45,7 +58,7 @@ export const useRoomName = (room: Room): string => { room.removeListener(RoomEvent.Name, updateName); room.removeListener(RoomStateEvent.Members, updateName); }; - }, [room, dmNickname, isDmTagged]); + }, [room, stateName, dmNickname, isDmTagged]); return name; }; diff --git a/src/app/hooks/useRoomPermissions.ts b/src/app/hooks/useRoomPermissions.ts index 99cd1a867..909e64c7e 100644 --- a/src/app/hooks/useRoomPermissions.ts +++ b/src/app/hooks/useRoomPermissions.ts @@ -1,4 +1,5 @@ import { useMemo } from 'react'; +import { EventType } from '$types/matrix-sdk'; import type { IPowerLevels, PowerLevelActions, @@ -8,6 +9,7 @@ import { readPowerLevel } from './usePowerLevels'; export type RoomPermissionsAPI = { event: (type: string, userId: string) => boolean; + message: (encrypted: boolean, userId: string) => boolean; stateEvent: (type: string, userId: string) => boolean; action: (action: PowerLevelActions, userId: string) => boolean; notificationAction: (action: PowerLevelNotificationsAction, userId: string) => boolean; @@ -24,6 +26,11 @@ export const getRoomPermissionsAPI = ( const requiredPL = readPowerLevel.event(powerLevels, type); return userPower >= requiredPL; }, + message: (encrypted, userId) => + api.event( + encrypted ? (EventType.RoomMessageEncrypted as string) : EventType.RoomMessage, + userId + ), stateEvent: (type, userId) => { if (creators.has(userId)) return true; const userPower = readPowerLevel.user(powerLevels, userId);