diff --git a/README.md b/README.md index 0eeb9c99..76ec07f7 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,12 @@ You can serve the application with a webserver of your choosing by simply copyin Execute the following commands to compile the app from its source code: ```sh -npm install # Installs all dependencies +npm ci # Installs all dependencies npm run build # Compiles the app into the dist/ directory ``` -You can then copy the files to a webserver's webroot of your choice. +You can then copy the files to a webserver's webroot of your choice. + To serve a development version of the app locally for testing, you may also use the command `npm start`. ### Running with Docker diff --git a/src/app/atoms/context-menu/ReusableContextMenu.jsx b/src/app/atoms/context-menu/ReusableContextMenu.jsx index f3a3c0d9..59bdb142 100644 --- a/src/app/atoms/context-menu/ReusableContextMenu.jsx +++ b/src/app/atoms/context-menu/ReusableContextMenu.jsx @@ -23,12 +23,14 @@ function ReusableContextMenu() { openerRef.current.style.height = `${cords.height}px`; openerRef.current.click(); } - const handleContextMenuOpen = (placement, cords, render) => { + const handleContextMenuOpen = (placement, cords, render, afterClose) => { if (key) { closeMenu(); return; } - setData({ placement, cords, render }); + setData({ + placement, cords, render, afterClose, + }); }; navigation.on(cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, handleContextMenuOpen); return () => { @@ -44,6 +46,7 @@ function ReusableContextMenu() { key = Math.random(); return; } + data?.afterClose?.(); if (setData) setData(null); if (key === null) return; diff --git a/src/app/molecules/message/Message.scss b/src/app/molecules/message/Message.scss index 8ec29bfd..dd26e0fd 100644 --- a/src/app/molecules/message/Message.scss +++ b/src/app/molecules/message/Message.scss @@ -303,23 +303,40 @@ // markdown formating .message__body { + & h1, h2, h3, h4, h5, h6 { + margin: 0; + margin-bottom: var(--sp-ultra-tight); + font-weight: var(--fw-medium); + &:first-child { + margin-top: 0; + } + &:last-child { + margin-bottom: 0; + } + } & h1, & h2 { color: var(--tc-surface-high); - margin: var(--sp-loose) 0 var(--sp-normal); - line-height: var(--lh-h1); + margin-top: var(--sp-normal); + font-size: var(--fs-h2); + line-height: var(--lh-h2); + letter-spacing: var(--ls-h2); } & h3, & h4 { color: var(--tc-surface-high); - margin: var(--sp-normal) 0 var(--sp-tight); - line-height: var(--lh-h2); + margin-top: var(--sp-tight); + font-size: var(--fs-s1); + line-height: var(--lh-s1); + letter-spacing: var(--ls-s1); } & h5, & h6 { color: var(--tc-surface-high); - margin: var(--sp-tight) 0 var(--sp-extra-tight); - line-height: var(--lh-s1); + margin-top: var(--sp-extra-tight); + font-size: var(--fs-b1); + line-height: var(--lh-b1); + letter-spacing: var(--ls-b1); } & hr { border-color: var(--bg-divider); @@ -365,7 +382,7 @@ @include scrollbar.scroll--auto-hide; } & pre { - display: inline-block; + width: fit-content; max-width: 100%; @include scrollbar.scroll; @include scrollbar.scroll__h; @@ -376,7 +393,7 @@ } } & blockquote { - display: inline-block; + width: fit-content; max-width: 100%; @include dir.side(border, 4px solid var(--bg-surface-active), 0); white-space: initial !important; diff --git a/src/app/molecules/power-level-selector/PowerLevelSelector.jsx b/src/app/molecules/power-level-selector/PowerLevelSelector.jsx index 7d531bf4..ca3e8417 100644 --- a/src/app/molecules/power-level-selector/PowerLevelSelector.jsx +++ b/src/app/molecules/power-level-selector/PowerLevelSelector.jsx @@ -11,7 +11,7 @@ function PowerLevelSelector({ value, max, onSelect, }) { const handleSubmit = (e) => { - const powerLevel = e.target.elements['power-level']; + const powerLevel = e.target.elements['power-level']?.value; if (!powerLevel) return; onSelect(Number(powerLevel)); }; diff --git a/src/app/molecules/room-optons/RoomOptions.jsx b/src/app/molecules/room-optons/RoomOptions.jsx new file mode 100644 index 00000000..83a94827 --- /dev/null +++ b/src/app/molecules/room-optons/RoomOptions.jsx @@ -0,0 +1,67 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { twemojify } from '../../../util/twemojify'; + +import initMatrix from '../../../client/initMatrix'; +import { openInviteUser } from '../../../client/action/navigation'; +import * as roomActions from '../../../client/action/room'; + +import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu'; +import RoomNotification from '../room-notification/RoomNotification'; + +import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg'; +import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg'; +import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg'; + +function RoomOptions({ roomId, afterOptionSelect }) { + const mx = initMatrix.matrixClient; + const room = mx.getRoom(roomId); + const canInvite = room?.canInvite(mx.getUserId()); + + const handleMarkAsRead = () => { + afterOptionSelect(); + if (!room) return; + const events = room.getLiveTimeline().getEvents(); + mx.sendReadReceipt(events[events.length - 1]); + }; + + const handleInviteClick = () => { + openInviteUser(roomId); + afterOptionSelect(); + }; + const handleLeaveClick = () => { + if (confirm('Are you really want to leave this room?')) { + roomActions.leave(roomId); + afterOptionSelect(); + } + }; + + return ( + <> + {twemojify(`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`)} + Mark as read + + Invite + + Leave + Notification + + + ); +} + +RoomOptions.defaultProps = { + afterOptionSelect: null, +}; + +RoomOptions.propTypes = { + roomId: PropTypes.string.isRequired, + afterOptionSelect: PropTypes.func, +}; + +export default RoomOptions; diff --git a/src/app/molecules/room-permissions/RoomPermissions.jsx b/src/app/molecules/room-permissions/RoomPermissions.jsx index d9001973..c7a2eeee 100644 --- a/src/app/molecules/room-permissions/RoomPermissions.jsx +++ b/src/app/molecules/room-permissions/RoomPermissions.jsx @@ -1,17 +1,22 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import './RoomPermissions.scss'; import initMatrix from '../../../client/initMatrix'; import { getPowerLabel } from '../../../util/matrixUtil'; +import { openReusableContextMenu } from '../../../client/action/navigation'; +import { getEventCords } from '../../../util/common'; import Text from '../../atoms/text/Text'; import Button from '../../atoms/button/Button'; import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; +import PowerLevelSelector from '../power-level-selector/PowerLevelSelector'; import SettingTile from '../setting-tile/SettingTile'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; +import { useForceUpdate } from '../../hooks/useForceUpdate'; + const permissionsInfo = { users_default: { name: 'Default role', @@ -23,6 +28,12 @@ const permissionsInfo = { description: 'Set minimum power level to send messages in room.', default: 0, }, + 'm.reaction': { + parent: 'events', + name: 'Send reactions', + description: 'Set minimum power level to send reactions in room.', + default: 0, + }, redact: { name: 'Delete messages sent by others', description: 'Set minimum power level to delete messages in room.', @@ -130,7 +141,7 @@ const permissionsInfo = { }; const roomPermsGroups = { - 'General Permissions': ['users_default', 'events_default', 'redact', 'notifications'], + 'General Permissions': ['users_default', 'events_default', 'm.reaction', 'redact', 'notifications'], 'Manage members permissions': ['invite', 'kick', 'ban'], 'Room profile permissions': ['m.room.avatar', 'm.room.name', 'm.room.topic'], 'Settings permissions': ['state_default', 'm.room.canonical_alias', 'm.room.power_levels', 'm.room.encryption', 'm.room.history_visibility'], @@ -144,13 +155,69 @@ const spacePermsGroups = { 'Settings permissions': ['state_default', 'm.room.canonical_alias', 'm.room.power_levels'], }; +function useRoomStateUpdate(roomId) { + const [, forceUpdate] = useForceUpdate(); + const mx = initMatrix.matrixClient; + + useEffect(() => { + const handleStateEvent = (event) => { + if (event.getRoomId() !== roomId) return; + forceUpdate(); + }; + + mx.on('RoomState.events', handleStateEvent); + return () => { + mx.removeListener('RoomState.events', handleStateEvent); + }; + }, [roomId]); +} + function RoomPermissions({ roomId }) { + useRoomStateUpdate(roomId); const mx = initMatrix.matrixClient; const room = mx.getRoom(roomId); const pLEvent = room.currentState.getStateEvents('m.room.power_levels')[0]; const permissions = pLEvent.getContent(); const canChangePermission = room.currentState.maySendStateEvent('m.room.power_levels', mx.getUserId()); + const handlePowerSelector = (e, permKey, parentKey, powerLevel) => { + const handlePowerLevelChange = (newPowerLevel) => { + if (powerLevel === newPowerLevel) return; + + const newPermissions = { ...permissions }; + if (parentKey) { + newPermissions[parentKey] = { + ...permissions[parentKey], + [permKey]: newPowerLevel, + }; + } else if (permKey === 'notifications') { + newPermissions[permKey] = { + ...permissions[permKey], + room: newPowerLevel, + }; + } else { + newPermissions[permKey] = newPowerLevel; + } + + mx.sendStateEvent(roomId, 'm.room.power_levels', newPermissions); + }; + + openReusableContextMenu( + 'bottom', + getEventCords(e, '.btn-surface'), + (closeMenu) => ( + { + closeMenu(); + handlePowerLevelChange(pl); + }} + /> + ), + ); + }; + return (
{ @@ -182,6 +249,11 @@ function RoomPermissions({ roomId }) { content={{permInfo.description}} options={( @@ -31,6 +33,7 @@ function RoomSelectorWrapper({ } RoomSelectorWrapper.defaultProps = { options: null, + onContextMenu: null, }; RoomSelectorWrapper.propTypes = { isSelected: PropTypes.bool.isRequired, @@ -38,12 +41,13 @@ RoomSelectorWrapper.propTypes = { onClick: PropTypes.func.isRequired, content: PropTypes.node.isRequired, options: PropTypes.node, + onContextMenu: PropTypes.func, }; function RoomSelector({ name, parentName, roomId, imageSrc, iconSrc, isSelected, isUnread, notificationCount, isAlert, - options, onClick, + options, onClick, onContextMenu, }) { return ( ); } @@ -87,6 +92,7 @@ RoomSelector.defaultProps = { imageSrc: null, iconSrc: null, options: null, + onContextMenu: null, }; RoomSelector.propTypes = { name: PropTypes.string.isRequired, @@ -103,6 +109,7 @@ RoomSelector.propTypes = { isAlert: PropTypes.bool.isRequired, options: PropTypes.node, onClick: PropTypes.func.isRequired, + onContextMenu: PropTypes.func, }; export default RoomSelector; diff --git a/src/app/organisms/navigation/Selector.jsx b/src/app/organisms/navigation/Selector.jsx index 80a03574..e321db82 100644 --- a/src/app/organisms/navigation/Selector.jsx +++ b/src/app/organisms/navigation/Selector.jsx @@ -4,12 +4,13 @@ import PropTypes from 'prop-types'; import initMatrix from '../../../client/initMatrix'; import navigation from '../../../client/state/navigation'; -import { openRoomOptions } from '../../../client/action/navigation'; +import { openReusableContextMenu } from '../../../client/action/navigation'; import { createSpaceShortcut, deleteSpaceShortcut } from '../../../client/action/room'; import { getEventCords, abbreviateNumber } from '../../../util/common'; import IconButton from '../../atoms/button/IconButton'; import RoomSelector from '../../molecules/room-selector/RoomSelector'; +import RoomOptions from '../../molecules/room-optons/RoomOptions'; import HashIC from '../../../../public/res/ic/outlined/hash.svg'; import HashGlobeIC from '../../../../public/res/ic/outlined/hash-globe.svg'; @@ -49,6 +50,15 @@ function Selector({ }; }, []); + const openRoomOptions = (e) => { + e.preventDefault(); + openReusableContextMenu( + 'right', + getEventCords(e, '.room-selector'), + (closeMenu) => , + ); + }; + const joinRuleToIconSrc = (joinRule) => ({ restricted: () => (room.isSpaceRoom() ? SpaceIC : HashIC), invite: () => (room.isSpaceRoom() ? SpaceLockIC : HashLockIC), @@ -96,13 +106,14 @@ function Selector({ notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))} isAlert={noti.getHighlightNoti(roomId) !== 0} onClick={onClick} + onContextMenu={openRoomOptions} options={( openRoomOptions(getEventCords(e), roomId)} + onClick={openRoomOptions} /> )} /> diff --git a/src/app/organisms/profile-viewer/ProfileViewer.jsx b/src/app/organisms/profile-viewer/ProfileViewer.jsx index 73b722e5..23890125 100644 --- a/src/app/organisms/profile-viewer/ProfileViewer.jsx +++ b/src/app/organisms/profile-viewer/ProfileViewer.jsx @@ -7,26 +7,87 @@ import { twemojify } from '../../../util/twemojify'; import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; -import { selectRoom } from '../../../client/action/navigation'; +import { selectRoom, openReusableContextMenu } from '../../../client/action/navigation'; import * as roomActions from '../../../client/action/room'; import { getUsername, getUsernameOfRoomMember, getPowerLabel } from '../../../util/matrixUtil'; +import { getEventCords } from '../../../util/common'; import colorMXID from '../../../util/colorMXID'; import Text from '../../atoms/text/Text'; import Chip from '../../atoms/chip/Chip'; import IconButton from '../../atoms/button/IconButton'; +import Input from '../../atoms/input/Input'; import Avatar from '../../atoms/avatar/Avatar'; import Button from '../../atoms/button/Button'; +import { MenuItem } from '../../atoms/context-menu/ContextMenu'; +import PowerLevelSelector from '../../molecules/power-level-selector/PowerLevelSelector'; import Dialog from '../../molecules/dialog/Dialog'; -import SettingTile from '../../molecules/setting-tile/SettingTile'; import ShieldEmptyIC from '../../../../public/res/ic/outlined/shield-empty.svg'; +import ChevronRightIC from '../../../../public/res/ic/outlined/chevron-right.svg'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import { useForceUpdate } from '../../hooks/useForceUpdate'; + +function ModerationTools({ + roomId, userId, +}) { + const mx = initMatrix.matrixClient; + const room = mx.getRoom(roomId); + const roomMember = room.getMember(userId); + + const myPowerLevel = room.getMember(mx.getUserId()).powerLevel; + const powerLevel = roomMember?.powerLevel || 0; + const canIKick = ( + roomMember?.membership === 'join' + && room.currentState.hasSufficientPowerLevelFor('kick', myPowerLevel) + && powerLevel < myPowerLevel + ); + const canIBan = ( + ['join', 'leave'].includes(roomMember?.membership) + && room.currentState.hasSufficientPowerLevelFor('ban', myPowerLevel) + && powerLevel < myPowerLevel + ); + + const handleKick = (e) => { + e.preventDefault(); + const kickReason = e.target.elements['kick-reason']?.value.trim(); + roomActions.kick(roomId, userId, kickReason !== '' ? kickReason : undefined); + }; + + const handleBan = (e) => { + e.preventDefault(); + const banReason = e.target.elements['ban-reason']?.value.trim(); + roomActions.ban(roomId, userId, banReason !== '' ? banReason : undefined); + }; + + return ( +
+ {canIKick && ( +
+ + +
+ )} + {canIBan && ( +
+ + +
+ )} +
+ ); +} +ModerationTools.propTypes = { + roomId: PropTypes.string.isRequired, + userId: PropTypes.string.isRequired, +}; + function SessionInfo({ userId }) { const [devices, setDevices] = useState(null); + const [isVisible, setIsVisible] = useState(false); const mx = initMatrix.matrixClient; useEffect(() => { @@ -51,10 +112,11 @@ function SessionInfo({ userId }) { }, [userId]); function renderSessionChips() { + if (!isVisible) return null; return (
- {devices === null && Loading sessions...} - {devices?.length === 0 && No session found.} + {devices === null && Loading sessions...} + {devices?.length === 0 && No session found.} {devices !== null && (devices.map((device) => ( - + setIsVisible(!isVisible)} + iconSrc={isVisible ? ChevronBottomIC : ChevronRightIC} + > + {`View ${devices?.length > 0 ? `${devices.length} ` : ''}sessions`} + + {renderSessionChips()}
); } @@ -98,6 +163,8 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { const userPL = room.getMember(userId)?.powerLevel || 0; const canIKick = room.currentState.hasSufficientPowerLevelFor('kick', myPowerlevel) && userPL < myPowerlevel; + const isBanned = member?.membership === 'ban'; + const onCreated = (dmRoomId) => { if (isMountedRef.current === false) return; setIsCreatingDM(false); @@ -119,7 +186,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { setIsInviting(false); }, [userId]); - async function openDM() { + const openDM = async () => { const directIds = [...initMatrix.roomList.directs]; // Check and open if user already have a DM with userId. @@ -145,9 +212,9 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { if (isMountedRef.current === false) return; setIsCreatingDM(false); } - } + }; - async function toggleIgnore() { + const toggleIgnore = async () => { const ignoredUsers = mx.getIgnoredUsers(); const uIndex = ignoredUsers.indexOf(userId); if (uIndex >= 0) { @@ -165,9 +232,9 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { } catch { setIsIgnoring(false); } - } + }; - async function toggleInvite() { + const toggleInvite = async () => { try { setIsInviting(true); let isInviteSent = false; @@ -182,7 +249,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { } catch { setIsInviting(false); } - } + }; return (
@@ -193,7 +260,14 @@ function ProfileFooter({ roomId, userId, onRequestClose }) { > {isCreatingDM ? 'Creating room...' : 'Message'} - { member?.membership === 'join' && } + { isBanned && canIKick && ( + + )} { (isInvited ? canIKick : room.canInvite(mx.getUserId())) && isInvitable && (
+ { userId !== mx.getUserId() && ( - setIsOpen(false)} - /> + )} ); - } + }; return ( setIsOpen(false)} - contentOptions={ setIsOpen(false)} tooltip="Close" />} + onRequestClose={closeDialog} + contentOptions={} > {roomId ? renderProfile() :
}
diff --git a/src/app/organisms/profile-viewer/ProfileViewer.scss b/src/app/organisms/profile-viewer/ProfileViewer.scss index 230c8db7..1401b777 100644 --- a/src/app/organisms/profile-viewer/ProfileViewer.scss +++ b/src/app/organisms/profile-viewer/ProfileViewer.scss @@ -1,3 +1,4 @@ +@use '../../partials/flex'; @use '../../partials/dir'; .profile-viewer__dialog { @@ -15,7 +16,6 @@ &__user { display: flex; padding-bottom: var(--sp-normal); - border-bottom: 1px solid var(--bg-surface-border); &__info { align-self: flex-end; @@ -61,12 +61,47 @@ } } -.session-info { - & .setting-tile__title .text { - color: var(--tc-surface-high); +.profile-viewer__admin-tool { + .setting-tile { + margin-top: var(--sp-loose); } +} + +.moderation-tools { + & > form { + margin: var(--sp-normal) 0; + display: flex; + align-items: flex-end; + & .input-container { + @extend .cp-fx__item-one; + @include dir.side(margin, 0, var(--sp-tight)); + } + & button { + height: 46px; + } + } +} + +.session-info { + box-shadow: var(--bs-surface-border); + border-radius: var(--bo-radius); + overflow: hidden; + + & .context-menu__item button { + padding: var(--sp-extra-tight); + & .ic-raw { + @include dir.side(margin, 0, var(--sp-extra-tight)); + } + } + &__chips { + border-top: 1px solid var(--bg-surface-border); + padding: var(--sp-tight); padding-top: var(--sp-ultra-tight); + + & > .text { + margin-top: var(--sp-extra-tight); + } & .chip { margin-top: var(--sp-extra-tight); @include dir.side(margin, 0, var(--sp-extra-tight)); diff --git a/src/app/organisms/room-optons/RoomOptions.jsx b/src/app/organisms/room-optons/RoomOptions.jsx deleted file mode 100644 index a131ddc3..00000000 --- a/src/app/organisms/room-optons/RoomOptions.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import React, { useEffect, useRef } from 'react'; - -import { twemojify } from '../../../util/twemojify'; - -import initMatrix from '../../../client/initMatrix'; -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; -import { openInviteUser } from '../../../client/action/navigation'; -import * as roomActions from '../../../client/action/room'; - -import ContextMenu, { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu'; -import RoomNotification from '../../molecules/room-notification/RoomNotification'; - -import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg'; -import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg'; -import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg'; - -import { useForceUpdate } from '../../hooks/useForceUpdate'; - -let isRoomOptionVisible = false; -let roomId = null; -function RoomOptions() { - const openerRef = useRef(null); - const [, forceUpdate] = useForceUpdate(); - - function openRoomOptions(cords, rId) { - if (roomId !== null || isRoomOptionVisible) { - roomId = null; - if (cords.detail === 0) openerRef.current.click(); - return; - } - openerRef.current.style.transform = `translate(${cords.x}px, ${cords.y}px)`; - roomId = rId; - openerRef.current.click(); - forceUpdate(); - } - - const afterRoomOptionsToggle = (isVisible) => { - isRoomOptionVisible = isVisible; - if (!isVisible) { - setTimeout(() => { - if (!isRoomOptionVisible) roomId = null; - }, 500); - } - }; - - useEffect(() => { - navigation.on(cons.events.navigation.ROOMOPTIONS_OPENED, openRoomOptions); - return () => { - navigation.on(cons.events.navigation.ROOMOPTIONS_OPENED, openRoomOptions); - }; - }, []); - - const handleMarkAsRead = () => { - const mx = initMatrix.matrixClient; - const room = mx.getRoom(roomId); - if (!room) return; - const events = room.getLiveTimeline().getEvents(); - mx.sendReadReceipt(events[events.length - 1]); - }; - - const handleInviteClick = () => openInviteUser(roomId); - const handleLeaveClick = (toggleMenu) => { - if (confirm('Are you really want to leave this room?')) { - roomActions.leave(roomId); - toggleMenu(); - } - }; - - const mx = initMatrix.matrixClient; - const room = mx.getRoom(roomId); - const canInvite = room?.canInvite(mx.getUserId()); - - return ( - ( - <> - {twemojify(`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`)} - { - handleMarkAsRead(); toggleMenu(); - }} - > - Mark as read - - { - handleInviteClick(); toggleMenu(); - }} - > - Invite - - handleLeaveClick(toggleMenu)}>Leave - Notification - {roomId && } - - )} - render={(toggleMenu) => ( - - )} - /> - ); -} - -export default RoomOptions; diff --git a/src/app/organisms/room/PeopleDrawer.jsx b/src/app/organisms/room/PeopleDrawer.jsx index 2ab9976c..026173d4 100644 --- a/src/app/organisms/room/PeopleDrawer.jsx +++ b/src/app/organisms/room/PeopleDrawer.jsx @@ -107,7 +107,7 @@ function PeopleDrawer({ roomId }) { let isRoomChanged = false; const updateMemberList = (event) => { if (isGettingMembers) return; - if (event && event?.event?.room_id !== roomId) return; + if (event && event?.getRoomId() !== roomId) return; setMemberList( simplyfiMembers( getMembersWithMembership(membership) @@ -125,6 +125,7 @@ function PeopleDrawer({ roomId }) { asyncSearch.on(asyncSearch.RESULT_SENT, handleSearchData); mx.on('RoomMember.membership', updateMemberList); + mx.on('RoomMember.powerLevel', updateMemberList); return () => { isRoomChanged = true; setMemberList([]); @@ -132,6 +133,7 @@ function PeopleDrawer({ roomId }) { setItemCount(PER_PAGE_MEMBER); asyncSearch.removeListener(asyncSearch.RESULT_SENT, handleSearchData); mx.removeListener('RoomMember.membership', updateMemberList); + mx.removeListener('RoomMember.powerLevel', updateMemberList); }; }, [roomId, membership]); diff --git a/src/app/organisms/room/RoomView.scss b/src/app/organisms/room/RoomView.scss index dcf3edda..2eda3ea7 100644 --- a/src/app/organisms/room/RoomView.scss +++ b/src/app/organisms/room/RoomView.scss @@ -15,7 +15,7 @@ &--dropped { transform: translateY(calc(100% - var(--header-height))); border-radius: var(--bo-radius) var(--bo-radius) 0 0; - box-shadow: 0 0 0 1px var(--bg-surface-border); + box-shadow: var(--bs-popup); } &__content-wrapper { diff --git a/src/app/organisms/room/RoomViewHeader.jsx b/src/app/organisms/room/RoomViewHeader.jsx index 847eb6ea..9c12a7e7 100644 --- a/src/app/organisms/room/RoomViewHeader.jsx +++ b/src/app/organisms/room/RoomViewHeader.jsx @@ -8,7 +8,7 @@ import { blurOnBubbling } from '../../atoms/button/script'; import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; -import { toggleRoomSettings, openRoomOptions } from '../../../client/action/navigation'; +import { toggleRoomSettings, openReusableContextMenu } from '../../../client/action/navigation'; import { togglePeopleDrawer } from '../../../client/action/settings'; import colorMXID from '../../../util/colorMXID'; import { getEventCords } from '../../../util/common'; @@ -18,6 +18,7 @@ import RawIcon from '../../atoms/system-icons/RawIcon'; import IconButton from '../../atoms/button/IconButton'; import Header, { TitleWrapper } from '../../atoms/header/Header'; import Avatar from '../../atoms/avatar/Avatar'; +import RoomOptions from '../../molecules/room-optons/RoomOptions'; import UserIC from '../../../../public/res/ic/outlined/user.svg'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; @@ -60,6 +61,14 @@ function RoomViewHeader({ roomId }) { }; }, [roomId]); + const openRoomOptions = (e) => { + openReusableContextMenu( + 'bottom', + getEventCords(e, '.ic-btn'), + (closeMenu) => , + ); + }; + return (