Skip to content

Commit a7e907e

Browse files
authored
Add thread information in pinned message list (#12902)
1 parent 3d80eff commit a7e907e

File tree

5 files changed

+171
-4
lines changed

5 files changed

+171
-4
lines changed

res/css/views/rooms/_PinnedEventTile.pcss

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,28 @@ limitations under the License.
3737
white-space: nowrap;
3838
}
3939
}
40+
41+
.mx_PinnedEventTile_thread {
42+
display: flex;
43+
gap: var(--cpd-space-2x);
44+
font: var(--cpd-font-body-sm-regular);
45+
46+
svg {
47+
width: 20px;
48+
fill: var(--cpd-color-icon-tertiary);
49+
}
50+
51+
span {
52+
display: flex;
53+
color: var(--cpd-color-text-secondary);
54+
}
55+
56+
button {
57+
background: transparent;
58+
border: none;
59+
cursor: pointer;
60+
text-decoration: underline;
61+
}
62+
}
4063
}
4164
}

src/components/views/rooms/PinnedEventTile.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Icon as UnpinIcon } from "@vector-im/compound-design-tokens/icons/unpin
2323
import { Icon as ForwardIcon } from "@vector-im/compound-design-tokens/icons/forward.svg";
2424
import { Icon as TriggerIcon } from "@vector-im/compound-design-tokens/icons/overflow-horizontal.svg";
2525
import { Icon as DeleteIcon } from "@vector-im/compound-design-tokens/icons/delete.svg";
26+
import { Icon as ThreadIcon } from "@vector-im/compound-design-tokens/icons/threads.svg";
2627
import classNames from "classnames";
2728

2829
import dis from "../../../dispatcher/dispatcher";
@@ -39,6 +40,7 @@ import { isContentActionable } from "../../../utils/EventUtils";
3940
import { getForwardableEvent } from "../../../events";
4041
import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload";
4142
import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog";
43+
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
4244

4345
const AVATAR_SIZE = "32px";
4446

@@ -69,6 +71,9 @@ export function PinnedEventTile({ event, room, permalinkCreator }: PinnedEventTi
6971
throw new Error("Pinned event unexpectedly has no sender");
7072
}
7173

