diff --git a/res/css/views/right_panel/_PinnedMessagesCard.scss b/res/css/views/right_panel/_PinnedMessagesCard.scss index 4cbc8b9abc..b6b8238bed 100644 --- a/res/css/views/right_panel/_PinnedMessagesCard.scss +++ b/res/css/views/right_panel/_PinnedMessagesCard.scss @@ -15,227 +15,21 @@ limitations under the License. */ .mx_PinnedMessagesCard { + padding-top: 0; + .mx_BaseCard_header { text-align: center; - margin-top: 20px; + margin-top: 0; + border-bottom: 1px solid $menu-border-color; - h2 { + > h2 { font-weight: $font-semi-bold; font-size: $font-18px; - margin: 12px 0 4px; + margin: 8px 0; } - .mx_RoomSummaryCard_alias { - font-size: $font-13px; - color: $secondary-fg-color; - } - - h2, .mx_RoomSummaryCard_alias { - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - white-space: pre-wrap; - } - - .mx_RoomSummaryCard_avatar { - display: inline-flex; - - .mx_RoomSummaryCard_e2ee { - display: inline-block; - position: relative; - width: 54px; - height: 54px; - border-radius: 50%; - background-color: #737d8c; - margin-top: -3px; // alignment - margin-left: -10px; // overlap - border: 3px solid $dark-panel-bg-color; - - &::before { - content: ''; - position: absolute; - top: 13px; - left: 13px; - height: 28px; - width: 28px; - mask-size: cover; - mask-repeat: no-repeat; - mask-position: center; - mask-image: url('$(res)/img/e2e/disabled.svg'); - background-color: #ffffff; - } - } - - .mx_RoomSummaryCard_e2ee_normal { - background-color: #424446; - &::before { - mask-image: url('$(res)/img/e2e/normal.svg'); - } - } - - .mx_RoomSummaryCard_e2ee_verified { - background-color: #0dbd8b; - &::before { - mask-image: url('$(res)/img/e2e/verified.svg'); - } - } - - .mx_RoomSummaryCard_e2ee_warning { - background-color: #ff4b55; - &::before { - mask-image: url('$(res)/img/e2e/warning.svg'); - } - } + .mx_BaseCard_close { + margin-right: 6px; } } - - .mx_RoomSummaryCard_aboutGroup { - .mx_RoomSummaryCard_Button { - padding-left: 44px; - - &::before { - content: ''; - position: absolute; - top: 8px; - left: 10px; - height: 24px; - width: 24px; - mask-repeat: no-repeat; - mask-position: center; - background-color: $icon-button-color; - } - } - } - - .mx_RoomSummaryCard_appsGroup { - .mx_RoomSummaryCard_Button { - // this button is special so we have to override some of the original styling - // as we will be applying it in its children - padding: 0; - height: auto; - color: $tertiary-fg-color; - - .mx_RoomSummaryCard_icon_app { - padding: 10px 48px 10px 12px; // based on typical mx_RoomSummaryCard_Button padding - text-overflow: ellipsis; - overflow: hidden; - - .mx_BaseAvatar_image { - vertical-align: top; - margin-right: 12px; - } - - span { - color: $primary-fg-color; - } - } - - .mx_RoomSummaryCard_app_pinToggle, - .mx_RoomSummaryCard_app_options { - position: absolute; - top: 0; - height: 100%; // to give bigger interactive zone - width: 24px; - padding: 12px 4px; - box-sizing: border-box; - min-width: 24px; // prevent flexbox crushing - - &:hover { - &::after { - content: ''; - position: absolute; - height: 24px; - width: 24px; - top: 8px; // equal to padding-top of parent - left: 0; - border-radius: 12px; - background-color: rgba(141, 151, 165, 0.1); - } - } - - &::before { - content: ''; - position: absolute; - height: 16px; - width: 16px; - mask-repeat: no-repeat; - mask-position: center; - mask-size: 16px; - background-color: $icon-button-color; - } - } - - .mx_RoomSummaryCard_app_pinToggle { - right: 24px; - - &::before { - mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); - } - } - - .mx_RoomSummaryCard_app_options { - right: 48px; - display: none; - - &::before { - mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); - } - } - - &.mx_RoomSummaryCard_Button_pinned { - &::after { - opacity: 0.2; - } - - .mx_RoomSummaryCard_app_pinToggle::before { - background-color: $accent-color; - } - } - - &:hover { - .mx_RoomSummaryCard_icon_app { - padding-right: 72px; - } - - .mx_RoomSummaryCard_app_options { - display: unset; - } - } - - &::before { - content: unset; - } - - &::after { - top: 8px; // re-align based on the height change - pointer-events: none; // pass through to the real button - } - } - } - - .mx_AccessibleButton_kind_link { - padding: 0; - margin-top: 12px; - margin-bottom: 12px; - font-size: $font-13px; - font-weight: $font-semi-bold; - } -} - -.mx_RoomSummaryCard_icon_people::before { - mask-image: url("$(res)/img/element-icons/room/members.svg"); -} - -.mx_RoomSummaryCard_icon_files::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); -} - -.mx_RoomSummaryCard_icon_share::before { - mask-image: url('$(res)/img/element-icons/room/share.svg'); -} - -.mx_RoomSummaryCard_icon_settings::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); } diff --git a/res/css/views/rooms/_PinnedEventTile.scss b/res/css/views/rooms/_PinnedEventTile.scss index 030a76674a..8e96f3caeb 100644 --- a/res/css/views/rooms/_PinnedEventTile.scss +++ b/res/css/views/rooms/_PinnedEventTile.scss @@ -16,62 +16,90 @@ limitations under the License. .mx_PinnedEventTile { min-height: 40px; - margin-bottom: 5px; width: 100%; - border-radius: 5px; // for the hover -} + padding: 0 4px 12px; -.mx_PinnedEventTile:hover { - background-color: $event-selected-color; -} + display: grid; + grid-template-areas: "avatar name remove" + "content content content" + "footer footer footer"; + grid-template-rows: max-content auto max-content; + grid-template-columns: 24px auto 24px; + grid-row-gap: 12px; + grid-column-gap: 8px; -.mx_PinnedEventTile .mx_PinnedEventTile_sender, -.mx_PinnedEventTile .mx_PinnedEventTile_timestamp { - color: #868686; - font-size: 0.8em; - vertical-align: top; - display: inline-block; - padding-bottom: 3px; -} + & + .mx_PinnedEventTile { + padding: 12px 4px; + border-top: 1px solid $menu-border-color; + } -.mx_PinnedEventTile .mx_PinnedEventTile_timestamp { - padding-left: 15px; - display: none; -} + .mx_PinnedEventTile_senderAvatar { + grid-area: avatar; + } -.mx_PinnedEventTile .mx_PinnedEventTile_senderAvatar .mx_BaseAvatar { - float: left; - margin-right: 10px; -} + .mx_PinnedEventTile_sender { + grid-area: name; + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-24px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } -.mx_PinnedEventTile_actions { - float: right; - margin-right: 10px; - display: none; -} + .mx_PinnedEventTile_unpinButton { + visibility: hidden; + grid-area: remove; + position: relative; + width: 24px; + height: 24px; + border-radius: 8px; -.mx_PinnedEventTile:hover .mx_PinnedEventTile_timestamp { - display: inline-block; -} + &:hover { + background-color: $roomheader-addroom-bg-color; + } -.mx_PinnedEventTile:hover .mx_PinnedEventTile_actions { - display: block; -} + &::before { + content: ""; + position: absolute; + //top: 0; + //left: 0; + height: inherit; + width: inherit; + background: $secondary-fg-color; + mask-position: center; + mask-size: 8px; + mask-repeat: no-repeat; + mask-image: url('$(res)/img/image-view/close.svg'); + } + } -.mx_PinnedEventTile_unpinButton { - display: inline-block; - cursor: pointer; - margin-left: 10px; -} + .mx_PinnedEventTile_message { + grid-area: content; + } -.mx_PinnedEventTile_gotoButton { - display: inline-block; - font-size: 0.7em; // Smaller text to avoid conflicting with the layout -} + .mx_PinnedEventTile_footer { + grid-area: footer; + font-size: 10px; + line-height: 12px; -.mx_PinnedEventTile_message { - margin-left: 50px; - position: relative; - top: 0; - left: 0; + .mx_PinnedEventTile_timestamp { + font-size: inherit; + line-height: inherit; + color: $secondary-fg-color; + } + + .mx_AccessibleButton_kind_link { + padding: 0; + margin-left: 12px; + font-size: inherit; + line-height: inherit; + } + } + + &:hover { + .mx_PinnedEventTile_unpinButton { + visibility: visible; + } + } } diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 579b12c3cf..48be8cd706 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -16,6 +16,7 @@ limitations under the License. import React, {useCallback, useContext, useEffect, useState} from "react"; import { Room } from "matrix-js-sdk/src/models/room"; +import { RoomState } from "matrix-js-sdk/src/models/room-state"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventType } from 'matrix-js-sdk/src/@types/event'; @@ -42,7 +43,7 @@ export const usePinnedEvents = (room: Room): string[] => { setPinnedEvents(room.currentState.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent()?.pinned || []); }, [room]); - useEventEmitter(room.currentState, "RoomState.events", update); + useEventEmitter(room?.currentState, "RoomState.events", update); useEffect(() => { update(); return () => { @@ -53,7 +54,6 @@ export const usePinnedEvents = (room: Room): string[] => { }; const ReadPinsEventId = "im.vector.room.read_pins"; -const ReadPinsNumIds = 10; export const useReadPinnedEvents = (room: Room): Set => { const [readPinnedEvents, setReadPinnedEvents] = useState>(new Set()); @@ -75,20 +75,36 @@ export const useReadPinnedEvents = (room: Room): Set => { return readPinnedEvents; }; +const useRoomState = (room: Room, mapper: (state: RoomState) => T): T => { + const [value, setValue] = useState(room ? mapper(room.currentState) : undefined); + + const update = useCallback(() => { + if (!room) return; + setValue(mapper(room.currentState)); + }, [room, mapper]); + + useEventEmitter(room?.currentState, "RoomState.events", update); + useEffect(() => { + update(); + return () => { + setValue(undefined); + }; + }, [update]); + return value; +}; + const PinnedMessagesCard = ({ room, onClose }: IProps) => { const cli = useContext(MatrixClientContext); + const canUnpin = useRoomState(room, state => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli)); const pinnedEventIds = usePinnedEvents(room); const readPinnedEvents = useReadPinnedEvents(room); useEffect(() => { const newlyRead = pinnedEventIds.filter(id => !readPinnedEvents.has(id)); if (newlyRead.length > 0) { - // Only keep the last N event IDs to avoid infinite growth + // clear out any read pinned events which no longer are pinned cli.setRoomAccountData(room.roomId, ReadPinsEventId, { - event_ids: [ - ...newlyRead.reverse(), - ...readPinnedEvents, - ].splice(0, ReadPinsNumIds), + event_ids: pinnedEventIds, }); } }, [cli, room.roomId, pinnedEventIds, readPinnedEvents]); @@ -122,24 +138,35 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => { if (!pinnedEvents) { content = ; } else if (pinnedEvents.length > 0) { - content = pinnedEvents.filter(Boolean).map(ev => ( - {}} - /> + let onUnpinClicked; + if (canUnpin) { + onUnpinClicked = async (event: MatrixEvent) => { + const pinnedEvents = room.currentState.getStateEvents(EventType.RoomPinnedEvents, ""); + if (pinnedEvents?.getContent()?.pinned) { + const pinned = pinnedEvents.getContent().pinned; + const index = pinned.indexOf(event.getId()); + if (index !== -1) { + pinned.splice(index, 1); + await cli.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned }, ""); + } + } + }; + } + + // show them in reverse, with latest pinned at the top + content = pinnedEvents.filter(Boolean).reverse().map(ev => ( + )); } else { - content =
+ content =

{_t("You’re all caught up")}

{_t("You have no visible notifications.")}

; } return { _t("Pinned") }} - className="mx_NotificationPanel" + header={

{ _t("Pinned messages") }

} + className="mx_PinnedMessagesCard" onClose={onClose} > { content } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 3d3f33be00..7ff7207fb6 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -55,7 +55,7 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }) => { return { + static contextType = MatrixClientContext; + private onTileClicked = () => { dis.dispatch({ action: 'view_room', - event_id: this.props.mxEvent.getId(), + event_id: this.props.event.getId(), highlighted: true, - room_id: this.props.mxEvent.getRoomId(), + room_id: this.props.event.getRoomId(), }); }; - private onUnpinClicked = () => { - const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", ""); - if (!pinnedEvents || !pinnedEvents.getContent().pinned) { - // Nothing to do: already unpinned - if (this.props.onUnpinned) this.props.onUnpinned(); - } else { - const pinned = pinnedEvents.getContent().pinned; - const index = pinned.indexOf(this.props.mxEvent.getId()); - if (index !== -1) { - pinned.splice(index, 1); - MatrixClientPeg.get().sendStateEvent(this.props.mxRoom.roomId, 'm.room.pinned_events', {pinned}, '') - .then(() => { - if (this.props.onUnpinned) this.props.onUnpinned(); - }); - } else if (this.props.onUnpinned) this.props.onUnpinned(); - } - }; - - private canUnpin() { - return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get()); - } - render() { - const sender = this.props.mxEvent.getSender(); - // Get the latest sender profile rather than historical - const senderProfile = this.props.mxRoom.getMember(sender); - const avatarSize = 40; + const sender = this.props.event.getSender(); + const senderProfile = this.props.room.getMember(sender); let unpinButton = null; - if (this.canUnpin()) { + if (this.props.onUnpinClicked) { unpinButton = ( - - {_t('Unpin - + ); } - return ( -
-
- - { _t("Jump to message") } - - { unpinButton } -
+ return
+ - - - - - { senderProfile ? senderProfile.name : sender } - - - { formatFullDate(new Date(this.props.mxEvent.getTs())) } - -
- {}} // we need to give this, apparently - /> -
+ + { senderProfile?.name || sender } + + + { unpinButton } + +
+ {}} // we need to give this, apparently + />
- ); + +
+ + { formatDate(new Date(this.props.event.getTs())) } + + + + { _t("View message") } + +
+
; } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0aebfc48ce..d5b37a7d2d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1509,7 +1509,7 @@ "Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.", "This is the start of .": "This is the start of .", "Unpin Message": "Unpin Message", - "Jump to message": "Jump to message", + "View message": "View message", "%(duration)ss": "%(duration)ss", "%(duration)sm": "%(duration)sm", "%(duration)sh": "%(duration)sh", @@ -1717,8 +1717,7 @@ "Yours, or the other users’ session": "Yours, or the other users’ session", "You’re all caught up": "You’re all caught up", "You have no visible notifications.": "You have no visible notifications.", - "Pinned": "Pinned", - "Pinned Messages": "Pinned Messages", + "Pinned messages": "Pinned messages", "Room Info": "Room Info", "You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets", "Unpin": "Unpin", @@ -1952,7 +1951,6 @@ "Rotate Right": "Rotate Right", "Download": "Download", "Information": "Information", - "View message": "View message", "Language Dropdown": "Language Dropdown", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index 0d96f92945..d62f6c6110 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -44,6 +44,7 @@ export enum RightPanelPhases { export const RIGHT_PANEL_PHASES_NO_ARGS = [ RightPanelPhases.RoomSummary, RightPanelPhases.NotificationPanel, + RightPanelPhases.PinnedMessages, RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, RightPanelPhases.GroupMemberList,