@@ -14,299 +14,95 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- import React from "react" ;
17+ import React , { useState } from "react" ;
1818import classNames from "classnames" ;
1919import { 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" ;
2420
25- import dis from "../../../dispatcher/dispatcher" ;
2621import { MatrixClientPeg } from "../../../MatrixClientPeg" ;
27- import { getPrimaryPermalinkEntity , parsePermalink } from "../../../utils/permalinks/Permalinks" ;
2822import 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" ;
3525
3626export enum PillType {
3727 UserMention = "TYPE_USER_MENTION" ,
3828 RoomMention = "TYPE_ROOM_MENTION" ,
3929 AtRoomMention = "TYPE_AT_ROOM_MENTION" , // '@room' mention
4030}
4131
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 {
4341 // The Type of this Pill. If url is given, this is auto-detected.
4442 type ?: PillType ;
4543 // The URL to pillify (no validation is done)
4644 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. */
4846 inMessage ?: boolean ;
4947 // The room in which this pill is being rendered
5048 room ?: Room ;
5149 // Whether to include an avatar in the pill
5250 shouldShowPillAvatar ?: boolean ;
5351}
5452
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+ } ) ;
11460
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 ;
15563 }
15664
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+ } ) ;
17272
173- private onMouseOver = ( ) : void => {
174- this . setState ( {
175- hover : true ,
176- } ) ;
73+ const onMouseOver = ( ) : void => {
74+ setHover ( true ) ;
17775 } ;
17876
179- private onMouseLeave = ( ) : void => {
180- this . setState ( {
181- hover : false ,
182- } ) ;
77+ const onMouseLeave = ( ) : void => {
78+ setHover ( false ) ;
18379 } ;
18480
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