@@ -14,299 +14,95 @@ See the License for the specific language governing permissions and
14
14
limitations under the License.
15
15
*/
16
16
17
- import React from "react" ;
17
+ import React , { useState } from "react" ;
18
18
import classNames from "classnames" ;
19
19
import { Room } from "matrix-js-sdk/src/models/room" ;
20
- import { RoomMember } from "matrix-js-sdk/src/models/room-member" ;
21
- import { logger } from "matrix-js-sdk/src/logger" ;
22
- import { MatrixClient } from "matrix-js-sdk/src/client" ;
23
- import { MatrixEvent } from "matrix-js-sdk/src/models/event" ;
24
20
25
- import dis from "../../../dispatcher/dispatcher" ;
26
21
import { MatrixClientPeg } from "../../../MatrixClientPeg" ;
27
- import { getPrimaryPermalinkEntity , parsePermalink } from "../../../utils/permalinks/Permalinks" ;
28
22
import MatrixClientContext from "../../../contexts/MatrixClientContext" ;
29
- import { Action } from "../../../dispatcher/actions" ;
30
- import Tooltip , { Alignment } from "./Tooltip" ;
31
- import RoomAvatar from "../avatars/RoomAvatar" ;
32
- import MemberAvatar from "../avatars/MemberAvatar" ;
33
- import { objectHasDiff } from "../../../utils/objects" ;
34
- import { ButtonEvent } from "./AccessibleButton" ;
23
+ import Tooltip , { Alignment } from "../elements/Tooltip" ;
24
+ import { usePermalink } from "../../../hooks/usePermalink" ;
35
25
36
26
export enum PillType {
37
27
UserMention = "TYPE_USER_MENTION" ,
38
28
RoomMention = "TYPE_ROOM_MENTION" ,
39
29
AtRoomMention = "TYPE_AT_ROOM_MENTION" , // '@room' mention
40
30
}
41
31
42
- interface IProps {
32
+ export const pillRoomNotifPos = ( text : string ) : number => {
33
+ return text . indexOf ( "@room" ) ;
34
+ } ;
35
+
36
+ export const pillRoomNotifLen = ( ) : number => {
37
+ return "@room" . length ;
38
+ } ;
39
+
40
+ export interface PillProps {
43
41
// The Type of this Pill. If url is given, this is auto-detected.
44
42
type ?: PillType ;
45
43
// The URL to pillify (no validation is done)
46
44
url ?: string ;
47
- // Whether the pill is in a message
45
+ /** Whether the pill is in a message. It will act as a link then. */
48
46
inMessage ?: boolean ;
49
47
// The room in which this pill is being rendered
50
48
room ?: Room ;
51
49
// Whether to include an avatar in the pill
52
50
shouldShowPillAvatar ?: boolean ;
53
51
}
54
52
55
- interface IState {
56
- // ID/alias of the room/user
57
- resourceId : string ;
58
- // Type of pill
59
- pillType : string ;
60
- // The member related to the user pill
61
- member ?: RoomMember ;
62
- // The room related to the room pill
63
- room ?: Room ;
64
- // Is the user hovering the pill
65
- hover : boolean ;
66
- }
67
-
68
- export default class Pill extends React . Component < IProps , IState > {
69
- private unmounted = true ;
70
- private matrixClient : MatrixClient ;
71
-
72
- public static roomNotifPos ( text : string ) : number {
73
- return text . indexOf ( "@room" ) ;
74
- }
75
-
76
- public static roomNotifLen ( ) : number {
77
- return "@room" . length ;
78
- }
79
-
80
- public constructor ( props : IProps ) {
81
- super ( props ) ;
82
-
83
- this . state = {
84
- resourceId : null ,
85
- pillType : null ,
86
- member : null ,
87
- room : null ,
88
- hover : false ,
89
- } ;
90
- }
91
-
92
- private load ( ) : void {
93
- let resourceId : string ;
94
- let prefix : string ;
95
-
96
- if ( this . props . url ) {
97
- if ( this . props . inMessage ) {
98
- const parts = parsePermalink ( this . props . url ) ;
99
- resourceId = parts . primaryEntityId ; // The room/user ID
100
- prefix = parts . sigil ; // The first character of prefix
101
- } else {
102
- resourceId = getPrimaryPermalinkEntity ( this . props . url ) ;
103
- prefix = resourceId ? resourceId [ 0 ] : undefined ;
104
- }
105
- }
106
-
107
- const pillType =
108
- this . props . type ||
109
- {
110
- "@" : PillType . UserMention ,
111
- "#" : PillType . RoomMention ,
112
- "!" : PillType . RoomMention ,
113
- } [ prefix ] ;
53
+ export const Pill : React . FC < PillProps > = ( { type : propType , url, inMessage, room, shouldShowPillAvatar } ) => {
54
+ const [ hover , setHover ] = useState ( false ) ;
55
+ const { avatar, onClick, resourceId, text, type } = usePermalink ( {
56
+ room,
57
+ type : propType ,
58
+ url,
59
+ } ) ;
114
60
115
- let member : RoomMember ;
116
- let room : Room ;
117
- switch ( pillType ) {
118
- case PillType . AtRoomMention :
119
- {
120
- room = this . props . room ;
121
- }
122
- break ;
123
- case PillType . UserMention :
124
- {
125
- const localMember = this . props . room ?. getMember ( resourceId ) ;
126
- member = localMember ;
127
- if ( ! localMember ) {
128
- member = new RoomMember ( null , resourceId ) ;
129
- this . doProfileLookup ( resourceId , member ) ;
130
- }
131
- }
132
- break ;
133
- case PillType . RoomMention :
134
- {
135
- const localRoom =
136
- resourceId [ 0 ] === "#"
137
- ? MatrixClientPeg . get ( )
138
- . getRooms ( )
139
- . find ( ( r ) => {
140
- return (
141
- r . getCanonicalAlias ( ) === resourceId || r . getAltAliases ( ) . includes ( resourceId )
142
- ) ;
143
- } )
144
- : MatrixClientPeg . get ( ) . getRoom ( resourceId ) ;
145
- room = localRoom ;
146
- if ( ! localRoom ) {
147
- // TODO: This would require a new API to resolve a room alias to
148
- // a room avatar and name.
149
- // this.doRoomProfileLookup(resourceId, member);
150
- }
151
- }
152
- break ;
153
- }
154
- this . setState ( { resourceId, pillType, member, room } ) ;
61
+ if ( ! type ) {
62
+ return null ;
155
63
}
156
64
157
- public componentDidMount ( ) : void {
158
- this . unmounted = false ;
159
- this . matrixClient = MatrixClientPeg . get ( ) ;
160
- this . load ( ) ;
161
- }
162
-
163
- public componentDidUpdate ( prevProps : Readonly < IProps > ) : void {
164
- if ( objectHasDiff ( this . props , prevProps ) ) {
165
- this . load ( ) ;
166
- }
167
- }
168
-
169
- public componentWillUnmount ( ) : void {
170
- this . unmounted = true ;
171
- }
65
+ const classes = classNames ( "mx_Pill" , {
66
+ mx_AtRoomPill : type === PillType . AtRoomMention ,
67
+ mx_RoomPill : type === PillType . RoomMention ,
68
+ mx_SpacePill : type === "space" ,
69
+ mx_UserPill : type === PillType . UserMention ,
70
+ mx_UserPill_me : resourceId === MatrixClientPeg . get ( ) . getUserId ( ) ,
71
+ } ) ;
172
72
173
- private onMouseOver = ( ) : void => {
174
- this . setState ( {
175
- hover : true ,
176
- } ) ;
73
+ const onMouseOver = ( ) : void => {
74
+ setHover ( true ) ;
177
75
} ;
178
76
179
- private onMouseLeave = ( ) : void => {
180
- this . setState ( {
181
- hover : false ,
182
- } ) ;
77
+ const onMouseLeave = ( ) : void => {
78
+ setHover ( false ) ;
183
79
} ;
184
80
185
- private doProfileLookup ( userId : string , member : RoomMember ) : void {
186
- MatrixClientPeg . get ( )
187
- . getProfileInfo ( userId )
188
- . then ( ( resp ) => {
189
- if ( this . unmounted ) {
190
- return ;
191
- }
192
- member . name = resp . displayname ;
193
- member . rawDisplayName = resp . displayname ;
194
- member . events . member = {
195
- getContent : ( ) => {
196
- return { avatar_url : resp . avatar_url } ;
197
- } ,
198
- getDirectionalContent : function ( ) {
199
- return this . getContent ( ) ;
200
- } ,
201
- } as MatrixEvent ;
202
- this . setState ( { member } ) ;
203
- } )
204
- . catch ( ( err ) => {
205
- logger . error ( "Could not retrieve profile data for " + userId + ":" , err ) ;
206
- } ) ;
207
- }
208
-
209
- private onUserPillClicked = ( e : ButtonEvent ) : void => {
210
- e . preventDefault ( ) ;
211
- dis . dispatch ( {
212
- action : Action . ViewUser ,
213
- member : this . state . member ,
214
- } ) ;
215
- } ;
216
-
217
- public render ( ) : React . ReactNode {
218
- const resource = this . state . resourceId ;
219
-
220
- let avatar = null ;
221
- let linkText = resource ;
222
- let pillClass ;
223
- let userId ;
224
- let href = this . props . url ;
225
- let onClick ;
226
- switch ( this . state . pillType ) {
227
- case PillType . AtRoomMention :
228
- {
229
- const room = this . props . room ;
230
- if ( room ) {
231
- linkText = "@room" ;
232
- if ( this . props . shouldShowPillAvatar ) {
233
- avatar = < RoomAvatar room = { room } width = { 16 } height = { 16 } aria-hidden = "true" /> ;
234
- }
235
- pillClass = "mx_AtRoomPill" ;
236
- }
237
- }
238
- break ;
239
- case PillType . UserMention :
240
- {
241
- // If this user is not a member of this room, default to the empty member
242
- const member = this . state . member ;
243
- if ( member ) {
244
- userId = member . userId ;
245
- member . rawDisplayName = member . rawDisplayName || "" ;
246
- linkText = member . rawDisplayName ;
247
- if ( this . props . shouldShowPillAvatar ) {
248
- avatar = (
249
- < MemberAvatar member = { member } width = { 16 } height = { 16 } aria-hidden = "true" hideTitle />
250
- ) ;
251
- }
252
- pillClass = "mx_UserPill" ;
253
- href = null ;
254
- onClick = this . onUserPillClicked ;
255
- }
256
- }
257
- break ;
258
- case PillType . RoomMention :
259
- {
260
- const room = this . state . room ;
261
- if ( room ) {
262
- linkText = room . name || resource ;
263
- if ( this . props . shouldShowPillAvatar ) {
264
- avatar = < RoomAvatar room = { room } width = { 16 } height = { 16 } aria-hidden = "true" /> ;
265
- }
266
- }
267
- pillClass = room ?. isSpaceRoom ( ) ? "mx_SpacePill" : "mx_RoomPill" ;
268
- }
269
- break ;
270
- }
271
-
272
- const classes = classNames ( "mx_Pill" , pillClass , {
273
- mx_UserPill_me : userId === MatrixClientPeg . get ( ) . getUserId ( ) ,
274
- } ) ;
275
-
276
- if ( this . state . pillType ) {
277
- let tip ;
278
- if ( this . state . hover && resource ) {
279
- tip = < Tooltip label = { resource } alignment = { Alignment . Right } /> ;
280
- }
281
-
282
- return (
283
- < bdi >
284
- < MatrixClientContext . Provider value = { this . matrixClient } >
285
- { this . props . inMessage ? (
286
- < a
287
- className = { classes }
288
- href = { href }
289
- onClick = { onClick }
290
- onMouseOver = { this . onMouseOver }
291
- onMouseLeave = { this . onMouseLeave }
292
- >
293
- { avatar }
294
- < span className = "mx_Pill_linkText" > { linkText } </ span >
295
- { tip }
296
- </ a >
297
- ) : (
298
- < span className = { classes } onMouseOver = { this . onMouseOver } onMouseLeave = { this . onMouseLeave } >
299
- { avatar }
300
- < span className = "mx_Pill_linkText" > { linkText } </ span >
301
- { tip }
302
- </ span >
303
- ) }
304
- </ MatrixClientContext . Provider >
305
- </ bdi >
306
- ) ;
307
- } else {
308
- // Deliberately render nothing if the URL isn't recognised
309
- return null ;
310
- }
311
- }
312
- }
81
+ const tip = hover && resourceId ? < Tooltip label = { resourceId } alignment = { Alignment . Right } /> : null ;
82
+
83
+ return (
84
+ < bdi >
85
+ < MatrixClientContext . Provider value = { MatrixClientPeg . get ( ) } >
86
+ { inMessage && url ? (
87
+ < a
88
+ className = { classes }
89
+ href = { url }
90
+ onClick = { onClick }
91
+ onMouseOver = { onMouseOver }
92
+ onMouseLeave = { onMouseLeave }
93
+ >
94
+ { shouldShowPillAvatar && avatar }
95
+ < span className = "mx_Pill_linkText" > { text } </ span >
96
+ { tip }
97
+ </ a >
98
+ ) : (
99
+ < span className = { classes } onMouseOver = { onMouseOver } onMouseLeave = { onMouseLeave } >
100
+ { shouldShowPillAvatar && avatar }
101
+ < span className = "mx_Pill_linkText" > { text } </ span >
102
+ { tip }
103
+ </ span >
104
+ ) }
105
+ </ MatrixClientContext . Provider >
106
+ </ bdi >
107
+ ) ;
108
+ } ;
0 commit comments