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

Commit 3eb6a55

Browse files
authored
Tweak pill UI (#10417)
1 parent 4c2b18c commit 3eb6a55

File tree

8 files changed

+177
-54
lines changed

8 files changed

+177
-54
lines changed

res/css/views/elements/_Pill.pcss

-6
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,6 @@ limitations under the License.
5959
min-width: $font-16px; /* ensure the avatar is not compressed */
6060
}
6161

62-
&.mx_EventPill .mx_BaseAvatar {
63-
/* Event pill avatars are inside the text. */
64-
margin-inline-start: 0.2em;
65-
margin-inline-end: 0.2em;
66-
}
67-
6862
.mx_Pill_text {
6963
min-width: 0;
7064
overflow: hidden;

src/components/views/elements/Pill.tsx

+25-18
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export const pillRoomNotifLen = (): number => {
4545
return "@room".length;
4646
};
4747

48+
const linkIcon = <LinkIcon className="mx_Pill_LinkIcon mx_BaseAvatar mx_BaseAvatar_image" />;
49+
4850
const PillRoomAvatar: React.FC<{
4951
shouldShowPillAvatar: boolean;
5052
room: Room | null;
@@ -56,7 +58,7 @@ const PillRoomAvatar: React.FC<{
5658
if (room) {
5759
return <RoomAvatar room={room} width={16} height={16} aria-hidden="true" />;
5860
}
59-
return <LinkIcon className="mx_Pill_LinkIcon mx_BaseAvatar mx_BaseAvatar_image" />;
61+
return linkIcon;
6062
};
6163

6264
const PillMemberAvatar: React.FC<{
@@ -88,7 +90,7 @@ export interface PillProps {
8890

8991
export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room, shouldShowPillAvatar = true }) => {
9092
const [hover, setHover] = useState(false);
91-
const { member, onClick, resourceId, targetRoom, text, type } = usePermalink({
93+
const { event, member, onClick, resourceId, targetRoom, text, type } = usePermalink({
9294
room,
9395
type: propType,
9496
url,
@@ -116,35 +118,38 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
116118
};
117119

118120
const tip = hover && resourceId ? <Tooltip label={resourceId} alignment={Alignment.Right} /> : null;
119-
let content: (ReactElement | string)[] = [];
120-
const textElement = <span className="mx_Pill_text">{text}</span>;
121+
let avatar: ReactElement | null = null;
122+
let pillText: string | null = text;
121123

122124
switch (type) {
123125
case PillType.EventInOtherRoom:
124126
{
125-
const avatar = <PillRoomAvatar shouldShowPillAvatar={shouldShowPillAvatar} room={targetRoom} />;
126-
content = [_t("Message in"), avatar || " ", textElement];
127+
avatar = <PillRoomAvatar shouldShowPillAvatar={shouldShowPillAvatar} room={targetRoom} />;
128+
pillText = _t("Message in %(room)s", {
129+
room: text,
130+
});
127131
}
128132
break;
129133
case PillType.EventInSameRoom:
130134
{
131-
const avatar = <PillMemberAvatar shouldShowPillAvatar={shouldShowPillAvatar} member={member} />;
132-
content = [_t("Message from"), avatar || " ", textElement];
135+
if (event) {
136+
avatar = <PillMemberAvatar shouldShowPillAvatar={shouldShowPillAvatar} member={member} />;
137+
pillText = _t("Message from %(user)s", {
138+
user: text,
139+
});
140+
} else {
141+
avatar = linkIcon;
142+
pillText = _t("Message");
143+
}
133144
}
134145
break;
135146
case PillType.AtRoomMention:
136147
case PillType.RoomMention:
137148
case "space":
138-
{
139-
const avatar = <PillRoomAvatar shouldShowPillAvatar={shouldShowPillAvatar} room={targetRoom} />;
140-
content = [avatar, textElement];
141-
}
149+
avatar = <PillRoomAvatar shouldShowPillAvatar={shouldShowPillAvatar} room={targetRoom} />;
142150
break;
143151
case PillType.UserMention:
144-
{
145-
const avatar = <PillMemberAvatar shouldShowPillAvatar={shouldShowPillAvatar} member={member} />;
146-
content = [avatar, textElement];
147-
}
152+
avatar = <PillMemberAvatar shouldShowPillAvatar={shouldShowPillAvatar} member={member} />;
148153
break;
149154
default:
150155
return null;
@@ -161,12 +166,14 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
161166
onMouseOver={onMouseOver}
162167
onMouseLeave={onMouseLeave}
163168
>
164-
{content}
169+
{avatar}
170+
<span className="mx_Pill_text">{pillText}</span>
165171
{tip}
166172
</a>
167173
) : (
168174
<span className={classes} onMouseOver={onMouseOver} onMouseLeave={onMouseLeave}>
169-
{content}
175+
{avatar}
176+
<span className="mx_Pill_text">{pillText}</span>
170177
{tip}
171178
</span>
172179
)}

src/hooks/usePermalink.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
17+
import { MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
1818

1919
import { ButtonEvent } from "../components/views/elements/AccessibleButton";
2020
import { PillType } from "../components/views/elements/Pill";
@@ -70,6 +70,11 @@ interface HookResult {
7070
* null here means that the type cannot be detected. Most likely if the URL was not a permalink.
7171
*/
7272
type: PillType | "space" | null;
73+
/**
74+
* Target event of the permalink.
75+
* Null if unable to load the event.
76+
*/
77+
event: MatrixEvent | null;
7378
}
7479

7580
/**
@@ -166,6 +171,7 @@ export const usePermalink: (args: Args) => HookResult = ({
166171
}
167172

168173
return {
174+
event,
169175
member,
170176
onClick,
171177
resourceId,

src/i18n/strings/en_EN.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2592,8 +2592,8 @@
25922592
"Rotate Right": "Rotate Right",
25932593
"Information": "Information",
25942594
"Language Dropdown": "Language Dropdown",
2595-
"Message in": "Message in",
2596-
"Message from": "Message from",
2595+
"Message in %(room)s": "Message in %(room)s",
2596+
"Message from %(user)s": "Message from %(user)s",
25972597
"Create poll": "Create poll",
25982598
"Create Poll": "Create Poll",
25992599
"Edit poll": "Edit poll",

test/components/views/elements/__snapshots__/Pill-test.tsx.snap

+2-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ exports[`<Pill> should render the expected pill for a message in another room 1`
7171
class="mx_Pill mx_EventPill"
7272
href="https://matrix.to/#/!room1:example.com/$123-456"
7373
>
74-
Message in
7574
<span
7675
aria-hidden="true"
7776
class="mx_BaseAvatar"
@@ -96,7 +95,7 @@ exports[`<Pill> should render the expected pill for a message in another room 1`
9695
<span
9796
class="mx_Pill_text"
9897
>
99-
Room 1
98+
Message in Room 1
10099
</span>
101100
</a>
102101
</bdi>
@@ -112,7 +111,6 @@ exports[`<Pill> should render the expected pill for a message in the same room 1
112111
class="mx_Pill mx_EventPill"
113112
href="https://matrix.to/#/!room1:example.com/$123-456"
114113
>
115-
Message from
116114
<span
117115
aria-hidden="true"
118116
class="mx_BaseAvatar"
@@ -137,7 +135,7 @@ exports[`<Pill> should render the expected pill for a message in the same room 1
137135
<span
138136
class="mx_Pill_text"
139137
>
140-
User 1
138+
Message from User 1
141139
</span>
142140
</a>
143141
</bdi>

test/components/views/messages/TextualBody-test.tsx

+71-20
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ limitations under the License.
1616

1717
import React from "react";
1818
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
19-
import { MockedObject } from "jest-mock";
19+
import { mocked, MockedObject } from "jest-mock";
2020
import { render } from "@testing-library/react";
21+
import * as prettier from "prettier";
2122

2223
import { getMockClientWithEventEmitter, mkEvent, mkMessage, mkStubRoom } from "../../../test-utils";
2324
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
@@ -28,10 +29,18 @@ import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
2829
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
2930
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
3031

31-
const mkRoomTextMessage = (body: string): MatrixEvent => {
32+
const room1Id = "!room1:example.com";
33+
const room2Id = "!room2:example.com";
34+
const room2Name = "Room 2";
35+
36+
interface MkRoomTextMessageOpts {
37+
roomId?: string;
38+
}
39+
40+
const mkRoomTextMessage = (body: string, mkRoomTextMessageOpts?: MkRoomTextMessageOpts): MatrixEvent => {
3241
return mkMessage({
3342
msg: body,
34-
room: "room_id",
43+
room: mkRoomTextMessageOpts?.roomId ?? room1Id,
3544
user: "sender",
3645
event: true,
3746
});
@@ -42,7 +51,7 @@ const mkFormattedMessage = (body: string, formattedBody: string): MatrixEvent =>
4251
msg: body,
4352
formattedMsg: formattedBody,
4453
format: "org.matrix.custom.html",
45-
room: "room_id",
54+
room: room1Id,
4655
user: "sender",
4756
event: true,
4857
});
@@ -53,12 +62,29 @@ describe("<TextualBody />", () => {
5362
jest.spyOn(MatrixClientPeg, "get").mockRestore();
5463
});
5564

56-
const defaultRoom = mkStubRoom("room_id", "test room", undefined);
65+
const defaultRoom = mkStubRoom(room1Id, "test room", undefined);
66+
const otherRoom = mkStubRoom(room2Id, room2Name, undefined);
5767
let defaultMatrixClient: MockedObject<MatrixClient>;
68+
69+
const defaultEvent = mkEvent({
70+
type: "m.room.message",
71+
room: room1Id,
72+
user: "sender",
73+
content: {
74+
body: "winks",
75+
msgtype: "m.emote",
76+
},
77+
event: true,
78+
});
79+
5880
beforeEach(() => {
5981
defaultMatrixClient = getMockClientWithEventEmitter({
60-
getRoom: () => defaultRoom,
61-
getRooms: () => [defaultRoom],
82+
getRoom: (roomId: string | undefined) => {
83+
if (roomId === room1Id) return defaultRoom;
84+
if (roomId === room2Id) return otherRoom;
85+
return null;
86+
},
87+
getRooms: () => [defaultRoom, otherRoom],
6288
getAccountData: (): MatrixEvent | undefined => undefined,
6389
isGuest: () => false,
6490
mxcUrlToHttp: (s: string) => s,
@@ -67,18 +93,13 @@ describe("<TextualBody />", () => {
6793
throw new Error("MockClient event not found");
6894
},
6995
});
70-
});
7196

72-
const defaultEvent = mkEvent({
73-
type: "m.room.message",
74-
room: "room_id",
75-
user: "sender",
76-
content: {
77-
body: "winks",
78-
msgtype: "m.emote",
79-
},
80-
event: true,
97+
mocked(defaultRoom).findEventById.mockImplementation((eventId: string) => {
98+
if (eventId === defaultEvent.getId()) return defaultEvent;
99+
return undefined;
100+
});
81101
});
102+
82103
const defaultProps = {
83104
mxEvent: defaultEvent,
84105
highlights: [] as string[],
@@ -88,6 +109,7 @@ describe("<TextualBody />", () => {
88109
permalinkCreator: new RoomPermalinkCreator(defaultRoom),
89110
mediaEventHelper: {} as MediaEventHelper,
90111
};
112+
91113
const getComponent = (props = {}, matrixClient: MatrixClient = defaultMatrixClient, renderingFn?: any) =>
92114
(renderingFn ?? render)(
93115
<MatrixClientContext.Provider value={matrixClient}>
@@ -100,7 +122,7 @@ describe("<TextualBody />", () => {
100122

101123
const ev = mkEvent({
102124
type: "m.room.message",
103-
room: "room_id",
125+
room: room1Id,
104126
user: "sender",
105127
content: {
106128
body: "winks",
@@ -120,7 +142,7 @@ describe("<TextualBody />", () => {
120142

121143
const ev = mkEvent({
122144
type: "m.room.message",
123-
room: "room_id",
145+
room: room1Id,
124146
user: "bot_sender",
125147
content: {
126148
body: "this is a notice, probably from a bot",
@@ -196,13 +218,42 @@ describe("<TextualBody />", () => {
196218
`"Visit <span><bdi><a class="mx_Pill mx_RoomPill" href="https://matrix.to/#/#room:example.com"><div class="mx_Pill_LinkIcon mx_BaseAvatar mx_BaseAvatar_image"></div><span class="mx_Pill_text">#room:example.com</span></a></bdi></span>"`,
197219
);
198220
});
221+
222+
it("should pillify a permalink to a message in the same room with the label »Message from Member«", () => {
223+
const ev = mkRoomTextMessage(`Visit https://matrix.to/#/${room1Id}/${defaultEvent.getId()}`);
224+
const { container } = getComponent({ mxEvent: ev });
225+
const content = container.querySelector(".mx_EventTile_body");
226+
expect(
227+
prettier.format(content.innerHTML.replace(defaultEvent.getId(), "%event_id%"), {
228+
parser: "html",
229+
}),
230+
).toMatchSnapshot();
231+
});
232+
233+
it("should pillify a permalink to an unknown message in the same room with the label »Message«", () => {
234+
const ev = mkRoomTextMessage(`Visit https://matrix.to/#/${room1Id}/!abc123`);
235+
const { container } = getComponent({ mxEvent: ev });
236+
const content = container.querySelector(".mx_EventTile_body");
237+
expect(content).toMatchSnapshot();
238+
});
239+
240+
it("should pillify a permalink to an event in another room with the label »Message in Room 2«", () => {
241+
const ev = mkRoomTextMessage(`Visit https://matrix.to/#/${room2Id}/${defaultEvent.getId()}`);
242+
const { container } = getComponent({ mxEvent: ev });
243+
const content = container.querySelector(".mx_EventTile_body");
244+
expect(
245+
prettier.format(content.innerHTML.replace(defaultEvent.getId(), "%event_id%"), {
246+
parser: "html",
247+
}),
248+
).toMatchSnapshot();
249+
});
199250
});
200251

201252
describe("renders formatted m.text correctly", () => {
202253
let matrixClient: MatrixClient;
203254
beforeEach(() => {
204255
matrixClient = getMockClientWithEventEmitter({
205-
getRoom: () => mkStubRoom("room_id", "room name", undefined),
256+
getRoom: () => mkStubRoom(room1Id, "room name", undefined),
206257
getAccountData: (): MatrixEvent | undefined => undefined,
207258
getUserId: () => "@me:my_server",
208259
getHomeserverUrl: () => "https://my_server/",

0 commit comments

Comments
 (0)