Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 60fe70b

Browse files
authored
Add a prefix to file, poll, image, video and audio in the pinned message banner (#12950)
* Move event preview to its own component * Remove unused parameter * Add prefix to file, audio, video and image in the pinned message banner * Add prefix to poll in the pinned message banner * Add tests
1 parent 9d8c5b6 commit 60fe70b

File tree

5 files changed

+359
-10
lines changed

5 files changed

+359
-10
lines changed

res/css/views/rooms/_PinnedMessageBanner.pcss

+4
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@
9494
overflow: hidden;
9595
text-overflow: ellipsis;
9696
white-space: nowrap;
97+
98+
.mx_PinnedMessageBanner_prefix {
99+
font: var(--cpd-font-body-sm-semibold);
100+
}
97101
}
98102

99103
.mx_PinnedMessageBanner_redactedMessage {

src/components/views/rooms/PinnedMessageBanner.tsx

+81-9
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import React, { JSX, useEffect, useMemo, useState } from "react";
1818
import { Icon as PinIcon } from "@vector-im/compound-design-tokens/icons/pin-solid.svg";
1919
import { Button } from "@vector-im/compound-web";
20-
import { Room } from "matrix-js-sdk/src/matrix";
20+
import { M_POLL_START, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix";
2121
import classNames from "classnames";
2222

2323
import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
@@ -59,16 +59,10 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
5959
const [currentEventIndex, setCurrentEventIndex] = useState(eventCount - 1);
6060
// When the number of pinned messages changes, we want to display the last message
6161
useEffect(() => {
62-
setCurrentEventIndex((currentEventIndex) => eventCount - 1);
62+
setCurrentEventIndex(() => eventCount - 1);
6363
}, [eventCount]);
6464

6565
const pinnedEvent = pinnedEvents[currentEventIndex];
66-
// Generate a preview for the pinned event
67-
const eventPreview = useMemo(() => {
68-
if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null;
69-
return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent);
70-
}, [pinnedEvent]);
71-
7266
if (!pinnedEvent) return null;
7367

7468
const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure();
@@ -116,7 +110,7 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
116110
)}
117111
</div>
118112
)}
119-
{eventPreview && <span className="mx_PinnedMessageBanner_message">{eventPreview}</span>}
113+
<EventPreview pinnedEvent={pinnedEvent} />
120114
{/* In case of redacted event, we want to display the nice sentence of the message event like in the timeline or in the pinned message list */}
121115
{shouldUseMessageEvent && (
122116
<div className="mx_PinnedMessageBanner_redactedMessage">
@@ -135,6 +129,84 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
135129
);
136130
}
137131

