diff --git a/res/css/views/rooms/_PinnedMessageBanner.pcss b/res/css/views/rooms/_PinnedMessageBanner.pcss
index 31cf3ece8a..838b356ac4 100644
--- a/res/css/views/rooms/_PinnedMessageBanner.pcss
+++ b/res/css/views/rooms/_PinnedMessageBanner.pcss
@@ -94,6 +94,10 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+
+ .mx_PinnedMessageBanner_prefix {
+ font: var(--cpd-font-body-sm-semibold);
+ }
}
.mx_PinnedMessageBanner_redactedMessage {
diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx
index 0479e30ce9..3f6a1b14a6 100644
--- a/src/components/views/rooms/PinnedMessageBanner.tsx
+++ b/src/components/views/rooms/PinnedMessageBanner.tsx
@@ -17,7 +17,7 @@
import React, { JSX, useEffect, useMemo, useState } from "react";
import { Icon as PinIcon } from "@vector-im/compound-design-tokens/icons/pin-solid.svg";
import { Button } from "@vector-im/compound-web";
-import { Room } from "matrix-js-sdk/src/matrix";
+import { M_POLL_START, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix";
import classNames from "classnames";
import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents";
@@ -59,16 +59,10 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
const [currentEventIndex, setCurrentEventIndex] = useState(eventCount - 1);
// When the number of pinned messages changes, we want to display the last message
useEffect(() => {
- setCurrentEventIndex((currentEventIndex) => eventCount - 1);
+ setCurrentEventIndex(() => eventCount - 1);
}, [eventCount]);
const pinnedEvent = pinnedEvents[currentEventIndex];
- // Generate a preview for the pinned event
- const eventPreview = useMemo(() => {
- if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null;
- return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent);
- }, [pinnedEvent]);
-
if (!pinnedEvent) return null;
const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure();
@@ -116,7 +110,7 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
)}
)}
- {eventPreview && {eventPreview}}
+
{/* 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 */}
{shouldUseMessageEvent && (
@@ -135,6 +129,84 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
);
}
+/**
+ * The props for the {@link EventPreview} component.
+ */
+interface EventPreviewProps {
+ /**
+ * The pinned event to display the preview for
+ */
+ pinnedEvent: MatrixEvent;
+}
+
+/**
+ * A component that displays a preview for the pinned event.
+ */
+function EventPreview({ pinnedEvent }: EventPreviewProps): JSX.Element | null {
+ const preview = useEventPreview(pinnedEvent);
+ if (!preview) return null;
+
+ const prefix = getPreviewPrefix(pinnedEvent.getType(), pinnedEvent.getContent().msgtype as MsgType);
+ if (!prefix)
+ return (
+
+ {preview}
+
+ );
+
+ return (
+
+ {_t(
+ "room|pinned_message_banner|preview",
+ {
+ prefix,
+ preview,
+ },
+ {
+ bold: (sub) => {sub},
+ },
+ )}
+
+ );
+}
+
+/**
+ * Hooks to generate a preview for the pinned event.
+ * @param pinnedEvent
+ */
+function useEventPreview(pinnedEvent: MatrixEvent | null): string | null {
+ return useMemo(() => {
+ if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null;
+ return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent);
+ }, [pinnedEvent]);
+}
+
+/**
+ * Get the prefix for the preview based on the type and the message type.
+ * @param type
+ * @param msgType
+ */
+function getPreviewPrefix(type: string, msgType: MsgType): string | null {
+ switch (type) {
+ case M_POLL_START.name:
+ return _t("room|pinned_message_banner|prefix|poll");
+ default:
+ }
+
+ switch (msgType) {
+ case MsgType.Audio:
+ return _t("room|pinned_message_banner|prefix|audio");
+ case MsgType.Image:
+ return _t("room|pinned_message_banner|prefix|image");
+ case MsgType.Video:
+ return _t("room|pinned_message_banner|prefix|video");
+ case MsgType.File:
+ return _t("room|pinned_message_banner|prefix|file");
+ default:
+ return null;
+ }
+}
+
const MAX_INDICATORS = 3;
/**
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index e0b85cc4e1..b1f4fb4607 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2053,6 +2053,14 @@
"button_view_all": "View all",
"description": "This room has pinned messages. Click to view them.",
"go_to_message": "View the pinned message in the timeline.",
+ "prefix": {
+ "audio": "Audio",
+ "file": "File",
+ "image": "Image",
+ "poll": "Poll",
+ "video": "Video"
+ },
+ "preview": "%(prefix)s: %(preview)s",
"title": "%(index)s of %(length)s Pinned messages"
},
"read_topic": "Click to read topic",
diff --git a/test/components/views/rooms/PinnedMessageBanner-test.tsx b/test/components/views/rooms/PinnedMessageBanner-test.tsx
index 0febf21a91..0231d18ffc 100644
--- a/test/components/views/rooms/PinnedMessageBanner-test.tsx
+++ b/test/components/views/rooms/PinnedMessageBanner-test.tsx
@@ -22,7 +22,7 @@ import userEvent from "@testing-library/user-event";
import * as pinnedEventHooks from "../../../../src/hooks/usePinnedEvents";
import { PinnedMessageBanner } from "../../../../src/components/views/rooms/PinnedMessageBanner";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
-import { stubClient } from "../../../test-utils";
+import { makePollStartEvent, stubClient } from "../../../test-utils";
import dis from "../../../../src/dispatcher/dispatcher";
import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
@@ -185,6 +185,32 @@ describe("", () => {
});
});
+ it.each([
+ ["m.file", "File"],
+ ["m.audio", "Audio"],
+ ["m.video", "Video"],
+ ["m.image", "Image"],
+ ])("should display the %s event type", (msgType, label) => {
+ const body = `Message with ${msgType} type`;
+ const event = makePinEvent({ content: { body, msgtype: msgType } });
+ jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event.getId()!]);
+ jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]);
+
+ const { asFragment } = renderBanner();
+ expect(screen.getByTestId("banner-message")).toHaveTextContent(`${label}: ${body}`);
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it("should display display a poll event", async () => {
+ const event = makePollStartEvent("Alice?", userId);
+ jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event.getId()!]);
+ jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]);
+
+ const { asFragment } = renderBanner();
+ expect(screen.getByTestId("banner-message")).toHaveTextContent("Poll: Alice?");
+ expect(asFragment()).toMatchSnapshot();
+ });
+
describe("Right button", () => {
beforeEach(() => {
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
diff --git a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap
index 90b1efb635..bc733146ba 100644
--- a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap
+++ b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap
@@ -1,5 +1,52 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[` should display display a poll event 1`] = `
+
+
+
+
+
+`;
+
exports[` should display the last message when the pinned event array changed 1`] = `
should display the last message when the pinned
Third pinned message
@@ -69,6 +117,194 @@ exports[` should display the last message when the pinned
`;
+exports[` should display the m.audio event type 1`] = `
+
+
+
+
+
+`;
+
+exports[` should display the m.file event type 1`] = `
+
+
+
+
+
+`;
+
+exports[` should display the m.image event type 1`] = `
+
+
+
+
+
+`;
+
+exports[` should display the m.video event type 1`] = `
+
+
+
+
+
+`;
+
exports[` should render 2 pinned event 1`] = `
should render 2 pinned event 1`] = `
Second pinned message
@@ -185,6 +422,7 @@ exports[` should render 4 pinned event 1`] = `
Fourth pinned message
@@ -233,6 +471,7 @@ exports[` should render a single pinned event 1`] = `
/>
First pinned message