Iterate pinned messages
This commit is contained in:
parent
fd74a946e0
commit
27ad90760d
7 changed files with 185 additions and 361 deletions
|
@ -15,227 +15,21 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_PinnedMessagesCard {
|
.mx_PinnedMessagesCard {
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
.mx_BaseCard_header {
|
.mx_BaseCard_header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 20px;
|
margin-top: 0;
|
||||||
|
border-bottom: 1px solid $menu-border-color;
|
||||||
|
|
||||||
h2 {
|
> h2 {
|
||||||
font-weight: $font-semi-bold;
|
font-weight: $font-semi-bold;
|
||||||
font-size: $font-18px;
|
font-size: $font-18px;
|
||||||
margin: 12px 0 4px;
|
margin: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSummaryCard_alias {
|
.mx_BaseCard_close {
|
||||||
font-size: $font-13px;
|
margin-right: 6px;
|
||||||
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_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');
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,62 +16,90 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_PinnedEventTile {
|
.mx_PinnedEventTile {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
margin-bottom: 5px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 5px; // for the hover
|
padding: 0 4px 12px;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
padding: 12px 4px;
|
||||||
|
border-top: 1px solid $menu-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_PinnedEventTile:hover {
|
.mx_PinnedEventTile_senderAvatar {
|
||||||
background-color: $event-selected-color;
|
grid-area: avatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_PinnedEventTile .mx_PinnedEventTile_sender,
|
.mx_PinnedEventTile_sender {
|
||||||
.mx_PinnedEventTile .mx_PinnedEventTile_timestamp {
|
grid-area: name;
|
||||||
color: #868686;
|
font-weight: $font-semi-bold;
|
||||||
font-size: 0.8em;
|
font-size: $font-15px;
|
||||||
vertical-align: top;
|
line-height: $font-24px;
|
||||||
display: inline-block;
|
text-overflow: ellipsis;
|
||||||
padding-bottom: 3px;
|
overflow: hidden;
|
||||||
}
|
white-space: nowrap;
|
||||||
|
|
||||||
.mx_PinnedEventTile .mx_PinnedEventTile_timestamp {
|
|
||||||
padding-left: 15px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_PinnedEventTile .mx_PinnedEventTile_senderAvatar .mx_BaseAvatar {
|
|
||||||
float: left;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_PinnedEventTile_actions {
|
|
||||||
float: right;
|
|
||||||
margin-right: 10px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_PinnedEventTile:hover .mx_PinnedEventTile_timestamp {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_PinnedEventTile:hover .mx_PinnedEventTile_actions {
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_PinnedEventTile_unpinButton {
|
.mx_PinnedEventTile_unpinButton {
|
||||||
display: inline-block;
|
visibility: hidden;
|
||||||
cursor: pointer;
|
grid-area: remove;
|
||||||
margin-left: 10px;
|
position: relative;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $roomheader-addroom-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_PinnedEventTile_gotoButton {
|
&::before {
|
||||||
display: inline-block;
|
content: "";
|
||||||
font-size: 0.7em; // Smaller text to avoid conflicting with the layout
|
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_message {
|
.mx_PinnedEventTile_message {
|
||||||
margin-left: 50px;
|
grid-area: content;
|
||||||
position: relative;
|
}
|
||||||
top: 0;
|
|
||||||
left: 0;
|
.mx_PinnedEventTile_footer {
|
||||||
|
grid-area: footer;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 12px;
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, {useCallback, useContext, useEffect, useState} from "react";
|
import React, {useCallback, useContext, useEffect, useState} from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
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 { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { EventType } from 'matrix-js-sdk/src/@types/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 || []);
|
setPinnedEvents(room.currentState.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent()?.pinned || []);
|
||||||
}, [room]);
|
}, [room]);
|
||||||
|
|
||||||
useEventEmitter(room.currentState, "RoomState.events", update);
|
useEventEmitter(room?.currentState, "RoomState.events", update);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
update();
|
update();
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -53,7 +54,6 @@ export const usePinnedEvents = (room: Room): string[] => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReadPinsEventId = "im.vector.room.read_pins";
|
const ReadPinsEventId = "im.vector.room.read_pins";
|
||||||
const ReadPinsNumIds = 10;
|
|
||||||
|
|
||||||
export const useReadPinnedEvents = (room: Room): Set<string> => {
|
export const useReadPinnedEvents = (room: Room): Set<string> => {
|
||||||
const [readPinnedEvents, setReadPinnedEvents] = useState<Set<string>>(new Set());
|
const [readPinnedEvents, setReadPinnedEvents] = useState<Set<string>>(new Set());
|
||||||
|
@ -75,20 +75,36 @@ export const useReadPinnedEvents = (room: Room): Set<string> => {
|
||||||
return readPinnedEvents;
|
return readPinnedEvents;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useRoomState = <T extends any>(room: Room, mapper: (state: RoomState) => T): T => {
|
||||||
|
const [value, setValue] = useState<T>(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 PinnedMessagesCard = ({ room, onClose }: IProps) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const canUnpin = useRoomState(room, state => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli));
|
||||||
const pinnedEventIds = usePinnedEvents(room);
|
const pinnedEventIds = usePinnedEvents(room);
|
||||||
const readPinnedEvents = useReadPinnedEvents(room);
|
const readPinnedEvents = useReadPinnedEvents(room);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newlyRead = pinnedEventIds.filter(id => !readPinnedEvents.has(id));
|
const newlyRead = pinnedEventIds.filter(id => !readPinnedEvents.has(id));
|
||||||
if (newlyRead.length > 0) {
|
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, {
|
cli.setRoomAccountData(room.roomId, ReadPinsEventId, {
|
||||||
event_ids: [
|
event_ids: pinnedEventIds,
|
||||||
...newlyRead.reverse(),
|
|
||||||
...readPinnedEvents,
|
|
||||||
].splice(0, ReadPinsNumIds),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [cli, room.roomId, pinnedEventIds, readPinnedEvents]);
|
}, [cli, room.roomId, pinnedEventIds, readPinnedEvents]);
|
||||||
|
@ -122,24 +138,35 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => {
|
||||||
if (!pinnedEvents) {
|
if (!pinnedEvents) {
|
||||||
content = <Spinner />;
|
content = <Spinner />;
|
||||||
} else if (pinnedEvents.length > 0) {
|
} else if (pinnedEvents.length > 0) {
|
||||||
content = pinnedEvents.filter(Boolean).map(ev => (
|
let onUnpinClicked;
|
||||||
<PinnedEventTile
|
if (canUnpin) {
|
||||||
key={ev.getId()}
|
onUnpinClicked = async (event: MatrixEvent) => {
|
||||||
mxRoom={room}
|
const pinnedEvents = room.currentState.getStateEvents(EventType.RoomPinnedEvents, "");
|
||||||
mxEvent={ev}
|
if (pinnedEvents?.getContent()?.pinned) {
|
||||||
onUnpinned={() => {}}
|
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 => (
|
||||||
|
<PinnedEventTile key={ev.getId()} room={room} event={ev} onUnpinClicked={onUnpinClicked} />
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
content = <div className="mx_RightPanel_empty mx_NotificationPanel_empty">
|
content = <div className="mx_RightPanel_empty mx_PinnedMessagesCard_empty">
|
||||||
<h2>{_t("You’re all caught up")}</h2>
|
<h2>{_t("You’re all caught up")}</h2>
|
||||||
<p>{_t("You have no visible notifications.")}</p>
|
<p>{_t("You have no visible notifications.")}</p>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <BaseCard
|
return <BaseCard
|
||||||
header={<h2>{ _t("Pinned") }</h2>}
|
header={<h2>{ _t("Pinned messages") }</h2>}
|
||||||
className="mx_NotificationPanel"
|
className="mx_PinnedMessagesCard"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
{ content }
|
{ content }
|
||||||
|
|
|
@ -55,7 +55,7 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }) => {
|
||||||
|
|
||||||
return <HeaderButton
|
return <HeaderButton
|
||||||
name="pinnedMessagesButton"
|
name="pinnedMessagesButton"
|
||||||
title={_t("Pinned Messages")}
|
title={_t("Pinned messages")}
|
||||||
isHighlighted={isHighlighted}
|
isHighlighted={isHighlighted}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
analytics={["Right Panel", "Pinned Messages Button", "click"]}
|
analytics={["Right Panel", "Pinned Messages Button", "click"]}
|
||||||
|
|
|
@ -19,110 +19,86 @@ import React from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import MessageEvent from "../messages/MessageEvent";
|
import MessageEvent from "../messages/MessageEvent";
|
||||||
import MemberAvatar from "../avatars/MemberAvatar";
|
import MemberAvatar from "../avatars/MemberAvatar";
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {formatFullDate} from '../../../DateUtils';
|
import { formatDate } from '../../../DateUtils';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
|
||||||
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
mxRoom: Room;
|
room: Room;
|
||||||
mxEvent: MatrixEvent;
|
event: MatrixEvent;
|
||||||
onUnpinned?(): void;
|
onUnpinClicked?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AVATAR_SIZE = 24;
|
||||||
|
|
||||||
@replaceableComponent("views.rooms.PinnedEventTile")
|
@replaceableComponent("views.rooms.PinnedEventTile")
|
||||||
export default class PinnedEventTile extends React.Component<IProps> {
|
export default class PinnedEventTile extends React.Component<IProps> {
|
||||||
|
static contextType = MatrixClientContext;
|
||||||
|
|
||||||
private onTileClicked = () => {
|
private onTileClicked = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
event_id: this.props.mxEvent.getId(),
|
event_id: this.props.event.getId(),
|
||||||
highlighted: true,
|
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() {
|
render() {
|
||||||
const sender = this.props.mxEvent.getSender();
|
const sender = this.props.event.getSender();
|
||||||
// Get the latest sender profile rather than historical
|
const senderProfile = this.props.room.getMember(sender);
|
||||||
const senderProfile = this.props.mxRoom.getMember(sender);
|
|
||||||
const avatarSize = 40;
|
|
||||||
|
|
||||||
let unpinButton = null;
|
let unpinButton = null;
|
||||||
if (this.canUnpin()) {
|
if (this.props.onUnpinClicked) {
|
||||||
unpinButton = (
|
unpinButton = (
|
||||||
<AccessibleButton onClick={this.onUnpinClicked} className="mx_PinnedEventTile_unpinButton">
|
<AccessibleTooltipButton
|
||||||
<img
|
onClick={this.props.onUnpinClicked}
|
||||||
src={require("../../../../res/img/cancel-red.svg")}
|
className="mx_PinnedEventTile_unpinButton"
|
||||||
width="8"
|
title={_t("Unpin")}
|
||||||
height="8"
|
|
||||||
alt={_t('Unpin Message')}
|
|
||||||
title={_t('Unpin Message')}
|
|
||||||
/>
|
/>
|
||||||
</AccessibleButton>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <div className="mx_PinnedEventTile">
|
||||||
<div className="mx_PinnedEventTile">
|
|
||||||
<div className="mx_PinnedEventTile_actions">
|
|
||||||
<AccessibleButton
|
|
||||||
className="mx_PinnedEventTile_gotoButton mx_textButton"
|
|
||||||
onClick={this.onTileClicked}
|
|
||||||
>
|
|
||||||
{ _t("Jump to message") }
|
|
||||||
</AccessibleButton>
|
|
||||||
{ unpinButton }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span className="mx_PinnedEventTile_senderAvatar">
|
|
||||||
<MemberAvatar
|
<MemberAvatar
|
||||||
|
className="mx_PinnedEventTile_senderAvatar"
|
||||||
member={senderProfile}
|
member={senderProfile}
|
||||||
width={avatarSize}
|
width={AVATAR_SIZE}
|
||||||
height={avatarSize}
|
height={AVATAR_SIZE}
|
||||||
fallbackUserId={sender}
|
fallbackUserId={sender}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<span className={"mx_PinnedEventTile_sender " + getUserNameColorClass(sender)}>
|
||||||
|
{ senderProfile?.name || sender }
|
||||||
</span>
|
</span>
|
||||||
<span className="mx_PinnedEventTile_sender">
|
|
||||||
{ senderProfile ? senderProfile.name : sender }
|
{ unpinButton }
|
||||||
</span>
|
|
||||||
<span className="mx_PinnedEventTile_timestamp">
|
|
||||||
{ formatFullDate(new Date(this.props.mxEvent.getTs())) }
|
|
||||||
</span>
|
|
||||||
<div className="mx_PinnedEventTile_message">
|
<div className="mx_PinnedEventTile_message">
|
||||||
<MessageEvent
|
<MessageEvent
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.event}
|
||||||
className="mx_PinnedEventTile_body"
|
className="mx_PinnedEventTile_body"
|
||||||
maxImageHeight={150}
|
maxImageHeight={150}
|
||||||
onHeightChanged={() => {}} // we need to give this, apparently
|
onHeightChanged={() => {}} // we need to give this, apparently
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mx_PinnedEventTile_footer">
|
||||||
|
<span className="mx_PinnedEventTile_timestamp">
|
||||||
|
{ formatDate(new Date(this.props.event.getTs())) }
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<AccessibleButton onClick={this.onTileClicked} kind="link">
|
||||||
|
{ _t("View message") }
|
||||||
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1509,7 +1509,7 @@
|
||||||
"Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.",
|
"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 <roomName/>.": "This is the start of <roomName/>.",
|
"This is the start of <roomName/>.": "This is the start of <roomName/>.",
|
||||||
"Unpin Message": "Unpin Message",
|
"Unpin Message": "Unpin Message",
|
||||||
"Jump to message": "Jump to message",
|
"View message": "View message",
|
||||||
"%(duration)ss": "%(duration)ss",
|
"%(duration)ss": "%(duration)ss",
|
||||||
"%(duration)sm": "%(duration)sm",
|
"%(duration)sm": "%(duration)sm",
|
||||||
"%(duration)sh": "%(duration)sh",
|
"%(duration)sh": "%(duration)sh",
|
||||||
|
@ -1717,8 +1717,7 @@
|
||||||
"Yours, or the other users’ session": "Yours, or the other users’ session",
|
"Yours, or the other users’ session": "Yours, or the other users’ session",
|
||||||
"You’re all caught up": "You’re all caught up",
|
"You’re all caught up": "You’re all caught up",
|
||||||
"You have no visible notifications.": "You have no visible notifications.",
|
"You have no visible notifications.": "You have no visible notifications.",
|
||||||
"Pinned": "Pinned",
|
"Pinned messages": "Pinned messages",
|
||||||
"Pinned Messages": "Pinned Messages",
|
|
||||||
"Room Info": "Room Info",
|
"Room Info": "Room Info",
|
||||||
"You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets",
|
"You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets",
|
||||||
"Unpin": "Unpin",
|
"Unpin": "Unpin",
|
||||||
|
@ -1952,7 +1951,6 @@
|
||||||
"Rotate Right": "Rotate Right",
|
"Rotate Right": "Rotate Right",
|
||||||
"Download": "Download",
|
"Download": "Download",
|
||||||
"Information": "Information",
|
"Information": "Information",
|
||||||
"View message": "View message",
|
|
||||||
"Language Dropdown": "Language Dropdown",
|
"Language Dropdown": "Language Dropdown",
|
||||||
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
|
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
|
||||||
"%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times",
|
"%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times",
|
||||||
|
|
|
@ -44,6 +44,7 @@ export enum RightPanelPhases {
|
||||||
export const RIGHT_PANEL_PHASES_NO_ARGS = [
|
export const RIGHT_PANEL_PHASES_NO_ARGS = [
|
||||||
RightPanelPhases.RoomSummary,
|
RightPanelPhases.RoomSummary,
|
||||||
RightPanelPhases.NotificationPanel,
|
RightPanelPhases.NotificationPanel,
|
||||||
|
RightPanelPhases.PinnedMessages,
|
||||||
RightPanelPhases.FilePanel,
|
RightPanelPhases.FilePanel,
|
||||||
RightPanelPhases.RoomMemberList,
|
RightPanelPhases.RoomMemberList,
|
||||||
RightPanelPhases.GroupMemberList,
|
RightPanelPhases.GroupMemberList,
|
||||||
|
|
Loading…
Reference in a new issue