132+
/**
133+
* The props for the {@link EventPreview} component.
134+
*/
135+
interface EventPreviewProps {
136+
/**
137+
* The pinned event to display the preview for
138+
*/
139+
pinnedEvent: MatrixEvent;
140+
}
141+
142+
/**
143+
* A component that displays a preview for the pinned event.
144+
*/
145+
function EventPreview({ pinnedEvent }: EventPreviewProps): JSX.Element | null {
146+
const preview = useEventPreview(pinnedEvent);
147+
if (!preview) return null;
148+
149+
const prefix = getPreviewPrefix(pinnedEvent.getType(), pinnedEvent.getContent().msgtype as MsgType);
150+
if (!prefix)
151+
return (
152+
<span className="mx_PinnedMessageBanner_message" data-testid="banner-message">
153+
{preview}
154+
</span>
155+
);
156+
157+
return (
158+
<span className="mx_PinnedMessageBanner_message" data-testid="banner-message">
159+
{_t(
160+
"room|pinned_message_banner|preview",
161+
{
162+
prefix,
163+
preview,
164+
},
165+
{
166+
bold: (sub) => <span className="mx_PinnedMessageBanner_prefix">{sub}</span>,
167+
},
168+
)}
169+
</span>
170+
);
171+
}
172+
173+
/**
174+
* Hooks to generate a preview for the pinned event.
175+
* @param pinnedEvent
176+
*/
177+
function useEventPreview(pinnedEvent: MatrixEvent | null): string | null {
178+
return useMemo(() => {
179+
if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null;
180+
return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent);
181+
}, [pinnedEvent]);
182+
}
183+
184+
/**
185+
* Get the prefix for the preview based on the type and the message type.
186+
* @param type
187+
* @param msgType
188+
*/
189+
function getPreviewPrefix(type: string, msgType: MsgType): string | null {
190+
switch (type) {
191+
case M_POLL_START.name:
192+
return _t("room|pinned_message_banner|prefix|poll");
193+
default:
194+
}
195+
196+
switch (msgType) {
197+
case MsgType.Audio:
198+
return _t("room|pinned_message_banner|prefix|audio");
199+
case MsgType.Image:
200+
return _t("room|pinned_message_banner|prefix|image");
201+
case MsgType.Video:
202+
return _t("room|pinned_message_banner|prefix|video");
203+
case MsgType.File:
204+
return _t("room|pinned_message_banner|prefix|file");
205+
default:
206+
return null;
207+
}
208+
}
209+
138210
const MAX_INDICATORS = 3;
139211

140212
/**

src/i18n/strings/en_EN.json

+8
Original file line numberDiff line numberDiff line change
@@ -2053,6 +2053,14 @@
20532053
"button_view_all": "View all",
20542054
"description": "This room has pinned messages. Click to view them.",
20552055
"go_to_message": "View the pinned message in the timeline.",
2056+
"prefix": {
2057+
"audio": "Audio",
2058+
"file": "File",
2059+
"image": "Image",
2060+
"poll": "Poll",
2061+
"video": "Video"
2062+
},
2063+
"preview": "<bold>%(prefix)s:</bold> %(preview)s",
20562064
"title": "<bold>%(index)s of %(length)s</bold> Pinned messages"
20572065
},
20582066
"read_topic": "Click to read topic",

test/components/views/rooms/PinnedMessageBanner-test.tsx

+27-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import userEvent from "@testing-library/user-event";
2222
import * as pinnedEventHooks from "../../../../src/hooks/usePinnedEvents";
2323
import { PinnedMessageBanner } from "../../../../src/components/views/rooms/PinnedMessageBanner";
2424
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
25-
import { stubClient } from "../../../test-utils";
25+
import { makePollStartEvent, stubClient } from "../../../test-utils";
2626
import dis from "../../../../src/dispatcher/dispatcher";
2727
import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore";
2828
import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
@@ -185,6 +185,32 @@ describe("<PinnedMessageBanner />", () => {
185185
});
186186
});
187187

188+
it.each([
189+
["m.file", "File"],
190+
["m.audio", "Audio"],
191+
["m.video", "Video"],
192+
["m.image", "Image"],
193+
])("should display the %s event type", (msgType, label) => {
194+
const body = `Message with ${msgType} type`;
195+
const event = makePinEvent({ content: { body, msgtype: msgType } });
196+
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event.getId()!]);
197+
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]);
198+
199+
const { asFragment } = renderBanner();
200+
expect(screen.getByTestId("banner-message")).toHaveTextContent(`${label}: ${body}`);
201+
expect(asFragment()).toMatchSnapshot();
202+
});
203+
204+
it("should display display a poll event", async () => {
205+
const event = makePollStartEvent("Alice?", userId);
206+
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event.getId()!]);
207+
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]);
208+
209+
const { asFragment } = renderBanner();
210+
expect(screen.getByTestId("banner-message")).toHaveTextContent("Poll: Alice?");
211+
expect(asFragment()).toMatchSnapshot();
212+
});
213+
188214
describe("Right button", () => {
189215
beforeEach(() => {
190216
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);

0 commit comments

Comments
 (0)