74+
const isInThread = Boolean(event.threadRootId);
75+
const displayThreadInfo = !event.isThreadRoot && isInThread;
76+
7277
return (
7378
<div className="mx_PinnedEventTile" role="listitem">
7479
<div>
@@ -97,6 +102,36 @@ export function PinnedEventTile({ event, room, permalinkCreator }: PinnedEventTi
97102
permalinkCreator={permalinkCreator}
98103
replacingEventId={event.replacingEventId()}
99104
/>
105+
{displayThreadInfo && (
106+
<div className="mx_PinnedEventTile_thread">
107+
<ThreadIcon />
108+
{_t(
109+
"right_panel|pinned_messages|reply_thread",
110+
{},
111+
{
112+
link: (sub) => (
113+
<button
114+
type="button"
115+
onClick={() => {
116+
if (!event.threadRootId) return;
117+
118+
const rootEvent = room.findEventById(event.threadRootId);
119+
if (!rootEvent) return;
120+
121+
dis.dispatch<ShowThreadPayload>({
122+
action: Action.ShowThread,
123+
rootEvent: rootEvent,
124+
push: true,
125+
});
126+
}}
127+
>
128+
{sub}
129+
</button>
130+
),
131+
},
132+
)}
133+
</div>
134+
)}
100135
</div>
101136
</div>
102137
);

src/i18n/strings/en_EN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,6 +1850,7 @@
18501850
"other": "You can only pin up to %(count)s widgets"
18511851
},
18521852
"menu": "Open menu",
1853+
"reply_thread": "Reply to a <link>thread message</link>",
18531854
"title": "Pinned messages",
18541855
"unpin_all": {
18551856
"button": "Unpin all messages",

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,36 @@ describe("<PinnedEventTile />", () => {
9797
expect(container).toMatchSnapshot();
9898
});
9999

100+
it("should render pinned event with thread info", async () => {
101+
const event = makePinEvent({
102+
content: {
103+
"body": "First pinned message",
104+
"msgtype": "m.text",
105+
"m.relates_to": {
106+
"event_id": "$threadRootEventId",
107+
"is_falling_back": true,
108+
"m.in_reply_to": {
109+
event_id: "$$threadRootEventId",
110+
},
111+
"rel_type": "m.thread",
112+
},
113+
},
114+
});
115+
const threadRootEvent = makePinEvent({ event_id: "$threadRootEventId" });
116+
jest.spyOn(room, "findEventById").mockReturnValue(threadRootEvent);
117+
118+
const { container } = renderComponent(event);
119+
expect(container).toMatchSnapshot();
120+
121+
await userEvent.click(screen.getByRole("button", { name: "thread message" }));
122+
// Check that the thread is opened
123+
expect(dis.dispatch).toHaveBeenCalledWith({
124+
action: Action.ShowThread,
125+
rootEvent: threadRootEvent,
126+
push: true,
127+
});
128+
});
129+
100130
it("should render the menu without unpin and delete", async () => {
101131
jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "mayClientSendStateEvent").mockReturnValue(
102132
false,

test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,88 @@ exports[`<PinnedEventTile /> should render pinned event 1`] = `
6565
</div>
6666
`;
6767

68+
exports[`<PinnedEventTile /> should render pinned event with thread info 1`] = `
69+
<div>
70+
<div
71+
class="mx_PinnedEventTile"
72+
role="listitem"
73+
>
74+
<div>
75+
<span
76+
class="_avatar_mcap2_17 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_mcap2_61"
77+
data-color="2"
78+
data-testid="avatar-img"
79+
data-type="round"
80+
role="presentation"
81+
style="--cpd-avatar-size: 32px;"
82+
>
83+
a
84+
</span>
85+
</div>
86+
<div
87+
class="mx_PinnedEventTile_wrapper"
88+
>
89+
<div
90+
class="mx_PinnedEventTile_top"
91+
>
92+
<span
93+
class="_typography_yh5dq_162 _font-body-md-semibold_yh5dq_64 mx_PinnedEventTile_sender mx_Username_color2"
94+
>
95+
@alice:server.org
96+
</span>
97+
<button
98+
aria-disabled="false"
99+
aria-expanded="false"
100+
aria-haspopup="menu"
101+
aria-label="Open menu"
102+
class="_icon-button_bh2qc_17"
103+
data-state="closed"
104+
id="radix-2"
105+
role="button"
106+
style="--cpd-icon-button-size: 24px;"
107+
tabindex="0"
108+
type="button"
109+
>
110+
<div
111+
class="_indicator-icon_133tf_26"
112+
style="--cpd-icon-button-size: 100%;"
113+
>
114+
<div />
115+
</div>
116+
</button>
117+
</div>
118+
<div
119+
class="mx_MTextBody mx_EventTile_content"
120+
>
121+
<div
122+
class="mx_EventTile_body translate"
123+
dir="auto"
124+
>
125+
First pinned message
126+
</div>
127+
</div>
128+
<div
129+
class="mx_PinnedEventTile_thread"
130+
>
131+
<div />
132+
<span>
133+
Reply to a
134+
<button
135+
type="button"
136+
>
137+
thread message
138+
</button>
139+
</span>
140+
</div>
141+
</div>
142+
</div>
143+
</div>
144+
`;
145+
68146
exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
69147
<div
70148
aria-label="Open menu"
71-
aria-labelledby="radix-6"
149+
aria-labelledby="radix-8"
72150
aria-orientation="vertical"
73151
class="_menu_1x5h1_17"
74152
data-align="start"
@@ -77,7 +155,7 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
77155
data-side="right"
78156
data-state="open"
79157
dir="ltr"
80-
id="radix-7"
158+
id="radix-9"
81159
role="menu"
82160
style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
83161
tabindex="-1"
@@ -226,7 +304,7 @@ exports[`<PinnedEventTile /> should render the menu with all the options 1`] = `
226304
exports[`<PinnedEventTile /> should render the menu without unpin and delete 1`] = `
227305
<div
228306
aria-label="Open menu"
229-
aria-labelledby="radix-2"
307+
aria-labelledby="radix-4"
230308
aria-orientation="vertical"
231309
class="_menu_1x5h1_17"
232310
data-align="start"
@@ -235,7 +313,7 @@ exports[`<PinnedEventTile /> should render the menu without unpin and delete 1`]
235313
data-side="right"
236314
data-state="open"
237315
dir="ltr"
238-
id="radix-3"
316+
id="radix-5"
239317
role="menu"
240318
style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;"
241319
tabindex="-1"

0 commit comments

Comments
 (0)