Always use current profile on thread events (#9524)
							parent
							
								
									5fb0f5cc3e
								
							
						
					
					
						commit
						3f3005a3ca
					
				| 
						 | 
				
			
			@ -15,10 +15,9 @@ See the License for the specific language governing permissions and
 | 
			
		|||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import React, { useContext } from 'react';
 | 
			
		||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
 | 
			
		||||
import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
 | 
			
		||||
import { logger } from "matrix-js-sdk/src/logger";
 | 
			
		||||
 | 
			
		||||
import dis from "../../../dispatcher/dispatcher";
 | 
			
		||||
import { Action } from "../../../dispatcher/actions";
 | 
			
		||||
| 
						 | 
				
			
			@ -26,8 +25,7 @@ import BaseAvatar from "./BaseAvatar";
 | 
			
		|||
import { mediaFromMxc } from "../../../customisations/Media";
 | 
			
		||||
import { CardContext } from '../right_panel/context';
 | 
			
		||||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
 | 
			
		||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
			
		||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
 | 
			
		||||
import { useRoomMemberProfile } from '../../../hooks/room/useRoomMemberProfile';
 | 
			
		||||
 | 
			
		||||
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
 | 
			
		||||
    member: RoomMember | null;
 | 
			
		||||
| 
						 | 
				
			
			@ -46,100 +44,58 @@ interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" |
 | 
			
		|||
    hideTitle?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IState {
 | 
			
		||||
    name: string;
 | 
			
		||||
    title: string;
 | 
			
		||||
    imageUrl?: string;
 | 
			
		||||
}
 | 
			
		||||
export default function MemberAvatar({
 | 
			
		||||
    width,
 | 
			
		||||
    height,
 | 
			
		||||
    resizeMethod = 'crop',
 | 
			
		||||
    viewUserOnClick,
 | 
			
		||||
    ...props
 | 
			
		||||
}: IProps) {
 | 
			
		||||
    const card = useContext(CardContext);
 | 
			
		||||
 | 
			
		||||
export default class MemberAvatar extends React.PureComponent<IProps, IState> {
 | 
			
		||||
    public static defaultProps = {
 | 
			
		||||
        width: 40,
 | 
			
		||||
        height: 40,
 | 
			
		||||
        resizeMethod: 'crop',
 | 
			
		||||
        viewUserOnClick: false,
 | 
			
		||||
    };
 | 
			
		||||
    const member = useRoomMemberProfile({
 | 
			
		||||
        userId: props.member?.userId,
 | 
			
		||||
        member: props.member,
 | 
			
		||||
        forceHistorical: props.forceHistorical,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    constructor(props: IProps) {
 | 
			
		||||
        super(props);
 | 
			
		||||
 | 
			
		||||
        this.state = MemberAvatar.getState(props);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static getDerivedStateFromProps(nextProps: IProps): IState {
 | 
			
		||||
        return MemberAvatar.getState(nextProps);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static getState(props: IProps): IState {
 | 
			
		||||
        let member = props.member;
 | 
			
		||||
        if (member && !props.forceHistorical && SettingsStore.getValue("useOnlyCurrentProfiles")) {
 | 
			
		||||
            const room = MatrixClientPeg.get().getRoom(member.roomId);
 | 
			
		||||
            if (room) {
 | 
			
		||||
                member = room.getMember(member.userId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (member?.name) {
 | 
			
		||||
            let imageUrl = null;
 | 
			
		||||
            const userTitle = UserIdentifierCustomisations.getDisplayUserIdentifier(
 | 
			
		||||
                member.userId, { roomId: member?.roomId },
 | 
			
		||||
    const name = member?.name ?? props.fallbackUserId;
 | 
			
		||||
    let title: string | undefined = props.title;
 | 
			
		||||
    let imageUrl: string | undefined;
 | 
			
		||||
    if (member?.name) {
 | 
			
		||||
        if (member.getMxcAvatarUrl()) {
 | 
			
		||||
            imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "").getThumbnailOfSourceHttp(
 | 
			
		||||
                width,
 | 
			
		||||
                height,
 | 
			
		||||
                resizeMethod,
 | 
			
		||||
            );
 | 
			
		||||
            if (member.getMxcAvatarUrl()) {
 | 
			
		||||
                imageUrl = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(
 | 
			
		||||
                    props.width,
 | 
			
		||||
                    props.height,
 | 
			
		||||
                    props.resizeMethod,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            return {
 | 
			
		||||
                name: member.name,
 | 
			
		||||
                title: props.title || userTitle,
 | 
			
		||||
                imageUrl: imageUrl,
 | 
			
		||||
            };
 | 
			
		||||
        } else if (props.fallbackUserId) {
 | 
			
		||||
            return {
 | 
			
		||||
                name: props.fallbackUserId,
 | 
			
		||||
                title: props.fallbackUserId,
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            logger.error("MemberAvatar called somehow with null member or fallbackUserId");
 | 
			
		||||
            return {} as IState; // prevent an explosion
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!title) {
 | 
			
		||||
            title = UserIdentifierCustomisations.getDisplayUserIdentifier(
 | 
			
		||||
                member?.userId ?? "", { roomId: member?.roomId ?? "" },
 | 
			
		||||
            ) ?? props.fallbackUserId;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    const userId = member?.userId ?? props.fallbackUserId;
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        let {
 | 
			
		||||
            member,
 | 
			
		||||
            fallbackUserId,
 | 
			
		||||
            onClick,
 | 
			
		||||
            viewUserOnClick,
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
            forceHistorical,
 | 
			
		||||
            hideTitle,
 | 
			
		||||
            ...otherProps
 | 
			
		||||
        } = this.props;
 | 
			
		||||
        const userId = member ? member.userId : fallbackUserId;
 | 
			
		||||
 | 
			
		||||
        if (viewUserOnClick) {
 | 
			
		||||
            onClick = () => {
 | 
			
		||||
    return (
 | 
			
		||||
        <BaseAvatar
 | 
			
		||||
            {...props}
 | 
			
		||||
            width={width}
 | 
			
		||||
            height={height}
 | 
			
		||||
            resizeMethod={resizeMethod}
 | 
			
		||||
            name={name ?? ""}
 | 
			
		||||
            title={props.hideTitle ? undefined : title}
 | 
			
		||||
            idName={userId}
 | 
			
		||||
            url={imageUrl}
 | 
			
		||||
            onClick={viewUserOnClick ? () => {
 | 
			
		||||
                dis.dispatch({
 | 
			
		||||
                    action: Action.ViewUser,
 | 
			
		||||
                    member: this.props.member,
 | 
			
		||||
                    push: this.context.isCard,
 | 
			
		||||
                    member: props.member,
 | 
			
		||||
                    push: card.isCard,
 | 
			
		||||
                });
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <BaseAvatar
 | 
			
		||||
                {...otherProps}
 | 
			
		||||
                name={this.state.name}
 | 
			
		||||
                title={hideTitle ? undefined : this.state.title}
 | 
			
		||||
                idName={userId}
 | 
			
		||||
                url={this.state.imageUrl}
 | 
			
		||||
                onClick={onClick}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
            } : props.onClick}
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MemberAvatar.contextType = CardContext;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,7 @@ import { getUserNameColorClass } from '../../../utils/FormattingUtils';
 | 
			
		|||
import UserIdentifier from "../../../customisations/UserIdentifier";
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
    member?: RoomMember;
 | 
			
		||||
    member?: RoomMember | null;
 | 
			
		||||
    fallbackName: string;
 | 
			
		||||
    onClick?(): void;
 | 
			
		||||
    colored?: boolean;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,51 +18,27 @@ import React from 'react';
 | 
			
		|||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
 | 
			
		||||
import { MsgType } from "matrix-js-sdk/src/@types/event";
 | 
			
		||||
 | 
			
		||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
 | 
			
		||||
import DisambiguatedProfile from "./DisambiguatedProfile";
 | 
			
		||||
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';
 | 
			
		||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
			
		||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
 | 
			
		||||
import { useRoomMemberProfile } from '../../../hooks/room/useRoomMemberProfile';
 | 
			
		||||
 | 
			
		||||
interface IProps {
 | 
			
		||||
    mxEvent: MatrixEvent;
 | 
			
		||||
    onClick?(): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class SenderProfile extends React.PureComponent<IProps> {
 | 
			
		||||
    public static contextType = MatrixClientContext;
 | 
			
		||||
    public context!: React.ContextType<typeof MatrixClientContext>;
 | 
			
		||||
export default function SenderProfile({ mxEvent, onClick }: IProps) {
 | 
			
		||||
    const member = useRoomMemberProfile({
 | 
			
		||||
        userId: mxEvent.getSender(),
 | 
			
		||||
        member: mxEvent.sender,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const { mxEvent, onClick } = this.props;
 | 
			
		||||
        const msgtype = mxEvent.getContent().msgtype;
 | 
			
		||||
 | 
			
		||||
        let member = mxEvent.sender;
 | 
			
		||||
        if (SettingsStore.getValue("useOnlyCurrentProfiles")) {
 | 
			
		||||
            const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
 | 
			
		||||
            if (room) {
 | 
			
		||||
                member = room.getMember(mxEvent.getSender());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return <RoomContext.Consumer>
 | 
			
		||||
            { roomContext => {
 | 
			
		||||
                if (msgtype === MsgType.Emote &&
 | 
			
		||||
                    roomContext.timelineRenderingType !== TimelineRenderingType.ThreadsList
 | 
			
		||||
                ) {
 | 
			
		||||
                    return null; // emote message must include the name so don't duplicate it
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return (
 | 
			
		||||
                    <DisambiguatedProfile
 | 
			
		||||
                        fallbackName={mxEvent.getSender() || ""}
 | 
			
		||||
                        onClick={onClick}
 | 
			
		||||
                        member={member}
 | 
			
		||||
                        colored={true}
 | 
			
		||||
                        emphasizeDisplayName={true}
 | 
			
		||||
                    />
 | 
			
		||||
                );
 | 
			
		||||
            } }
 | 
			
		||||
        </RoomContext.Consumer>;
 | 
			
		||||
    }
 | 
			
		||||
    return mxEvent.getContent().msgtype !== MsgType.Emote
 | 
			
		||||
        ? <DisambiguatedProfile
 | 
			
		||||
            fallbackName={mxEvent.getSender() ?? ""}
 | 
			
		||||
            onClick={onClick}
 | 
			
		||||
            member={member}
 | 
			
		||||
            colored={true}
 | 
			
		||||
            emphasizeDisplayName={true}
 | 
			
		||||
        />
 | 
			
		||||
        : null;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1311,7 +1311,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
 | 
			
		|||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
            case TimelineRenderingType.Thread: {
 | 
			
		||||
                const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
 | 
			
		||||
                return React.createElement(this.props.as || "li", {
 | 
			
		||||
                    "ref": this.ref,
 | 
			
		||||
                    "className": classes,
 | 
			
		||||
| 
						 | 
				
			
			@ -1325,12 +1324,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
 | 
			
		|||
                    "onMouseEnter": () => this.setState({ hover: true }),
 | 
			
		||||
                    "onMouseLeave": () => this.setState({ hover: false }),
 | 
			
		||||
                }, [
 | 
			
		||||
                    <div className="mx_EventTile_roomName" key="mx_EventTile_roomName">
 | 
			
		||||
                        <RoomAvatar room={room} width={28} height={28} />
 | 
			
		||||
                        <a href={permalink} onClick={this.onPermalinkClicked}>
 | 
			
		||||
                            { room ? room.name : '' }
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>,
 | 
			
		||||
                    <div className="mx_EventTile_senderDetails" key="mx_EventTile_senderDetails">
 | 
			
		||||
                        { avatar }
 | 
			
		||||
                        { sender }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2022 The Matrix.org Foundation C.I.C.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
 | 
			
		||||
import { useContext, useEffect, useState } from "react";
 | 
			
		||||
 | 
			
		||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
 | 
			
		||||
import { useSettingValue } from "../useSettings";
 | 
			
		||||
 | 
			
		||||
export function useRoomMemberProfile({
 | 
			
		||||
    userId = "",
 | 
			
		||||
    member: propMember,
 | 
			
		||||
    forceHistorical = false,
 | 
			
		||||
}: {
 | 
			
		||||
    userId: string | undefined;
 | 
			
		||||
    member?: RoomMember | null;
 | 
			
		||||
    forceHistorical?: boolean;
 | 
			
		||||
}): RoomMember | undefined | null {
 | 
			
		||||
    const [member, setMember] = useState<RoomMember | undefined | null>(propMember);
 | 
			
		||||
 | 
			
		||||
    const context = useContext(RoomContext);
 | 
			
		||||
    const useOnlyCurrentProfiles = useSettingValue("useOnlyCurrentProfiles");
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const threadContexts = [TimelineRenderingType.ThreadsList, TimelineRenderingType.Thread];
 | 
			
		||||
        if ((propMember && !forceHistorical && useOnlyCurrentProfiles)
 | 
			
		||||
            || threadContexts.includes(context?.timelineRenderingType)) {
 | 
			
		||||
            setMember(context?.room?.getMember(userId));
 | 
			
		||||
        }
 | 
			
		||||
    }, [forceHistorical, propMember, context.room, context?.timelineRenderingType, useOnlyCurrentProfiles, userId]);
 | 
			
		||||
 | 
			
		||||
    return member;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -184,21 +184,65 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
 | 
			
		|||
                    Symbol(kCapture): false,
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
                resizeMethod="crop"
 | 
			
		||||
                viewUserOnClick={false}
 | 
			
		||||
                width={36}
 | 
			
		||||
              >
 | 
			
		||||
                <BaseAvatar
 | 
			
		||||
                  height={36}
 | 
			
		||||
                  hideTitle={false}
 | 
			
		||||
                  idName="@alice:server"
 | 
			
		||||
                  member={
 | 
			
		||||
                    RoomMember {
 | 
			
		||||
                      "_events": {},
 | 
			
		||||
                      "_eventsCount": 0,
 | 
			
		||||
                      "_isOutOfBand": false,
 | 
			
		||||
                      "_maxListeners": undefined,
 | 
			
		||||
                      "disambiguate": false,
 | 
			
		||||
                      "events": {},
 | 
			
		||||
                      "membership": undefined,
 | 
			
		||||
                      "modified": 1647270879403,
 | 
			
		||||
                      "name": "@alice:server",
 | 
			
		||||
                      "powerLevel": 0,
 | 
			
		||||
                      "powerLevelNorm": 0,
 | 
			
		||||
                      "rawDisplayName": "@alice:server",
 | 
			
		||||
                      "requestedProfileInfo": false,
 | 
			
		||||
                      "roomId": "!room:server",
 | 
			
		||||
                      "typing": false,
 | 
			
		||||
                      "user": undefined,
 | 
			
		||||
                      "userId": "@alice:server",
 | 
			
		||||
                      Symbol(kCapture): false,
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                  name="@alice:server"
 | 
			
		||||
                  resizeMethod="crop"
 | 
			
		||||
                  title="@alice:server"
 | 
			
		||||
                  url={null}
 | 
			
		||||
                  width={36}
 | 
			
		||||
                >
 | 
			
		||||
                  <span
 | 
			
		||||
                    className="mx_BaseAvatar"
 | 
			
		||||
                    hideTitle={false}
 | 
			
		||||
                    member={
 | 
			
		||||
                      RoomMember {
 | 
			
		||||
                        "_events": {},
 | 
			
		||||
                        "_eventsCount": 0,
 | 
			
		||||
                        "_isOutOfBand": false,
 | 
			
		||||
                        "_maxListeners": undefined,
 | 
			
		||||
                        "disambiguate": false,
 | 
			
		||||
                        "events": {},
 | 
			
		||||
                        "membership": undefined,
 | 
			
		||||
                        "modified": 1647270879403,
 | 
			
		||||
                        "name": "@alice:server",
 | 
			
		||||
                        "powerLevel": 0,
 | 
			
		||||
                        "powerLevelNorm": 0,
 | 
			
		||||
                        "rawDisplayName": "@alice:server",
 | 
			
		||||
                        "requestedProfileInfo": false,
 | 
			
		||||
                        "roomId": "!room:server",
 | 
			
		||||
                        "typing": false,
 | 
			
		||||
                        "user": undefined,
 | 
			
		||||
                        "userId": "@alice:server",
 | 
			
		||||
                        Symbol(kCapture): false,
 | 
			
		||||
                      }
 | 
			
		||||
                    }
 | 
			
		||||
                    role="presentation"
 | 
			
		||||
                  >
 | 
			
		||||
                    <span
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,4 +14,4 @@ exports[`<TextualBody /> renders formatted m.text correctly pills do not appear
 | 
			
		|||
</span>"
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`<TextualBody /> renders formatted m.text correctly pills get injected correctly into the DOM 1`] = `"<span class="mx_EventTile_body markdown-body" dir="auto">Hey <span><bdi><a class="mx_Pill mx_UserPill"><img class="mx_BaseAvatar mx_BaseAvatar_image" src="mxc://avatar.url/image.png" style="width: 16px; height: 16px;" alt="" aria-hidden="true"><span class="mx_Pill_linkText">Member</span></a></bdi></span></span>"`;
 | 
			
		||||
exports[`<TextualBody /> renders formatted m.text correctly pills get injected correctly into the DOM 1`] = `"<span class="mx_EventTile_body markdown-body" dir="auto">Hey <span><bdi><a class="mx_Pill mx_UserPill"><img class="mx_BaseAvatar mx_BaseAvatar_image" src="mxc://avatar.url/image.png" style="width: 16px; height: 16px;" alt="" member="[object Object]" aria-hidden="true"><span class="mx_Pill_linkText">Member</span></a></bdi></span></span>"`;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue