Support room version 12 (#2399)
* WIP - support room version 12 * add room creators hook * revert changes from powerlevels * improve use room creators hook * add hook to get dm users * add options to add creators in create room/space * add member item component in member drawer * remove unused import * extract member drawer header component * get room creators as set only if room version support them * add room permissions hook * support room v12 creators power * make predecessor event id optional * add info about founders in permissions * allow to create infinite powers to room creators * allow everyone with permission to create infinite power * handle additional creators in room upgrade * add option to follow space tombstone
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
import { keyframes, style } from '@vanilla-extract/css';
|
||||
import { color, config, toRem } from 'folds';
|
||||
import { config, toRem } from 'folds';
|
||||
|
||||
export const MembersDrawer = style({
|
||||
width: toRem(266),
|
||||
backgroundColor: color.Background.Container,
|
||||
color: color.Background.OnContainer,
|
||||
});
|
||||
|
||||
export const MembersDrawerHeader = style({
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
TooltipProvider,
|
||||
config,
|
||||
} from 'folds';
|
||||
import { Room, RoomMember } from 'matrix-js-sdk';
|
||||
import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
useAsyncSearch,
|
||||
} from '../../hooks/useAsyncSearch';
|
||||
import { useDebounce } from '../../hooks/useDebounce';
|
||||
import { usePowerLevelTags, useFlattenPowerLevelTagMembers } from '../../hooks/usePowerLevelTags';
|
||||
import { TypingIndicator } from '../../components/typing-indicator';
|
||||
import { getMemberDisplayName, getMemberSearchStr } from '../../utils/room';
|
||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
||||
@@ -51,12 +50,116 @@ import { UserAvatar } from '../../components/user-avatar';
|
||||
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useMembershipFilter, useMembershipFilterMenu } from '../../hooks/useMemberFilter';
|
||||
import { useMemberSort, useMemberSortMenu } from '../../hooks/useMemberSort';
|
||||
import { usePowerLevelsAPI, usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import { useMemberPowerSort, useMemberSort, useMemberSortMenu } from '../../hooks/useMemberSort';
|
||||
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import { MembershipFilterMenu } from '../../components/MembershipFilterMenu';
|
||||
import { MemberSortMenu } from '../../components/MemberSortMenu';
|
||||
import { useOpenUserRoomProfile, useUserRoomProfileState } from '../../state/hooks/userRoomProfile';
|
||||
import { useSpaceOptionally } from '../../hooks/useSpace';
|
||||
import { ContainerColor } from '../../styles/ContainerColor.css';
|
||||
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
|
||||
type MemberDrawerHeaderProps = {
|
||||
room: Room;
|
||||
};
|
||||
function MemberDrawerHeader({ room }: MemberDrawerHeaderProps) {
|
||||
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
|
||||
|
||||
return (
|
||||
<Header className={css.MembersDrawerHeader} variant="Background" size="600">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Text title={`${room.getJoinedMemberCount()} Members`} size="H5" truncate>
|
||||
{`${millify(room.getJoinedMemberCount())} Members`}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No" alignItems="Center">
|
||||
<TooltipProvider
|
||||
position="Bottom"
|
||||
align="End"
|
||||
offset={4}
|
||||
tooltip={
|
||||
<Tooltip>
|
||||
<Text>Close</Text>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
{(triggerRef) => (
|
||||
<IconButton
|
||||
ref={triggerRef}
|
||||
variant="Background"
|
||||
onClick={() => setPeopleDrawer(false)}
|
||||
>
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
</Box>
|
||||
</Box>
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
|
||||
type MemberItemProps = {
|
||||
mx: MatrixClient;
|
||||
useAuthentication: boolean;
|
||||
room: Room;
|
||||
member: RoomMember;
|
||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||
pressed?: boolean;
|
||||
typing?: boolean;
|
||||
};
|
||||
function MemberItem({
|
||||
mx,
|
||||
useAuthentication,
|
||||
room,
|
||||
member,
|
||||
onClick,
|
||||
pressed,
|
||||
typing,
|
||||
}: MemberItemProps) {
|
||||
const name =
|
||||
getMemberDisplayName(room, member.userId) ?? getMxIdLocalPart(member.userId) ?? member.userId;
|
||||
const avatarMxcUrl = member.getMxcAvatarUrl();
|
||||
const avatarUrl = avatarMxcUrl
|
||||
? mx.mxcUrlToHttp(avatarMxcUrl, 100, 100, 'crop', undefined, false, useAuthentication)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
style={{ padding: `0 ${config.space.S200}` }}
|
||||
aria-pressed={pressed}
|
||||
data-user-id={member.userId}
|
||||
variant="Background"
|
||||
radii="400"
|
||||
onClick={onClick}
|
||||
before={
|
||||
<Avatar size="200">
|
||||
<UserAvatar
|
||||
userId={member.userId}
|
||||
src={avatarUrl ?? undefined}
|
||||
alt={name}
|
||||
renderFallback={() => <Icon size="50" src={Icons.User} filled />}
|
||||
/>
|
||||
</Avatar>
|
||||
}
|
||||
after={
|
||||
typing && (
|
||||
<Badge size="300" variant="Secondary" fill="Soft" radii="Pill" outlined>
|
||||
<TypingIndicator size="300" />
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="T400" truncate>
|
||||
{name}
|
||||
</Text>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
|
||||
limit: 1000,
|
||||
@@ -80,9 +183,10 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const scrollTopAnchorRef = useRef<HTMLDivElement>(null);
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const [, getPowerLevelTag] = usePowerLevelTags(room, powerLevels);
|
||||
const creators = useRoomCreators(room);
|
||||
const getPowerTag = useGetMemberPowerTag(room, creators, powerLevels);
|
||||
|
||||
const fetchingMembers = members.length < room.getJoinedMemberCount();
|
||||
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
|
||||
const openUserRoomProfile = useOpenUserRoomProfile();
|
||||
const space = useSpaceOptionally();
|
||||
const openProfileUserId = useUserRoomProfileState()?.userId;
|
||||
@@ -91,20 +195,16 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
||||
const sortFilterMenu = useMemberSortMenu();
|
||||
const [sortFilterIndex, setSortFilterIndex] = useSetting(settingsAtom, 'memberSortFilterIndex');
|
||||
const [membershipFilterIndex, setMembershipFilterIndex] = useState(0);
|
||||
const { getPowerLevel } = usePowerLevelsAPI(powerLevels);
|
||||
|
||||
const membershipFilter = useMembershipFilter(membershipFilterIndex, membershipFilterMenu);
|
||||
const memberSort = useMemberSort(sortFilterIndex, sortFilterMenu);
|
||||
const memberPowerSort = useMemberPowerSort(creators);
|
||||
|
||||
const typingMembers = useRoomTypingMember(room.roomId);
|
||||
|
||||
const filteredMembers = useMemo(
|
||||
() =>
|
||||
members
|
||||
.filter(membershipFilter.filterFn)
|
||||
.sort(memberSort.sortFn)
|
||||
.sort((a, b) => b.powerLevel - a.powerLevel),
|
||||
[members, membershipFilter, memberSort]
|
||||
() => members.filter(membershipFilter.filterFn).sort(memberSort.sortFn).sort(memberPowerSort),
|
||||
[members, membershipFilter, memberSort, memberPowerSort]
|
||||
);
|
||||
|
||||
const [result, search, resetSearch] = useAsyncSearch(
|
||||
@@ -116,11 +216,7 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
||||
|
||||
const processMembers = result ? result.items : filteredMembers;
|
||||
|
||||
const PLTagOrRoomMember = useFlattenPowerLevelTagMembers(
|
||||
processMembers,
|
||||
getPowerLevel,
|
||||
getPowerLevelTag
|
||||
);
|
||||
const PLTagOrRoomMember = useFlattenPowerTagMembers(processMembers, getPowerTag);
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: PLTagOrRoomMember.length,
|
||||
@@ -140,9 +236,6 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
||||
{ wait: 200 }
|
||||
);
|
||||
|
||||
const getName = (member: RoomMember) =>
|
||||
getMemberDisplayName(room, member.userId) ?? getMxIdLocalPart(member.userId) ?? member.userId;
|
||||
|
||||
const handleMemberClick: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
const btn = evt.currentTarget as HTMLButtonElement;
|
||||
const userId = btn.getAttribute('data-user-id');
|
||||
@@ -151,38 +244,12 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className={css.MembersDrawer} shrink="No" direction="Column">
|
||||
<Header className={css.MembersDrawerHeader} variant="Background" size="600">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Text title={`${room.getJoinedMemberCount()} Members`} size="H5" truncate>
|
||||
{`${millify(room.getJoinedMemberCount())} Members`}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No" alignItems="Center">
|
||||
<TooltipProvider
|
||||
position="Bottom"
|
||||
align="End"
|
||||
offset={4}
|
||||
tooltip={
|
||||
<Tooltip>
|
||||
<Text>Close</Text>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
{(triggerRef) => (
|
||||
<IconButton
|
||||
ref={triggerRef}
|
||||
variant="Background"
|
||||
onClick={() => setPeopleDrawer(false)}
|
||||
>
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
</Box>
|
||||
</Box>
|
||||
</Header>
|
||||
<Box
|
||||
className={classNames(css.MembersDrawer, ContainerColor({ variant: 'Background' }))}
|
||||
shrink="No"
|
||||
direction="Column"
|
||||
>
|
||||
<MemberDrawerHeader room={room} />
|
||||
<Box className={css.MemberDrawerContentBase} grow="Yes">
|
||||
<Scroll ref={scrollRef} variant="Background" size="300" visibility="Hover" hideTrack>
|
||||
<Box className={css.MemberDrawerContent} direction="Column" gap="200">
|
||||
@@ -334,60 +401,28 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const member = tagOrMember;
|
||||
const name = getName(member);
|
||||
const avatarMxcUrl = member.getMxcAvatarUrl();
|
||||
const avatarUrl = avatarMxcUrl
|
||||
? mx.mxcUrlToHttp(
|
||||
avatarMxcUrl,
|
||||
100,
|
||||
100,
|
||||
'crop',
|
||||
undefined,
|
||||
false,
|
||||
useAuthentication
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
<div
|
||||
style={{
|
||||
padding: `0 ${config.space.S200}`,
|
||||
transform: `translateY(${vItem.start}px)`,
|
||||
}}
|
||||
aria-pressed={openProfileUserId === member.userId}
|
||||
data-index={vItem.index}
|
||||
data-user-id={member.userId}
|
||||
ref={virtualizer.measureElement}
|
||||
key={`${room.roomId}-${member.userId}`}
|
||||
className={css.DrawerVirtualItem}
|
||||
variant="Background"
|
||||
radii="400"
|
||||
onClick={handleMemberClick}
|
||||
before={
|
||||
<Avatar size="200">
|
||||
<UserAvatar
|
||||
userId={member.userId}
|
||||
src={avatarUrl ?? undefined}
|
||||
alt={name}
|
||||
renderFallback={() => <Icon size="50" src={Icons.User} filled />}
|
||||
/>
|
||||
</Avatar>
|
||||
}
|
||||
after={
|
||||
typingMembers.find((receipt) => receipt.userId === member.userId) && (
|
||||
<Badge size="300" variant="Secondary" fill="Soft" radii="Pill" outlined>
|
||||
<TypingIndicator size="300" />
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
data-index={vItem.index}
|
||||
key={`${room.roomId}-${tagOrMember.userId}`}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="T400" truncate>
|
||||
{name}
|
||||
</Text>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
<MemberItem
|
||||
mx={mx}
|
||||
useAuthentication={useAuthentication}
|
||||
room={room}
|
||||
member={tagOrMember}
|
||||
onClick={handleMemberClick}
|
||||
pressed={openProfileUserId === tagOrMember.userId}
|
||||
typing={typingMembers.some(
|
||||
(receipt) => receipt.userId === tagOrMember.userId
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -108,21 +108,23 @@ import { ReplyLayout, ThreadIndicator } from '../../components/message';
|
||||
import { roomToParentsAtom } from '../../state/room/roomToParents';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useImagePackRooms } from '../../hooks/useImagePackRooms';
|
||||
import { GetPowerLevelTag } from '../../hooks/usePowerLevelTags';
|
||||
import { powerLevelAPI, usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
import { useIsDirectRoom } from '../../hooks/useRoom';
|
||||
import { useAccessiblePowerTagColors, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
import { useTheme } from '../../hooks/useTheme';
|
||||
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
|
||||
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
||||
|
||||
interface RoomInputProps {
|
||||
editor: Editor;
|
||||
fileDropContainerRef: RefObject<HTMLElement>;
|
||||
roomId: string;
|
||||
room: Room;
|
||||
getPowerLevelTag: GetPowerLevelTag;
|
||||
accessibleTagColors: Map<string, string>;
|
||||
}
|
||||
export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
({ editor, fileDropContainerRef, roomId, room, getPowerLevelTag, accessibleTagColors }, ref) => {
|
||||
({ editor, fileDropContainerRef, roomId, room }, ref) => {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
||||
@@ -134,13 +136,24 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
const emojiBtnRef = useRef<HTMLButtonElement>(null);
|
||||
const roomToParents = useAtomValue(roomToParentsAtom);
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
const [msgDraft, setMsgDraft] = useAtom(roomIdToMsgDraftAtomFamily(roomId));
|
||||
const [replyDraft, setReplyDraft] = useAtom(roomIdToReplyDraftAtomFamily(roomId));
|
||||
const replyUserID = replyDraft?.userId;
|
||||
|
||||
const replyPowerTag = getPowerLevelTag(powerLevelAPI.getPowerLevel(powerLevels, replyUserID));
|
||||
const replyPowerColor = replyPowerTag.color
|
||||
const powerLevelTags = usePowerLevelTags(room, powerLevels);
|
||||
const creatorsTag = useRoomCreatorsTag();
|
||||
const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels);
|
||||
const theme = useTheme();
|
||||
const accessibleTagColors = useAccessiblePowerTagColors(
|
||||
theme.kind,
|
||||
creatorsTag,
|
||||
powerLevelTags
|
||||
);
|
||||
|
||||
const replyPowerTag = replyUserID ? getMemberPowerTag(replyUserID) : undefined;
|
||||
const replyPowerColor = replyPowerTag?.color
|
||||
? accessibleTagColors.get(replyPowerTag.color)
|
||||
: undefined;
|
||||
const replyUsernameColor =
|
||||
@@ -277,7 +290,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
});
|
||||
handleCancelUpload(uploads);
|
||||
const contents = fulfilledPromiseSettledResult(await Promise.allSettled(contentsPromises));
|
||||
contents.forEach((content) => mx.sendMessage(roomId, content));
|
||||
contents.forEach((content) => mx.sendMessage(roomId, content as any));
|
||||
};
|
||||
|
||||
const submit = useCallback(() => {
|
||||
@@ -356,7 +369,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||
content['m.relates_to'].is_falling_back = false;
|
||||
}
|
||||
}
|
||||
mx.sendMessage(roomId, content);
|
||||
mx.sendMessage(roomId, content as any);
|
||||
resetEditor(editor);
|
||||
resetEditorHistory(editor);
|
||||
setReplyDraft(undefined);
|
||||
|
||||
@@ -101,7 +101,7 @@ import * as css from './RoomTimeline.css';
|
||||
import { inSameDay, minuteDifference, timeDayMonthYear, today, yesterday } from '../../utils/time';
|
||||
import { createMentionElement, isEmptyEditor, moveCursor } from '../../components/editor';
|
||||
import { roomIdToReplyDraftAtomFamily } from '../../state/room/roomInputDrafts';
|
||||
import { usePowerLevelsAPI, usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import { GetContentCallback, MessageEvent, StateEvent } from '../../../types/matrix/room';
|
||||
import { useKeyDown } from '../../hooks/useKeyDown';
|
||||
import { useDocumentFocusChange } from '../../hooks/useDocumentFocusChange';
|
||||
@@ -117,10 +117,15 @@ import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useIgnoredUsers } from '../../hooks/useIgnoredUsers';
|
||||
import { useImagePackRooms } from '../../hooks/useImagePackRooms';
|
||||
import { GetPowerLevelTag } from '../../hooks/usePowerLevelTags';
|
||||
import { useIsDirectRoom } from '../../hooks/useRoom';
|
||||
import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
||||
import { useSpaceOptionally } from '../../hooks/useSpace';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||||
import { useAccessiblePowerTagColors, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
|
||||
import { useTheme } from '../../hooks/useTheme';
|
||||
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
|
||||
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
||||
|
||||
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
|
||||
({ position, className, ...props }, ref) => (
|
||||
@@ -223,8 +228,6 @@ type RoomTimelineProps = {
|
||||
eventId?: string;
|
||||
roomInputRef: RefObject<HTMLElement>;
|
||||
editor: Editor;
|
||||
getPowerLevelTag: GetPowerLevelTag;
|
||||
accessibleTagColors: Map<string, string>;
|
||||
};
|
||||
|
||||
const PAGINATION_LIMIT = 80;
|
||||
@@ -427,14 +430,7 @@ const getRoomUnreadInfo = (room: Room, scrollTo = false) => {
|
||||
};
|
||||
};
|
||||
|
||||
export function RoomTimeline({
|
||||
room,
|
||||
eventId,
|
||||
roomInputRef,
|
||||
editor,
|
||||
getPowerLevelTag,
|
||||
accessibleTagColors,
|
||||
}: RoomTimelineProps) {
|
||||
export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
@@ -459,13 +455,24 @@ export function RoomTimeline({
|
||||
|
||||
const setReplyDraft = useSetAtom(roomIdToReplyDraftAtomFamily(room.roomId));
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const { canDoAction, canSendEvent, canSendStateEvent, getPowerLevel } =
|
||||
usePowerLevelsAPI(powerLevels);
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
const myPowerLevel = getPowerLevel(mx.getUserId() ?? '');
|
||||
const canRedact = canDoAction('redact', myPowerLevel);
|
||||
const canSendReaction = canSendEvent(MessageEvent.Reaction, myPowerLevel);
|
||||
const canPinEvent = canSendStateEvent(StateEvent.RoomPinnedEvents, myPowerLevel);
|
||||
const creatorsTag = useRoomCreatorsTag();
|
||||
const powerLevelTags = usePowerLevelTags(room, powerLevels);
|
||||
const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels);
|
||||
|
||||
const theme = useTheme();
|
||||
const accessiblePowerTagColors = useAccessiblePowerTagColors(
|
||||
theme.kind,
|
||||
creatorsTag,
|
||||
powerLevelTags
|
||||
);
|
||||
|
||||
const permissions = useRoomPermissions(creators, powerLevels);
|
||||
|
||||
const canRedact = permissions.action('redact', mx.getSafeUserId());
|
||||
const canSendReaction = permissions.event(MessageEvent.Reaction, mx.getSafeUserId());
|
||||
const canPinEvent = permissions.stateEvent(StateEvent.RoomPinnedEvents, mx.getSafeUserId());
|
||||
const [editId, setEditId] = useState<string>();
|
||||
|
||||
const roomToParents = useAtomValue(roomToParentsAtom);
|
||||
@@ -990,7 +997,7 @@ export function RoomTimeline({
|
||||
(reactions.find(eventWithShortcode)?.getContent().shortcode as string | undefined);
|
||||
mx.sendEvent(
|
||||
room.roomId,
|
||||
MessageEvent.Reaction,
|
||||
MessageEvent.Reaction as any,
|
||||
getReactionContent(targetEventId, key, rShortcode)
|
||||
);
|
||||
},
|
||||
@@ -1025,7 +1032,6 @@ export function RoomTimeline({
|
||||
editedEvent?.getContent()['m.new_content'] ?? mEvent.getContent()) as GetContentCallback;
|
||||
|
||||
const senderId = mEvent.getSender() ?? '';
|
||||
const senderPowerLevel = getPowerLevel(mEvent.getSender());
|
||||
const senderDisplayName =
|
||||
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
|
||||
|
||||
@@ -1059,9 +1065,8 @@ export function RoomTimeline({
|
||||
replyEventId={replyEventId}
|
||||
threadRootId={threadRootId}
|
||||
onClick={handleOpenReply}
|
||||
getPowerLevel={getPowerLevel}
|
||||
getPowerLevelTag={getPowerLevelTag}
|
||||
accessibleTagColors={accessibleTagColors}
|
||||
getMemberPowerTag={getMemberPowerTag}
|
||||
accessibleTagColors={accessiblePowerTagColors}
|
||||
legacyUsernameColor={legacyUsernameColor || direct}
|
||||
/>
|
||||
)
|
||||
@@ -1080,8 +1085,8 @@ export function RoomTimeline({
|
||||
}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
powerLevelTag={getPowerLevelTag(senderPowerLevel)}
|
||||
accessibleTagColors={accessibleTagColors}
|
||||
memberPowerTag={getMemberPowerTag(senderId)}
|
||||
accessibleTagColors={accessiblePowerTagColors}
|
||||
legacyUsernameColor={legacyUsernameColor || direct}
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
@@ -1111,7 +1116,6 @@ export function RoomTimeline({
|
||||
const hasReactions = reactions && reactions.length > 0;
|
||||
const { replyEventId, threadRootId } = mEvent;
|
||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||
const senderPowerLevel = getPowerLevel(mEvent.getSender());
|
||||
|
||||
return (
|
||||
<Message
|
||||
@@ -1143,9 +1147,8 @@ export function RoomTimeline({
|
||||
replyEventId={replyEventId}
|
||||
threadRootId={threadRootId}
|
||||
onClick={handleOpenReply}
|
||||
getPowerLevel={getPowerLevel}
|
||||
getPowerLevelTag={getPowerLevelTag}
|
||||
accessibleTagColors={accessibleTagColors}
|
||||
getMemberPowerTag={getMemberPowerTag}
|
||||
accessibleTagColors={accessiblePowerTagColors}
|
||||
legacyUsernameColor={legacyUsernameColor || direct}
|
||||
/>
|
||||
)
|
||||
@@ -1164,8 +1167,8 @@ export function RoomTimeline({
|
||||
}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
powerLevelTag={getPowerLevelTag(senderPowerLevel)}
|
||||
accessibleTagColors={accessibleTagColors}
|
||||
memberPowerTag={getMemberPowerTag(mEvent.getSender() ?? '')}
|
||||
accessibleTagColors={accessiblePowerTagColors}
|
||||
legacyUsernameColor={legacyUsernameColor || direct}
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
@@ -1232,7 +1235,6 @@ export function RoomTimeline({
|
||||
const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey();
|
||||
const hasReactions = reactions && reactions.length > 0;
|
||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||
const senderPowerLevel = getPowerLevel(mEvent.getSender());
|
||||
|
||||
return (
|
||||
<Message
|
||||
@@ -1268,8 +1270,8 @@ export function RoomTimeline({
|
||||
}
|
||||
hideReadReceipts={hideActivity}
|
||||
showDeveloperTools={showDeveloperTools}
|
||||
powerLevelTag={getPowerLevelTag(senderPowerLevel)}
|
||||
accessibleTagColors={accessibleTagColors}
|
||||
memberPowerTag={getMemberPowerTag(mEvent.getSender() ?? '')}
|
||||
accessibleTagColors={accessiblePowerTagColors}
|
||||
legacyUsernameColor={legacyUsernameColor || direct}
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ReactEditor } from 'slate-react';
|
||||
import { isKeyHotkey } from 'is-hotkey';
|
||||
import { useStateEvent } from '../../hooks/useStateEvent';
|
||||
import { StateEvent } from '../../../types/matrix/room';
|
||||
import { usePowerLevelsAPI, usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useEditor } from '../../components/editor';
|
||||
import { RoomInputPlaceholder } from './RoomInputPlaceholder';
|
||||
@@ -21,8 +21,8 @@ import { editableActiveElement } from '../../utils/dom';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
import { useAccessibleTagColors, usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
||||
import { useTheme } from '../../hooks/useTheme';
|
||||
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
|
||||
const FN_KEYS_REGEX = /^F\d+$/;
|
||||
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
||||
@@ -70,15 +70,10 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
||||
|
||||
const tombstoneEvent = useStateEvent(room, StateEvent.RoomTombstone);
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const { getPowerLevel, canSendEvent } = usePowerLevelsAPI(powerLevels);
|
||||
const myUserId = mx.getUserId();
|
||||
const canMessage = myUserId
|
||||
? canSendEvent(EventType.RoomMessage, getPowerLevel(myUserId))
|
||||
: false;
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
const [powerLevelTags, getPowerLevelTag] = usePowerLevelTags(room, powerLevels);
|
||||
const theme = useTheme();
|
||||
const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags);
|
||||
const permissions = useRoomPermissions(creators, powerLevels);
|
||||
const canMessage = permissions.event(EventType.RoomMessage, mx.getSafeUserId());
|
||||
|
||||
useKeyDown(
|
||||
window,
|
||||
@@ -109,8 +104,6 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
||||
eventId={eventId}
|
||||
roomInputRef={roomInputRef}
|
||||
editor={editor}
|
||||
getPowerLevelTag={getPowerLevelTag}
|
||||
accessibleTagColors={accessibleTagColors}
|
||||
/>
|
||||
<RoomViewTyping room={room} />
|
||||
</Box>
|
||||
@@ -131,8 +124,6 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
||||
roomId={roomId}
|
||||
fileDropContainerRef={roomViewRef}
|
||||
ref={roomInputRef}
|
||||
getPowerLevelTag={getPowerLevelTag}
|
||||
accessibleTagColors={accessibleTagColors}
|
||||
/>
|
||||
)}
|
||||
{!canMessage && (
|
||||
|
||||
@@ -42,7 +42,7 @@ import { getCanonicalAliasOrRoomId, isRoomAlias, mxcUrlToHttp } from '../../util
|
||||
import { _SearchPathSearchParams } from '../../pages/paths';
|
||||
import * as css from './RoomViewHeader.css';
|
||||
import { useRoomUnread } from '../../state/hooks/unread';
|
||||
import { usePowerLevelsAPI, usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
||||
import { markAsRead } from '../../../client/action/notifications';
|
||||
import { roomToUnreadAtom } from '../../state/room/roomToUnread';
|
||||
import { openInviteUser } from '../../../client/action/navigation';
|
||||
@@ -67,6 +67,8 @@ import {
|
||||
} from '../../hooks/useRoomsNotificationPreferences';
|
||||
import { JumpToTime } from './jump-to-time';
|
||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||||
|
||||
type RoomMenuProps = {
|
||||
room: Room;
|
||||
@@ -77,8 +79,10 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
|
||||
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
const permissions = useRoomPermissions(creators, powerLevels);
|
||||
const canInvite = permissions.action('invite', mx.getSafeUserId());
|
||||
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
||||
const notificationMode = getRoomNotificationMode(notificationPreferences, room.roomId);
|
||||
const { navigateRoom } = useRoomNavigate();
|
||||
|
||||
@@ -75,10 +75,10 @@ import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
|
||||
import { getViaServers } from '../../../plugins/via-servers';
|
||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||
import { useRoomPinnedEvents } from '../../../hooks/useRoomPinnedEvents';
|
||||
import { StateEvent } from '../../../../types/matrix/room';
|
||||
import { getTagIconSrc, PowerLevelTag } from '../../../hooks/usePowerLevelTags';
|
||||
import { MemberPowerTag, StateEvent } from '../../../../types/matrix/room';
|
||||
import { PowerIcon } from '../../../components/power';
|
||||
import colorMXID from '../../../../util/colorMXID';
|
||||
import { getPowerTagIconSrc } from '../../../hooks/useMemberPowerTag';
|
||||
|
||||
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
|
||||
|
||||
@@ -371,7 +371,7 @@ export const MessagePinItem = as<
|
||||
if (!isPinned && eventId) {
|
||||
pinContent.pinned.push(eventId);
|
||||
}
|
||||
mx.sendStateEvent(room.roomId, StateEvent.RoomPinnedEvents, pinContent);
|
||||
mx.sendStateEvent(room.roomId, StateEvent.RoomPinnedEvents as any, pinContent);
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
@@ -679,7 +679,7 @@ export type MessageProps = {
|
||||
reactions?: ReactNode;
|
||||
hideReadReceipts?: boolean;
|
||||
showDeveloperTools?: boolean;
|
||||
powerLevelTag?: PowerLevelTag;
|
||||
memberPowerTag?: MemberPowerTag;
|
||||
accessibleTagColors?: Map<string, string>;
|
||||
legacyUsernameColor?: boolean;
|
||||
hour24Clock: boolean;
|
||||
@@ -710,7 +710,7 @@ export const Message = as<'div', MessageProps>(
|
||||
reactions,
|
||||
hideReadReceipts,
|
||||
showDeveloperTools,
|
||||
powerLevelTag,
|
||||
memberPowerTag,
|
||||
accessibleTagColors,
|
||||
legacyUsernameColor,
|
||||
hour24Clock,
|
||||
@@ -733,11 +733,11 @@ export const Message = as<'div', MessageProps>(
|
||||
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
|
||||
const senderAvatarMxc = getMemberAvatarMxc(room, senderId);
|
||||
|
||||
const tagColor = powerLevelTag?.color
|
||||
? accessibleTagColors?.get(powerLevelTag.color)
|
||||
const tagColor = memberPowerTag?.color
|
||||
? accessibleTagColors?.get(memberPowerTag.color)
|
||||
: undefined;
|
||||
const tagIconSrc = powerLevelTag?.icon
|
||||
? getTagIconSrc(mx, useAuthentication, powerLevelTag.icon)
|
||||
const tagIconSrc = memberPowerTag?.icon
|
||||
? getPowerTagIconSrc(mx, useAuthentication, memberPowerTag.icon)
|
||||
: undefined;
|
||||
|
||||
const usernameColor = legacyUsernameColor ? colorMXID(senderId) : tagColor;
|
||||
|
||||
@@ -69,18 +69,23 @@ import { Image } from '../../../components/media';
|
||||
import { ImageViewer } from '../../../components/image-viewer';
|
||||
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
|
||||
import { VirtualTile } from '../../../components/virtualizer';
|
||||
import { usePowerLevelsAPI, usePowerLevelsContext } from '../../../hooks/usePowerLevels';
|
||||
import { usePowerLevelsContext } from '../../../hooks/usePowerLevels';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||
import { ContainerColor } from '../../../styles/ContainerColor.css';
|
||||
import {
|
||||
getTagIconSrc,
|
||||
useAccessibleTagColors,
|
||||
usePowerLevelTags,
|
||||
} from '../../../hooks/usePowerLevelTags';
|
||||
import { usePowerLevelTags } from '../../../hooks/usePowerLevelTags';
|
||||
import { useTheme } from '../../../hooks/useTheme';
|
||||
import { PowerIcon } from '../../../components/power';
|
||||
import colorMXID from '../../../../util/colorMXID';
|
||||
import { useIsDirectRoom } from '../../../hooks/useRoom';
|
||||
import { useRoomCreators } from '../../../hooks/useRoomCreators';
|
||||
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
|
||||
import {
|
||||
GetMemberPowerTag,
|
||||
getPowerTagIconSrc,
|
||||
useAccessiblePowerTagColors,
|
||||
useGetMemberPowerTag,
|
||||
} from '../../../hooks/useMemberPowerTag';
|
||||
import { useRoomCreatorsTag } from '../../../hooks/useRoomCreatorsTag';
|
||||
|
||||
type PinnedMessageProps = {
|
||||
room: Room;
|
||||
@@ -88,22 +93,27 @@ type PinnedMessageProps = {
|
||||
renderContent: RenderMatrixEvent<[MatrixEvent, string, GetContentCallback]>;
|
||||
onOpen: (roomId: string, eventId: string) => void;
|
||||
canPinEvent: boolean;
|
||||
getMemberPowerTag: GetMemberPowerTag;
|
||||
accessibleTagColors: Map<string, string>;
|
||||
legacyUsernameColor: boolean;
|
||||
hour24Clock: boolean;
|
||||
dateFormatString: string;
|
||||
};
|
||||
function PinnedMessage({ room, eventId, renderContent, onOpen, canPinEvent }: PinnedMessageProps) {
|
||||
function PinnedMessage({
|
||||
room,
|
||||
eventId,
|
||||
renderContent,
|
||||
onOpen,
|
||||
canPinEvent,
|
||||
getMemberPowerTag,
|
||||
accessibleTagColors,
|
||||
legacyUsernameColor,
|
||||
hour24Clock,
|
||||
dateFormatString,
|
||||
}: PinnedMessageProps) {
|
||||
const pinnedEvent = useRoomEvent(room, eventId);
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const mx = useMatrixClient();
|
||||
const direct = useIsDirectRoom();
|
||||
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
||||
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const { getPowerLevel } = usePowerLevelsAPI(powerLevels);
|
||||
const [powerLevelTags, getPowerLevelTag] = usePowerLevelTags(room, powerLevels);
|
||||
const theme = useTheme();
|
||||
const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags);
|
||||
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||
|
||||
const [unpinState, unpin] = useAsyncCallback(
|
||||
useCallback(() => {
|
||||
@@ -169,14 +179,15 @@ function PinnedMessage({ room, eventId, renderContent, onOpen, canPinEvent }: Pi
|
||||
const senderAvatarMxc = getMemberAvatarMxc(room, sender);
|
||||
const getContent = (() => pinnedEvent.getContent()) as GetContentCallback;
|
||||
|
||||
const senderPowerLevel = getPowerLevel(sender);
|
||||
const powerLevelTag = getPowerLevelTag(senderPowerLevel);
|
||||
const tagColor = powerLevelTag?.color ? accessibleTagColors?.get(powerLevelTag.color) : undefined;
|
||||
const tagIconSrc = powerLevelTag?.icon
|
||||
? getTagIconSrc(mx, useAuthentication, powerLevelTag.icon)
|
||||
const memberPowerTag = getMemberPowerTag(sender);
|
||||
const tagColor = memberPowerTag?.color
|
||||
? accessibleTagColors?.get(memberPowerTag.color)
|
||||
: undefined;
|
||||
const tagIconSrc = memberPowerTag?.icon
|
||||
? getPowerTagIconSrc(mx, useAuthentication, memberPowerTag.icon)
|
||||
: undefined;
|
||||
|
||||
const usernameColor = legacyUsernameColor || direct ? colorMXID(sender) : tagColor;
|
||||
const usernameColor = legacyUsernameColor ? colorMXID(sender) : tagColor;
|
||||
|
||||
return (
|
||||
<ModernLayout
|
||||
@@ -222,8 +233,7 @@ function PinnedMessage({ room, eventId, renderContent, onOpen, canPinEvent }: Pi
|
||||
replyEventId={pinnedEvent.replyEventId}
|
||||
threadRootId={pinnedEvent.threadRootId}
|
||||
onClick={handleOpenClick}
|
||||
getPowerLevel={getPowerLevel}
|
||||
getPowerLevelTag={getPowerLevelTag}
|
||||
getMemberPowerTag={getMemberPowerTag}
|
||||
accessibleTagColors={accessibleTagColors}
|
||||
legacyUsernameColor={legacyUsernameColor}
|
||||
/>
|
||||
@@ -242,14 +252,34 @@ export const RoomPinMenu = forwardRef<HTMLDivElement, RoomPinMenuProps>(
|
||||
const mx = useMatrixClient();
|
||||
const userId = mx.getUserId()!;
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const { canSendStateEvent, getPowerLevel } = usePowerLevelsAPI(powerLevels);
|
||||
const canPinEvent = canSendStateEvent(StateEvent.RoomPinnedEvents, getPowerLevel(userId));
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
const permissions = useRoomPermissions(creators, powerLevels);
|
||||
const canPinEvent = permissions.stateEvent(StateEvent.RoomPinnedEvents, userId);
|
||||
|
||||
const creatorsTag = useRoomCreatorsTag();
|
||||
const powerLevelTags = usePowerLevelTags(room, powerLevels);
|
||||
const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels);
|
||||
|
||||
const theme = useTheme();
|
||||
const accessibleTagColors = useAccessiblePowerTagColors(
|
||||
theme.kind,
|
||||
creatorsTag,
|
||||
powerLevelTags
|
||||
);
|
||||
|
||||
const pinnedEvents = useRoomPinnedEvents(room);
|
||||
const sortedPinnedEvent = useMemo(() => Array.from(pinnedEvents).reverse(), [pinnedEvents]);
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
|
||||
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
|
||||
|
||||
const direct = useIsDirectRoom();
|
||||
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
||||
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||
|
||||
const { navigateRoom } = useRoomNavigate();
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -464,6 +494,11 @@ export const RoomPinMenu = forwardRef<HTMLDivElement, RoomPinMenuProps>(
|
||||
renderContent={renderMatrixEvent}
|
||||
onOpen={handleOpen}
|
||||
canPinEvent={canPinEvent}
|
||||
getMemberPowerTag={getMemberPowerTag}
|
||||
accessibleTagColors={accessibleTagColors}
|
||||
legacyUsernameColor={legacyUsernameColor || direct}
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
/>
|
||||
</SequenceCard>
|
||||
</VirtualTile>
|
||||
|
||||
Reference in New Issue
Block a user