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

Correct accessibility labels for unread rooms in spotlight #9003

Merged
merged 14 commits into from
Jul 11, 2022
Merged
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
8 changes: 2 additions & 6 deletions src/components/views/dialogs/ForwardDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ import BaseAvatar from "../avatars/BaseAvatar";
import { Action } from "../../../dispatcher/actions";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { ButtonEvent } from "../elements/AccessibleButton";
import { roomContextDetailsText } from "../../../utils/i18n-helpers";
import { isLocationEvent } from "../../../utils/EventUtils";
import { isSelfLocation, locationEventGeoUri } from "../../../utils/location";
import { RoomContextDetails } from "../rooms/RoomContextDetails";

const AVATAR_SIZE = 30;

Expand Down Expand Up @@ -130,8 +130,6 @@ const Entry: React.FC<IEntryProps> = ({ room, type, content, matrixClient: cli,
/>;
}

const detailsText = roomContextDetailsText(room);

return <div className="mx_ForwardList_entry">
<AccessibleTooltipButton
className="mx_ForwardList_roomButton"
Expand All @@ -141,9 +139,7 @@ const Entry: React.FC<IEntryProps> = ({ room, type, content, matrixClient: cli,
>
<DecoratedRoomAvatar room={room} avatarSize={32} />
<span className="mx_ForwardList_entry_name">{ room.name }</span>
{ detailsText && <span className="mx_ForwardList_entry_detail">
{ detailsText }
</span> }
<RoomContextDetails component="span" className="mx_ForwardList_entry_detail" room={room} />
</AccessibleTooltipButton>
<AccessibleTooltipButton
kind={sendState === SendState.Failed ? "danger_outline" : "primary_outline"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ import { getDisplayAliasForRoom } from "../../../structures/RoomDirectory";
const MAX_NAME_LENGTH = 80;
const MAX_TOPIC_LENGTH = 800;

export function PublicRoomResultDetails({ room }: { room: IPublicRoomsChunkRoom }): JSX.Element {
interface Props {
room: IPublicRoomsChunkRoom;
labelId: string;
descriptionId: string;
detailsId: string;
}

export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsId }: Props): JSX.Element {
let name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
if (name.length > MAX_NAME_LENGTH) {
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
Expand All @@ -41,12 +48,12 @@ export function PublicRoomResultDetails({ room }: { room: IPublicRoomsChunkRoom
return (
<div className="mx_SpotlightDialog_result_publicRoomDetails">
<div className="mx_SpotlightDialog_result_publicRoomHeader">
<span className="mx_SpotlightDialog_result_publicRoomName">{ name }</span>
<span className="mx_SpotlightDialog_result_publicRoomAlias">
<span id={labelId} className="mx_SpotlightDialog_result_publicRoomName">{ name }</span>
<span id={descriptionId} className="mx_SpotlightDialog_result_publicRoomAlias">
{ room.canonical_alias ?? room.room_id }
</span>
</div>
<div className="mx_SpotlightDialog_result_publicRoomDescription">
<div id={detailsId} className="mx_SpotlightDialog_result_publicRoomDescription">
<span className="mx_SpotlightDialog_result_publicRoomMemberCount">
{ _t("%(count)s Members", {
count: room.num_joined_members,
Expand Down
194 changes: 152 additions & 42 deletions src/components/views/dialogs/spotlight/SpotlightDialog.tsx

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions src/components/views/rooms/RecentlyViewedButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import { MenuItem } from "../../structures/ContextMenu";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
import { RoomContextDetails } from "./RoomContextDetails";
import InteractiveTooltip, { Direction } from "../elements/InteractiveTooltip";
import { Action } from "../../../dispatcher/actions";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { roomContextDetailsText } from "../../../utils/i18n-helpers";
import RoomAvatar from "../avatars/RoomAvatar";

const RecentlyViewedButton = () => {
Expand All @@ -37,8 +37,6 @@ const RecentlyViewedButton = () => {
<h4>{ _t("Recently viewed") }</h4>
<div>
{ crumbs.map(crumb => {
const contextDetails = roomContextDetailsText(crumb);

return <MenuItem
key={crumb.roomId}
onClick={(ev) => {
Expand All @@ -57,9 +55,7 @@ const RecentlyViewedButton = () => {
}
<span className="mx_RecentlyViewedButton_entry_label">
<div>{ crumb.name }</div>
{ contextDetails && <div className="mx_RecentlyViewedButton_entry_spaces">
{ contextDetails }
</div> }
<RoomContextDetails className="mx_RecentlyViewedButton_entry_spaces" room={crumb} />
</span>
</MenuItem>;
}) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React from "react";
import { Room } from "matrix-js-sdk/src/matrix";
import React, { ComponentPropsWithoutRef, ElementType } from "react";

import { roomContextDetailsText, spaceContextDetailsText } from "../../../../utils/i18n-helpers";
import { roomContextDetails } from "../../../utils/i18n-helpers";

export const RoomResultDetails = ({ room }: { room: Room }) => {
const contextDetails = room.isSpaceRoom() ? spaceContextDetailsText(room) : roomContextDetailsText(room);
type Props<T extends ElementType> = ComponentPropsWithoutRef<T> & {
component?: T;
room: Room;
};

export function RoomContextDetails<T extends ElementType>({ room, component: Component = "div", ...other }: Props<T>) {
const contextDetails = roomContextDetails(room);
if (contextDetails) {
return <div className="mx_SpotlightDialog_result_details">
{ contextDetails }
</div>;
return <Component
{...other}
aria-label={contextDetails.ariaLabel}
>
{ contextDetails.details }
</Component>;
}

return null;
};
}
4 changes: 4 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -661,9 +661,13 @@
"about a day from now": "about a day from now",
"%(num)s days from now": "%(num)s days from now",
"%(space1Name)s and %(space2Name)s": "%(space1Name)s and %(space2Name)s",
"In spaces %(space1Name)s and %(space2Name)s.": "In spaces %(space1Name)s and %(space2Name)s.",
"%(spaceName)s and %(count)s others|other": "%(spaceName)s and %(count)s others",
"%(spaceName)s and %(count)s others|zero": "%(spaceName)s",
"%(spaceName)s and %(count)s others|one": "%(spaceName)s and %(count)s other",
"In %(spaceName)s and %(count)s other spaces.|other": "In %(spaceName)s and %(count)s other spaces.",
"In %(spaceName)s and %(count)s other spaces.|zero": "In space %(spaceName)s.",
"In %(spaceName)s and %(count)s other spaces.|one": "In %(spaceName)s and %(count)s other space.",
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
"Unexpected server error trying to leave the room": "Unexpected server error trying to leave the room",
"Can't leave Server Notices room": "Can't leave Server Notices room",
Expand Down
51 changes: 19 additions & 32 deletions src/utils/i18n-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,49 +20,36 @@ import SpaceStore from "../stores/spaces/SpaceStore";
import { _t } from "../languageHandler";
import DMRoomMap from "./DMRoomMap";

export function spaceContextDetailsText(space: Room): string {
if (!space.isSpaceRoom()) return undefined;

const [parent, secondParent, ...otherParents] = SpaceStore.instance.getKnownParents(space.roomId);
if (secondParent && !otherParents?.length) {
// exactly 2 edge case for improved i18n
return _t("%(space1Name)s and %(space2Name)s", {
space1Name: space.client.getRoom(parent)?.name,
space2Name: space.client.getRoom(secondParent)?.name,
});
} else if (parent) {
return _t("%(spaceName)s and %(count)s others", {
spaceName: space.client.getRoom(parent)?.name,
count: otherParents.length,
});
}

return space.getCanonicalAlias();
export interface RoomContextDetails {
details: string;
ariaLabel?: string;
}

export function roomContextDetailsText(room: Room): string {
if (room.isSpaceRoom()) return undefined;

export function roomContextDetails(room: Room): RoomContextDetails | null {
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
// if we’ve got more than 2 users, don’t treat it like a regular DM
const isGroupDm = room.getMembers().length > 2;
if (dmPartner && !isGroupDm) {
return dmPartner;
if (!room.isSpaceRoom() && dmPartner && !isGroupDm) {
return { details: dmPartner };
}

const [parent, secondParent, ...otherParents] = SpaceStore.instance.getKnownParents(room.roomId);
if (secondParent && !otherParents?.length) {
// exactly 2 edge case for improved i18n
return _t("%(space1Name)s and %(space2Name)s", {
space1Name: room.client.getRoom(parent)?.name,
space2Name: room.client.getRoom(secondParent)?.name,
});
const space1Name = room.client.getRoom(parent)?.name;
const space2Name = room.client.getRoom(secondParent)?.name;
return {
details: _t("%(space1Name)s and %(space2Name)s", { space1Name, space2Name }),
ariaLabel: _t("In spaces %(space1Name)s and %(space2Name)s.", { space1Name, space2Name }),
};
} else if (parent) {
return _t("%(spaceName)s and %(count)s others", {
spaceName: room.client.getRoom(parent)?.name,
count: otherParents.length,
});
const spaceName = room.client.getRoom(parent)?.name;
const count = otherParents.length;
return {
details: _t("%(spaceName)s and %(count)s others", { spaceName, count }),
ariaLabel: _t("In %(spaceName)s and %(count)s other spaces.", { spaceName, count }),
};
}

return room.getCanonicalAlias();
return { details: room.getCanonicalAlias() };
}