mirror of
https://github.com/element-hq/element-web
synced 2024-11-21 16:55:34 +03:00
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
This commit is contained in:
parent
9d8c5b6a1c
commit
60fe70b3cc
5 changed files with 359 additions and 10 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
{eventPreview && <span className="mx_PinnedMessageBanner_message">{eventPreview}</span>}
|
||||
<EventPreview pinnedEvent={pinnedEvent} />
|
||||
{/* 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 && (
|
||||
<div className="mx_PinnedMessageBanner_redactedMessage">
|
||||
|
@ -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 (
|
||||
<span className="mx_PinnedMessageBanner_message" data-testid="banner-message">
|
||||
{preview}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<span className="mx_PinnedMessageBanner_message" data-testid="banner-message">
|
||||
{_t(
|
||||
"room|pinned_message_banner|preview",
|
||||
{
|
||||
prefix,
|
||||
preview,
|
||||
},
|
||||
{
|
||||
bold: (sub) => <span className="mx_PinnedMessageBanner_prefix">{sub}</span>,
|
||||
},
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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": "<bold>%(prefix)s:</bold> %(preview)s",
|
||||
"title": "<bold>%(index)s of %(length)s</bold> Pinned messages"
|
||||
},
|
||||
"read_topic": "Click to read topic",
|
||||
|
|
|
@ -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("<PinnedMessageBanner />", () => {
|
|||
});
|
||||
});
|
||||
|
||||
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()!]);
|
||||
|
|
|
@ -1,5 +1,52 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<PinnedMessageBanner /> should display display a poll event 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
aria-label="This room has pinned messages. Click to view them."
|
||||
class="mx_PinnedMessageBanner"
|
||||
data-single-message="true"
|
||||
data-testid="pinned-message-banner"
|
||||
>
|
||||
<button
|
||||
aria-label="View the pinned message in the timeline."
|
||||
class="mx_PinnedMessageBanner_main"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_content"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicators"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicator mx_PinnedMessageBanner_Indicator--active"
|
||||
data-testid="banner-indicator"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_PinIcon"
|
||||
width="20"
|
||||
/>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_message"
|
||||
data-testid="banner-message"
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_prefix"
|
||||
>
|
||||
Poll:
|
||||
</span>
|
||||
Alice?
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<PinnedMessageBanner /> should display the last message when the pinned event array changed 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
|
@ -51,6 +98,7 @@ exports[`<PinnedMessageBanner /> should display the last message when the pinned
|
|||
</div>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_message"
|
||||
data-testid="banner-message"
|
||||
>
|
||||
Third pinned message
|
||||
</span>
|
||||
|
@ -69,6 +117,194 @@ exports[`<PinnedMessageBanner /> should display the last message when the pinned
|
|||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<PinnedMessageBanner /> should display the m.audio event type 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
aria-label="This room has pinned messages. Click to view them."
|
||||
class="mx_PinnedMessageBanner"
|
||||
data-single-message="true"
|
||||
data-testid="pinned-message-banner"
|
||||
>
|
||||
<button
|
||||
aria-label="View the pinned message in the timeline."
|
||||
class="mx_PinnedMessageBanner_main"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_content"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicators"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicator mx_PinnedMessageBanner_Indicator--active"
|
||||
data-testid="banner-indicator"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_PinIcon"
|
||||
width="20"
|
||||
/>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_message"
|
||||
data-testid="banner-message"
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_prefix"
|
||||
>
|
||||
Audio:
|
||||
</span>
|
||||
Message with m.audio type
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<PinnedMessageBanner /> should display the m.file event type 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
aria-label="This room has pinned messages. Click to view them."
|
||||
class="mx_PinnedMessageBanner"
|
||||
data-single-message="true"
|
||||
data-testid="pinned-message-banner"
|
||||
>
|
||||
<button
|
||||
aria-label="View the pinned message in the timeline."
|
||||
class="mx_PinnedMessageBanner_main"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_content"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicators"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicator mx_PinnedMessageBanner_Indicator--active"
|
||||
data-testid="banner-indicator"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_PinIcon"
|
||||
width="20"
|
||||
/>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_message"
|
||||
data-testid="banner-message"
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_prefix"
|
||||
>
|
||||
File:
|
||||
</span>
|
||||
Message with m.file type
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<PinnedMessageBanner /> should display the m.image event type 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
aria-label="This room has pinned messages. Click to view them."
|
||||
class="mx_PinnedMessageBanner"
|
||||
data-single-message="true"
|
||||
data-testid="pinned-message-banner"
|
||||
>
|
||||
<button
|
||||
aria-label="View the pinned message in the timeline."
|
||||
class="mx_PinnedMessageBanner_main"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_content"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicators"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicator mx_PinnedMessageBanner_Indicator--active"
|
||||
data-testid="banner-indicator"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_PinIcon"
|
||||
width="20"
|
||||
/>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_message"
|
||||
data-testid="banner-message"
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_prefix"
|
||||
>
|
||||
Image:
|
||||
</span>
|
||||
Message with m.image type
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<PinnedMessageBanner /> should display the m.video event type 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
aria-label="This room has pinned messages. Click to view them."
|
||||
class="mx_PinnedMessageBanner"
|
||||
data-single-message="true"
|
||||
data-testid="pinned-message-banner"
|
||||
>
|
||||
<button
|
||||
aria-label="View the pinned message in the timeline."
|
||||
class="mx_PinnedMessageBanner_main"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_content"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicators"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_Indicator mx_PinnedMessageBanner_Indicator--active"
|
||||
data-testid="banner-indicator"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedMessageBanner_PinIcon"
|
||||
width="20"
|
||||
/>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_message"
|
||||
data-testid="banner-message"
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_prefix"
|
||||
>
|
||||
Video:
|
||||
</span>
|
||||
Message with m.video type
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<PinnedMessageBanner /> should render 2 pinned event 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
|
@ -116,6 +352,7 @@ exports[`<PinnedMessageBanner /> should render 2 pinned event 1`] = `
|
|||
</div>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_message"
|
||||
data-testid="banner-message"
|
||||
>
|
||||
Second pinned message
|
||||
</span>
|
||||
|
@ -185,6 +422,7 @@ exports[`<PinnedMessageBanner /> should render 4 pinned event 1`] = `
|
|||
</div>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_message"
|
||||
data-testid="banner-message"
|
||||
>
|
||||
Fourth pinned message
|
||||
</span>
|
||||
|
@ -233,6 +471,7 @@ exports[`<PinnedMessageBanner /> should render a single pinned event 1`] = `
|
|||
/>
|
||||
<span
|
||||
class="mx_PinnedMessageBanner_message"
|
||||
data-testid="banner-message"
|
||||
>
|
||||
First pinned message
|
||||
</span>
|
||||
|
|
Loading…
Reference in a new issue