Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions apps/web/res/css/views/messages/_DisambiguatedProfile.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,28 @@ Please see LICENSE files in the repository root for full details.

/** Disambiguated profile needs to have a different layout in the member tile */
.mx_MemberTileView .mx_DisambiguatedProfile {
display: flex;
flex-direction: column;
/* Grid so that the name and the user status emoji share the first row
(with the name truncating and the emoji always visible), while the
disambiguated MXID gets a full row below. */
display: grid;
grid-template-columns: minmax(0, max-content) auto;
align-items: center;

.mx_DisambiguatedProfile_mxid {
grid-column: 1 / -1;
margin-inline-start: 0;
font: var(--cpd-font-body-sm-regular);
text-overflow: ellipsis;
overflow: hidden;
}

.mx_DisambiguatedProfile_userStatus {
margin-inline-start: var(--cpd-space-0-5x);
}

span:not(.mx_DisambiguatedProfile_mxid) {
/**
In a member tile, this span element is a flex child and so
In a member tile, this span element is a grid child and so
we need the following for text overflow to work.
**/
overflow: hidden;
Expand Down
4 changes: 4 additions & 0 deletions apps/web/res/css/views/right_panel/_RoomSummaryCard.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Please see LICENSE files in the repository root for full details.
text-overflow: ellipsis;
}

.mx_RoomSummaryCard_userStatus {
max-width: 100%;
}

.mx_RoomSummaryCard_topic {
padding: 0 12px;
color: var(--cpd-color-text-secondary);
Expand Down
5 changes: 5 additions & 0 deletions apps/web/res/css/views/right_panel/_UserInfo.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ Please see LICENSE files in the repository root for full details.
height: 20px;
}

.mx_UserInfo_userStatusMessage {
justify-content: center;
max-width: 100%;
}

.mx_UserInfo_timezone {
height: 20px;
margin: 0;
Expand Down
6 changes: 6 additions & 0 deletions apps/web/res/css/views/rooms/_RoomHeader.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ Please see LICENSE files in the repository root for full details.
padding: var(--cpd-space-1x);
}

.mx_RoomHeader_userStatus {
/* With the heading's own 4px gap this makes up the 8px gap from the design */
margin-inline-start: var(--cpd-space-1x);
min-width: 0;
}

.mx_RoomHeader .mx_FacePile {
color: $secondary-content;
background: $background;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/MatrixClientPeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
opts.threadSupport = true;
if (SettingsStore.getValue("feature_user_status")) {
opts.unstableMSC4429SyncUserProfileFields = ["org.matrix.msc4426.status"];
opts.unstableMSC4429SyncUserProfileFields = ["org.matrix.msc4426.status", "org.matrix.msc4426.call"];

Check warning on line 269 in apps/web/src/MatrixClientPeg.ts

View workflow job for this annotation

GitHub Actions / Tests

Uncovered Line

Line 269 is not covered by tests
}

if (SettingsStore.getValue("feature_sliding_sync")) {
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/autocomplete/Components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ interface ITextualCompletionProps {
"className"?: string;
"aria-selected"?: boolean;
"ref"?: Ref<HTMLDivElement>;
/** Optional node rendered immediately after the title, e.g. a user status emoji. */
"titleAdornment"?: React.ReactNode;
}

export const TextualCompletion = (props: ITextualCompletionProps): JSX.Element => {
Expand All @@ -30,6 +32,7 @@ export const TextualCompletion = (props: ITextualCompletionProps): JSX.Element =
subtitle,
description,
className,
titleAdornment,
"aria-selected": ariaSelectedAttribute,
ref,
...restProps
Expand All @@ -43,6 +46,7 @@ export const TextualCompletion = (props: ITextualCompletionProps): JSX.Element =
ref={ref}
>
<span className="mx_Autocomplete_Completion_title">{title}</span>
{titleAdornment}
<span className="mx_Autocomplete_Completion_subtitle">{subtitle}</span>
<span className="mx_Autocomplete_Completion_description">{description}</span>
</div>
Expand All @@ -60,6 +64,7 @@ export const PillCompletion = (props: IPillCompletionProps): JSX.Element => {
description,
className,
children,
titleAdornment,
"aria-selected": ariaSelectedAttribute,
ref,
...restProps
Expand All @@ -74,6 +79,7 @@ export const PillCompletion = (props: IPillCompletionProps): JSX.Element => {
>
{children}
<span className="mx_Autocomplete_Completion_title">{title}</span>
{titleAdornment}
<span className="mx_Autocomplete_Completion_subtitle">{subtitle}</span>
<span className="mx_Autocomplete_Completion_description">{description}</span>
</div>
Expand Down
7 changes: 6 additions & 1 deletion apps/web/src/autocomplete/UserProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import { MatrixClientPeg } from "../MatrixClientPeg";
import QueryMatcher from "./QueryMatcher";
import { PillCompletion } from "./Components";
import { UserStatusIcon } from "./UserStatusIcon";
import AutocompleteProvider from "./AutocompleteProvider";
import { _t } from "../languageHandler";
import { makeUserPermalink } from "../utils/permalinks/Permalinks";
Expand Down Expand Up @@ -127,7 +128,11 @@
suffix: selection.beginning && range!.start === 0 ? ": " : " ",
href: makeUserPermalink(user.userId),
component: (
<PillCompletion title={displayName} description={description ?? undefined}>
<PillCompletion
title={displayName}
titleAdornment={<UserStatusIcon userId={user.userId} />}
description={description ?? undefined}

Check warning on line 134 in apps/web/src/autocomplete/UserProvider.tsx

View workflow job for this annotation

GitHub Actions / Tests

Uncovered Line

Line 134 is not covered by tests
>
<MemberAvatar member={user} size="24px" />
</PillCompletion>
),
Expand Down
29 changes: 29 additions & 0 deletions apps/web/src/autocomplete/UserStatusIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2026 Element Creations Ltd.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

import React, { type JSX } from "react";
import { UserStatusIconView } from "@element-hq/web-shared-components";

import { useUserStatus } from "../hooks/useUserStatus";

interface Props {
/**
* The ID of the user whose status should be displayed.
*/
userId: string;
}

/**
* Fetches and displays the MSC4426 status emoji for a user, e.g. after their
* display name in the user mention autocomplete. Renders nothing if the feature
* is disabled or the user has no status.
*/
export function UserStatusIcon({ userId }: Props): JSX.Element | null {

Check warning on line 25 in apps/web/src/autocomplete/UserStatusIcon.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark the props of the component as read-only.

See more on https://sonarcloud.io/project/issues?id=element-web&issues=AZ88kc53MWESBjyfEIGV&open=AZ88kc53MWESBjyfEIGV&pullRequest=34161
const status = useUserStatus(userId);
if (!status) return null;
return <UserStatusIconView status={status} />;

Check warning on line 28 in apps/web/src/autocomplete/UserStatusIcon.tsx

View workflow job for this annotation

GitHub Actions / Tests

Uncovered Line

Lines 25-28 are not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
RoomEvent,
RoomStateEvent,
} from "matrix-js-sdk/src/matrix";
import { type UserStatus } from "@element-hq/web-shared-components";

import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
Expand Down Expand Up @@ -41,9 +42,17 @@ import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
import { tagRoom } from "../../../utils/room/tagRoom";
import { inviteToRoom } from "../../../utils/room/inviteToRoom";
import { getTagsForRoom } from "../../../utils/room/getTagsForRoom";
import { useDmMember } from "../../views/avatars/WithPresenceIndicator";
import { useUserStatus } from "../../../hooks/useUserStatus";

export interface RoomSummaryCardState {
isDirectMessage: boolean;
/**
* The MSC4426 status of the other user in a direct message, shown below the name.
* Undefined for non-DM rooms, when the user status feature is disabled, or when the
* user has no status.
*/
userStatus: UserStatus | undefined;
/**
* Whether the room is encrypted, used to display the correct badge and icon
*/
Expand Down Expand Up @@ -182,6 +191,8 @@ export function useRoomSummaryCardViewModel(
const isFavorite = roomTags.includes(DefaultTagID.Favourite);

const isDirectMessage = useIsDirectMessage(room);
const dmMember = useDmMember(room);
const userStatus = useUserStatus(dmMember?.userId);

const onRoomMembersClick = (): void => {
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.MemberList }, true);
Expand Down Expand Up @@ -261,6 +272,7 @@ export function useRoomSummaryCardViewModel(

return {
isDirectMessage,
userStatus,
isRoomEncrypted,
roomJoinRule,
e2eStatus,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error"
import ErrorSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down";
import { JoinRule, type Room } from "matrix-js-sdk/src/matrix";
import { Box, Flex, HistoryVisibilityBadge, LinkedText } from "@element-hq/web-shared-components";
import {
Box,
Flex,
HistoryVisibilityBadge,
LinkedText,
UserStatusMessageView,
} from "@element-hq/web-shared-components";

import BaseCard from "./BaseCard.tsx";
import { _t } from "../../../languageHandler.tsx";
Expand Down Expand Up @@ -153,6 +159,9 @@ const RoomSummaryCardView: React.FC<IProps> = ({
>
{name}
</Heading>
{vm.userStatus && (
<UserStatusMessageView status={vm.userStatus} className="mx_RoomSummaryCard_userStatus" />
)}
<Text
as="div"
size="sm"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
import React, { type JSX } from "react";
import { type User, type RoomMember } from "matrix-js-sdk/src/matrix";
import { Heading, Tooltip, Text } from "@vector-im/compound-web";
import { Flex } from "@element-hq/web-shared-components";
import { Flex, UserStatusMessageView } from "@element-hq/web-shared-components";

import { useUserfoHeaderViewModel } from "../../../viewmodels/right_panel/user_info/UserInfoHeaderViewModel";
import MemberAvatar from "../../avatars/MemberAvatar";
import { Container, type Member, type IDevice } from "../UserInfo";
import PresenceLabel from "../../rooms/PresenceLabel";
import CopyableText from "../../elements/CopyableText";
import { UserInfoHeaderVerificationView } from "./UserInfoHeaderVerificationView";
import { useUserStatus } from "../../../../hooks/useUserStatus";

export interface UserInfoHeaderViewProps {
member: Member;
Expand All @@ -33,6 +34,7 @@
const vm = useUserfoHeaderViewModel({ member, roomId });
const avatarUrl = (member as User).avatarUrl;
const displayName = (member as RoomMember).rawDisplayName;
const userStatus = useUserStatus(member.userId);

let presenceLabel: JSX.Element | undefined;

Expand Down Expand Up @@ -73,6 +75,9 @@
{displayName}
</Flex>
</Heading>
{userStatus && (

Check warning on line 78 in apps/web/src/components/views/right_panel/user_info/UserInfoHeaderView.tsx

View workflow job for this annotation

GitHub Actions / Tests

Uncovered Line

Line 78 is not covered by tests
<UserStatusMessageView status={userStatus} className="mx_UserInfo_userStatusMessage" />
)}
{presenceLabel}
{vm.timezoneInfo && (
<Tooltip label={vm.timezoneInfo?.timezone ?? ""}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { MemberTileView } from "./common/MemberTileView";
import { InvitedIconView } from "./common/InvitedIconView";
import { type MemberWithSeparator } from "../../../../viewmodels/memberlist/MemberListViewModel";
import { DisambiguatedProfileViewModel } from "../../../../../viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel";
import { useUserStatus } from "../../../../../hooks/useUserStatus";

interface IProps {
/**
Expand Down Expand Up @@ -47,17 +48,22 @@ export function RoomMemberTileView(props: IProps): JSX.Element {
/>
);
const name = vm.name;
const userStatus = useUserStatus(member.userId);
const disambiguatedProfileVM = useCreateAutoDisposedViewModel(
() =>
new DisambiguatedProfileViewModel({
fallbackName: name,
member,
withTooltip: true,
userStatus,
}),
);
useEffect(() => {
disambiguatedProfileVM.setMember(name, member);
}, [disambiguatedProfileVM, member, name]);
useEffect(() => {
disambiguatedProfileVM.setUserStatus(userStatus);
}, [disambiguatedProfileVM, userStatus]);
const nameJSX = <DisambiguatedProfileView vm={disambiguatedProfileVM} className="mx_DisambiguatedProfile" />;

const presenceState = member.presenceState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public";
import { HistoryVisibility, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
import { type ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
import { Flex, Box } from "@element-hq/web-shared-components";
import { Flex, Box, UserStatusMessageView } from "@element-hq/web-shared-components";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { HistoryIcon, UserProfileSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";

Expand Down Expand Up @@ -57,6 +57,7 @@
import { CurrentRightPanelPhaseContextProvider } from "../../../../contexts/CurrentRightPanelPhaseContext.tsx";
import { LocalRoom } from "../../../../models/LocalRoom.ts";
import { useIsEncrypted } from "../../../../hooks/useIsEncrypted.ts";
import { useUserStatus } from "../../../../hooks/useUserStatus.ts";

function RoomHeaderButtons({
room,
Expand Down Expand Up @@ -448,6 +449,7 @@
const historyVisibility = useRoomState(room, (state) => state.getHistoryVisibility());
const dmMember = useDmMember(room);
const isDirectMessage = !!dmMember;
const dmUserStatus = useUserStatus(dmMember?.userId);
const isRoomEncrypted = useIsEncrypted(client, room);
const e2eStatus = useEncryptionStatus(client, room);
const askToJoinEnabled = useFeatureEnabled("feature_ask_to_join");
Expand Down Expand Up @@ -496,6 +498,10 @@
>
<span className="mx_RoomHeader_truncated mx_lineClamp">{roomName}</span>

{isDirectMessage && dmUserStatus && (

Check warning on line 501 in apps/web/src/components/views/rooms/RoomHeader/RoomHeader.tsx

View workflow job for this annotation

GitHub Actions / Tests

Uncovered Line

Line 501 is not covered by tests
<UserStatusMessageView status={dmUserStatus} className="mx_RoomHeader_userStatus" />
)}

{!isDirectMessage && joinRule === JoinRule.Public && (
<Tooltip label={_t("common|public_room")} placement="right">
<PublicIcon
Expand Down
Loading
Loading