Correct accessibility labels for unread rooms in spotlight (#9003)
* Correct accessibility labels for unread rooms in spotlight * Improve public room result accessibility * Improve room result accessibility
This commit is contained in:
parent
375ff265db
commit
a9d6896502
7 changed files with 206 additions and 98 deletions
|
@ -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;
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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"}
|
||||
|
|
|
@ -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)}...`;
|
||||
|
@ -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,
|
||||
|
|
|
@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { WebSearch as WebSearchEvent } from "@matrix-org/analytics-events/types/typescript/WebSearch";
|
||||
import classNames from "classnames";
|
||||
import { capitalize, sum } from "lodash";
|
||||
import { WebSearch as WebSearchEvent } from "@matrix-org/analytics-events/types/typescript/WebSearch";
|
||||
import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
|
||||
import { IPublicRoomsChunkRoom, MatrixClient, RoomMember, RoomType } from "matrix-js-sdk/src/matrix";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
@ -50,6 +50,7 @@ import { useDebouncedCallback } from "../../../../hooks/spotlight/useDebouncedCa
|
|||
import { useRecentSearches } from "../../../../hooks/spotlight/useRecentSearches";
|
||||
import { useProfileInfo } from "../../../../hooks/useProfileInfo";
|
||||
import { usePublicRoomDirectory } from "../../../../hooks/usePublicRoomDirectory";
|
||||
import { useFeatureEnabled } from "../../../../hooks/useSettings";
|
||||
import { useSpaceResults } from "../../../../hooks/useSpaceResults";
|
||||
import { useUserDirectory } from "../../../../hooks/useUserDirectory";
|
||||
import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
|
||||
|
@ -63,6 +64,7 @@ import SdkConfig from "../../../../SdkConfig";
|
|||
import { SettingLevel } from "../../../../settings/SettingLevel";
|
||||
import SettingsStore from "../../../../settings/SettingsStore";
|
||||
import { BreadcrumbsStore } from "../../../../stores/BreadcrumbsStore";
|
||||
import { RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState";
|
||||
import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore";
|
||||
import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||
import { RoomViewStore } from "../../../../stores/RoomViewStore";
|
||||
|
@ -78,6 +80,7 @@ import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar";
|
|||
import { SearchResultAvatar } from "../../avatars/SearchResultAvatar";
|
||||
import { NetworkDropdown } from "../../directory/NetworkDropdown";
|
||||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
import LabelledCheckbox from "../../elements/LabelledCheckbox";
|
||||
import Spinner from "../../elements/Spinner";
|
||||
import NotificationBadge from "../../rooms/NotificationBadge";
|
||||
import BaseDialog from "../BaseDialog";
|
||||
|
@ -85,10 +88,8 @@ import FeedbackDialog from "../FeedbackDialog";
|
|||
import { IDialogProps } from "../IDialogProps";
|
||||
import { Option } from "./Option";
|
||||
import { PublicRoomResultDetails } from "./PublicRoomResultDetails";
|
||||
import { RoomResultDetails } from "./RoomResultDetails";
|
||||
import { RoomContextDetails } from "../../rooms/RoomContextDetails";
|
||||
import { TooltipOption } from "./TooltipOption";
|
||||
import LabelledCheckbox from "../../elements/LabelledCheckbox";
|
||||
import { useFeatureEnabled } from "../../../../hooks/useSettings";
|
||||
|
||||
const MAX_RECENT_SEARCHES = 10;
|
||||
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
|
||||
|
@ -259,6 +260,22 @@ const findVisibleRoomMembers = (cli: MatrixClient, filterDMs = true) => {
|
|||
).filter(it => it.userId !== cli.getUserId());
|
||||
};
|
||||
|
||||
const roomAriaUnreadLabel = (room: Room, notification: RoomNotificationState): string | undefined => {
|
||||
if (notification.hasMentions) {
|
||||
return _t("%(count)s unread messages including mentions.", {
|
||||
count: notification.count,
|
||||
});
|
||||
} else if (notification.hasUnreadCount) {
|
||||
return _t("%(count)s unread messages.", {
|
||||
count: notification.count,
|
||||
});
|
||||
} else if (notification.isUnread) {
|
||||
return _t("Unread messages.");
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
interface IDirectoryOpts {
|
||||
limit: number;
|
||||
query: string;
|
||||
|
@ -523,6 +540,12 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
if (trimmedQuery || filter !== null) {
|
||||
const resultMapper = (result: Result): JSX.Element => {
|
||||
if (isRoomResult(result)) {
|
||||
const notification = RoomNotificationStateStore.instance.getRoomState(result.room);
|
||||
const unreadLabel = roomAriaUnreadLabel(result.room, notification);
|
||||
const ariaProperties = {
|
||||
"aria-label": unreadLabel ? `${result.room.name} ${unreadLabel}` : result.room.name,
|
||||
"aria-details": `mx_SpotlightDialog_button_result_${result.room.roomId}_details`,
|
||||
};
|
||||
return (
|
||||
<Option
|
||||
id={`mx_SpotlightDialog_button_result_${result.room.roomId}`}
|
||||
|
@ -530,11 +553,20 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
onClick={(ev) => {
|
||||
viewRoom(result.room.roomId, true, ev?.type !== "click");
|
||||
}}
|
||||
{...ariaProperties}
|
||||
>
|
||||
<DecoratedRoomAvatar room={result.room} avatarSize={AVATAR_SIZE} tooltipProps={{ tabIndex: -1 }} />
|
||||
<DecoratedRoomAvatar
|
||||
room={result.room}
|
||||
avatarSize={AVATAR_SIZE}
|
||||
tooltipProps={{ tabIndex: -1 }}
|
||||
/>
|
||||
{ result.room.name }
|
||||
<NotificationBadge notification={RoomNotificationStateStore.instance.getRoomState(result.room)} />
|
||||
<RoomResultDetails room={result.room} />
|
||||
<NotificationBadge notification={notification} />
|
||||
<RoomContextDetails
|
||||
id={`mx_SpotlightDialog_button_result_${result.room.roomId}_details`}
|
||||
className="mx_SpotlightDialog_result_details"
|
||||
room={result.room}
|
||||
/>
|
||||
</Option>
|
||||
);
|
||||
}
|
||||
|
@ -547,10 +579,17 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
startDm(cli, [result.member]);
|
||||
onFinished();
|
||||
}}
|
||||
aria-label={result.member instanceof RoomMember
|
||||
? result.member.rawDisplayName
|
||||
: result.member.name}
|
||||
aria-describedby={`mx_SpotlightDialog_button_result_${result.member.userId}_details`}
|
||||
>
|
||||
<SearchResultAvatar user={result.member} size={AVATAR_SIZE} />
|
||||
{ result.member instanceof RoomMember ? result.member.rawDisplayName : result.member.name }
|
||||
<div className="mx_SpotlightDialog_result_details">
|
||||
<div
|
||||
id={`mx_SpotlightDialog_button_result_${result.member.userId}_details`}
|
||||
className="mx_SpotlightDialog_result_details"
|
||||
>
|
||||
{ result.member.userId }
|
||||
</div>
|
||||
</Option>
|
||||
|
@ -575,6 +614,9 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
>
|
||||
{ _t(clientRoom ? "View" : "Join") }
|
||||
</AccessibleButton>}
|
||||
aria-labelledby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`}
|
||||
aria-describedby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_alias`}
|
||||
aria-details={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_details`}
|
||||
>
|
||||
<BaseAvatar
|
||||
className="mx_SearchResultAvatar"
|
||||
|
@ -586,7 +628,12 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
width={AVATAR_SIZE}
|
||||
height={AVATAR_SIZE}
|
||||
/>
|
||||
<PublicRoomResultDetails room={result.publicRoom} />
|
||||
<PublicRoomResultDetails
|
||||
room={result.publicRoom}
|
||||
labelId={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`}
|
||||
descriptionId={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_alias`}
|
||||
detailsId={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_details`}
|
||||
/>
|
||||
</Option>
|
||||
);
|
||||
}
|
||||
|
@ -608,8 +655,13 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
let peopleSection: JSX.Element;
|
||||
if (results[Section.People].length) {
|
||||
peopleSection = (
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_results" role="group">
|
||||
<h4>{ _t("Recent Conversations") }</h4>
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_results"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_people">
|
||||
<h4 id="mx_SpotlightDialog_section_people">
|
||||
{ _t("Recent Conversations") }
|
||||
</h4>
|
||||
<div>
|
||||
{ results[Section.People].slice(0, SECTION_LIMIT).map(resultMapper) }
|
||||
</div>
|
||||
|
@ -620,8 +672,13 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
let suggestionsSection: JSX.Element;
|
||||
if (results[Section.Suggestions].length && filter === Filter.People) {
|
||||
suggestionsSection = (
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_results" role="group">
|
||||
<h4>{ _t("Suggestions") }</h4>
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_results"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_suggestions">
|
||||
<h4 id="mx_SpotlightDialog_section_suggestions">
|
||||
{ _t("Suggestions") }
|
||||
</h4>
|
||||
<div>
|
||||
{ results[Section.Suggestions].slice(0, SECTION_LIMIT).map(resultMapper) }
|
||||
</div>
|
||||
|
@ -632,8 +689,13 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
let roomsSection: JSX.Element;
|
||||
if (results[Section.Rooms].length) {
|
||||
roomsSection = (
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_results" role="group">
|
||||
<h4>{ _t("Rooms") }</h4>
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_results"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_rooms">
|
||||
<h4 id="mx_SpotlightDialog_section_rooms">
|
||||
{ _t("Rooms") }
|
||||
</h4>
|
||||
<div>
|
||||
{ results[Section.Rooms].slice(0, SECTION_LIMIT).map(resultMapper) }
|
||||
</div>
|
||||
|
@ -644,8 +706,13 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
let spacesSection: JSX.Element;
|
||||
if (results[Section.Spaces].length) {
|
||||
spacesSection = (
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_results" role="group">
|
||||
<h4>{ _t("Spaces you're in") }</h4>
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_results"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_spaces">
|
||||
<h4 id="mx_SpotlightDialog_section_spaces">
|
||||
{ _t("Spaces you're in") }
|
||||
</h4>
|
||||
<div>
|
||||
{ results[Section.Spaces].slice(0, SECTION_LIMIT).map(resultMapper) }
|
||||
</div>
|
||||
|
@ -656,9 +723,14 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
let publicRoomsSection: JSX.Element;
|
||||
if (filter === Filter.PublicRooms) {
|
||||
publicRoomsSection = (
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_results" role="group">
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_results"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_publicRooms">
|
||||
<div className="mx_SpotlightDialog_sectionHeader">
|
||||
<h4>{ _t("Suggestions") }</h4>
|
||||
<h4 id="mx_SpotlightDialog_section_publicRooms">
|
||||
{ _t("Suggestions") }
|
||||
</h4>
|
||||
<div className="mx_SpotlightDialog_options">
|
||||
{ exploringPublicSpacesEnabled && <>
|
||||
<LabelledCheckbox
|
||||
|
@ -692,8 +764,13 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
let spaceRoomsSection: JSX.Element;
|
||||
if (spaceResults.length && activeSpace && filter === null) {
|
||||
spaceRoomsSection = (
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_results" role="group">
|
||||
<h4>{ _t("Other rooms in %(spaceName)s", { spaceName: activeSpace.name }) }</h4>
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_results"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_spaceRooms">
|
||||
<h4 id="mx_SpotlightDialog_section_spaceRooms">
|
||||
{ _t("Other rooms in %(spaceName)s", { spaceName: activeSpace.name }) }
|
||||
</h4>
|
||||
<div>
|
||||
{ spaceResults.slice(0, SECTION_LIMIT).map((room: IHierarchyRoom): JSX.Element => (
|
||||
<Option
|
||||
|
@ -807,8 +884,13 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
let groupChatSection: JSX.Element;
|
||||
if (filter === Filter.People) {
|
||||
groupChatSection = (
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_otherSearches" role="group">
|
||||
<h4>{ _t('Other options') }</h4>
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_otherSearches"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_groupChat">
|
||||
<h4 id="mx_SpotlightDialog_section_groupChat">
|
||||
{ _t('Other options') }
|
||||
</h4>
|
||||
<Option
|
||||
id="mx_SpotlightDialog_button_startGroupChat"
|
||||
className="mx_SpotlightDialog_startGroupChat"
|
||||
|
@ -823,8 +905,13 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
let messageSearchSection: JSX.Element;
|
||||
if (filter === null) {
|
||||
messageSearchSection = (
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_otherSearches" role="group">
|
||||
<h4>{ _t("Other searches") }</h4>
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_otherSearches"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_messageSearch">
|
||||
<h4 id="mx_SpotlightDialog_section_messageSearch">
|
||||
{ _t("Other searches") }
|
||||
</h4>
|
||||
<div className="mx_SpotlightDialog_otherSearches_messageSearchText">
|
||||
{ _t(
|
||||
"To search messages, look for this icon at the top of a room <icon/>",
|
||||
|
@ -859,36 +946,59 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
// Firefox sometimes makes this element focusable due to overflow,
|
||||
// so force it out of tab order by default.
|
||||
tabIndex={-1}
|
||||
aria-labelledby="mx_SpotlightDialog_section_recentSearches"
|
||||
>
|
||||
<h4>
|
||||
<h4 id="mx_SpotlightDialog_section_recentSearches">
|
||||
{ _t("Recent searches") }
|
||||
<AccessibleButton kind="link" onClick={clearRecentSearches}>
|
||||
{ _t("Clear") }
|
||||
</AccessibleButton>
|
||||
</h4>
|
||||
<div>
|
||||
{ recentSearches.map(room => (
|
||||
{ recentSearches.map(room => {
|
||||
const notification = RoomNotificationStateStore.instance.getRoomState(room);
|
||||
const unreadLabel = roomAriaUnreadLabel(room, notification);
|
||||
const ariaProperties = {
|
||||
"aria-label": unreadLabel ? `${room.name} ${unreadLabel}` : room.name,
|
||||
"aria-details": `mx_SpotlightDialog_button_recentSearch_${room.roomId}_details`,
|
||||
};
|
||||
return (
|
||||
<Option
|
||||
id={`mx_SpotlightDialog_button_recentSearch_${room.roomId}`}
|
||||
key={room.roomId}
|
||||
onClick={(ev) => {
|
||||
viewRoom(room.roomId, true, ev?.type !== "click");
|
||||
}}
|
||||
{...ariaProperties}
|
||||
>
|
||||
<DecoratedRoomAvatar room={room} avatarSize={AVATAR_SIZE} tooltipProps={{ tabIndex: -1 }} />
|
||||
<DecoratedRoomAvatar
|
||||
room={room}
|
||||
avatarSize={AVATAR_SIZE}
|
||||
tooltipProps={{ tabIndex: -1 }}
|
||||
/>
|
||||
{ room.name }
|
||||
<NotificationBadge notification={RoomNotificationStateStore.instance.getRoomState(room)} />
|
||||
<RoomResultDetails room={room} />
|
||||
<NotificationBadge notification={notification} />
|
||||
<RoomContextDetails
|
||||
id={`mx_SpotlightDialog_button_recentSearch_${room.roomId}_details`}
|
||||
className="mx_SpotlightDialog_result_details"
|
||||
room={room}
|
||||
/>
|
||||
</Option>
|
||||
)) }
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
content = <>
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_recentlyViewed" role="group">
|
||||
<h4>{ _t("Recently viewed") }</h4>
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_recentlyViewed"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_recentlyViewed">
|
||||
<h4 id="mx_SpotlightDialog_section_recentlyViewed">
|
||||
{ _t("Recently viewed") }
|
||||
</h4>
|
||||
<div>
|
||||
{ BreadcrumbsStore.instance.rooms
|
||||
.filter(r => r.roomId !== RoomViewStore.instance.getRoomId())
|
||||
|
|
|
@ -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 = () => {
|
||||
|
@ -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) => {
|
||||
|
@ -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>;
|
||||
}) }
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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() };
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue