diff --git a/cypress/e2e/room-directory/room-directory.spec.ts b/cypress/e2e/room-directory/room-directory.spec.ts index f179b0988c2..9e2ee10c960 100644 --- a/cypress/e2e/room-directory/room-directory.spec.ts +++ b/cypress/e2e/room-directory/room-directory.spec.ts @@ -88,15 +88,16 @@ describe("Room Directory", () => { cy.get('[role="button"][aria-label="Explore rooms"]').click(); - cy.get('.mx_RoomDirectory_dialogWrapper [name="dirsearch"]').type("Unknown Room"); - cy.get(".mx_RoomDirectory_dialogWrapper h5").should("contain", 'No results for "Unknown Room"'); - cy.get(".mx_RoomDirectory_dialogWrapper").percySnapshotElement("Room Directory - filtered no results"); - - cy.get('.mx_RoomDirectory_dialogWrapper [name="dirsearch"]').type("{selectAll}{backspace}test1234"); - cy.contains(".mx_RoomDirectory_dialogWrapper .mx_RoomDirectory_listItem", name) - .should("exist").as("resultRow"); - cy.get(".mx_RoomDirectory_dialogWrapper").percySnapshotElement("Room Directory - filtered one result"); - cy.get("@resultRow").find(".mx_AccessibleButton").contains("Join").click(); + cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("Unknown Room"); + cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_otherSearches_messageSearchText") + .should("contain", "can't find the room you're looking for"); + cy.get(".mx_SpotlightDialog_wrapper").percySnapshotElement("Room Directory - filtered no results"); + + cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("{selectAll}{backspace}test1234"); + cy.contains(".mx_SpotlightDialog .mx_SpotlightDialog_result_publicRoomName", name) + .should("exist"); + cy.get(".mx_SpotlightDialog_wrapper").percySnapshotElement("Room Directory - filtered one result"); + cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_option").find(".mx_AccessibleButton").contains("Join").click(); cy.url().should('contain', `/#/room/#test1234:localhost`); }); diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 67c07b5d973..7f21752d4a1 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -64,7 +64,6 @@ @import "./structures/_NotificationPanel.pcss"; @import "./structures/_QuickSettingsButton.pcss"; @import "./structures/_RightPanel.pcss"; -@import "./structures/_RoomDirectory.pcss"; @import "./structures/_RoomSearch.pcss"; @import "./structures/_RoomStatusBar.pcss"; @import "./structures/_RoomView.pcss"; @@ -171,7 +170,6 @@ @import "./views/elements/_CopyableText.pcss"; @import "./views/elements/_DesktopCapturerSourcePicker.pcss"; @import "./views/elements/_DialPadBackspaceButton.pcss"; -@import "./views/elements/_DirectorySearchBox.pcss"; @import "./views/elements/_Dropdown.pcss"; @import "./views/elements/_EditableItemList.pcss"; @import "./views/elements/_ErrorBoundary.pcss"; diff --git a/res/css/structures/_RoomDirectory.pcss b/res/css/structures/_RoomDirectory.pcss deleted file mode 100644 index f5ecc734d2c..00000000000 --- a/res/css/structures/_RoomDirectory.pcss +++ /dev/null @@ -1,220 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2020 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. -*/ - -.mx_RoomDirectory_dialogWrapper > .mx_Dialog { - max-width: 960px; - height: 100%; -} - -.mx_RoomDirectory_dialog { - height: 100%; - display: flex; - flex-direction: column; -} - -.mx_RoomDirectory { - margin-bottom: 12px; - color: $primary-content; - word-break: break-word; - display: flex; - flex-direction: column; - flex: 1; -} - -.mx_RoomDirectory_list { - flex: 1; - display: flex; - flex-direction: column; -} - -.mx_RoomDirectory_list .mx_RoomView_messageListWrapper { - justify-content: flex-start; -} - -.mx_RoomDirectory_listheader { - display: block; - margin-top: 13px; -} - -.mx_RoomDirectory_searchbox { - flex: 1 !important; -} - -.mx_RoomDirectory_listheader .mx_GenericDropdownMenu_button { - margin: 0 9px 0 auto; - width: fit-content; -} - -.mx_RoomDirectory_tableWrapper { - overflow-y: auto; - flex: 1 1 0; - - .mx_RoomDirectory_footer { - margin-top: 24px; - text-align: center; - - > h5 { - margin: 0; - font-weight: $font-semi-bold; - font-size: $font-15px; - line-height: $font-18px; - color: $primary-content; - } - - > p { - margin: 40px auto 60px; - font-size: $font-14px; - line-height: $font-20px; - color: $secondary-content; - max-width: 464px; /* easier reading */ - } - - > hr { - margin: 0; - border: none; - height: 1px; - background-color: $header-panel-bg-color; - } - - .mx_RoomDirectory_newRoom { - margin: 24px auto 0; - width: max-content; - } - } -} - -.mx_RoomDirectory_table { - color: $primary-content; - display: grid; - font-size: $font-12px; - grid-template-columns: max-content auto max-content max-content max-content; - row-gap: 24px; - text-align: left; - width: 100%; -} - -.mx_RoomDirectory_roomAvatar { - padding: 2px 14px 0 0; -} - -.mx_RoomDirectory_roomMemberCount { - align-self: center; - color: $light-fg-color; - padding: 3px 10px 0; - - &::before { - background-color: $light-fg-color; - display: inline-block; - vertical-align: text-top; - margin-right: 2px; - content: ""; - mask: url("$(res)/img/feather-customised/user.svg"); - mask-repeat: no-repeat; - mask-position: center; - /* scale it down and make the size slightly bigger (16 instead of 14px) */ - /* to avoid rendering artifacts */ - mask-size: 80%; - width: 16px; - height: 16px; - } -} - -.mx_RoomDirectory_join, -.mx_RoomDirectory_preview { - align-self: center; - white-space: nowrap; -} - -.mx_RoomDirectory_name { - display: inline-block; - font-size: $font-18px; - font-weight: 600; -} - -.mx_RoomDirectory_perms { - display: inline-block; -} - -.mx_RoomDirectory_perm { - border-radius: 10px; - display: inline-block; - height: 20px; - line-height: $font-20px; - padding: 0 5px; - color: $accent-fg-color; - background-color: $pill-bg-color; -} - -.mx_RoomDirectory_topic { - cursor: initial; - color: $light-fg-color; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 3; - overflow: hidden; -} - -.mx_RoomDirectory_alias { - font-size: $font-12px; - color: $settings-grey-fg-color; -} - -.mx_RoomDirectory .mx_RoomView_MessageList { - padding: 0; -} - -.mx_RoomDirectory > span { - font-size: $font-15px; - margin-top: 0; -} - -@media screen and (max-width: 700px) { - .mx_RoomDirectory_roomMemberCount { - padding: 0px; - } - - .mx_RoomDirectory_join { - margin-left: 0px; - } - - .mx_RoomDirectory_alias { - margin-top: 10px; - margin-bottom: 10px; - } - - .mx_RoomDirectory_roomDescription { - padding-bottom: 0px; - } - - .mx_RoomDirectory_name { - margin-bottom: 5px; - } - - .mx_RoomDirectory_roomAvatar { - margin-top: 10px; - } - - .mx_RoomDirectory_table { - grid-template-columns: auto; - row-gap: 14px; - margin-top: 5px; - } -} - -.mx_RoomDirectory_listItem { - display: contents; -} diff --git a/res/css/views/elements/_DirectorySearchBox.pcss b/res/css/views/elements/_DirectorySearchBox.pcss deleted file mode 100644 index f8da4a578fc..00000000000 --- a/res/css/views/elements/_DirectorySearchBox.pcss +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2016 OpenMarket Ltd - -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. -*/ - -.mx_DirectorySearchBox { - display: flex; - padding-left: 9px; - padding-right: 9px; -} - -.mx_DirectorySearchBox_joinButton { - display: table-cell; - padding: 3px; - padding-left: 10px; - padding-right: 10px; - background-color: $secondary-accent-color; - border-radius: 3px; - background-image: url('$(res)/img/icon-return.svg'); - background-position: 8px 70%; - background-repeat: no-repeat; - text-indent: 18px; - font-weight: 600; - font-size: $font-12px; - user-select: none; - cursor: pointer; -} - -.mx_DirectorySearchBox_clear { - background-color: $alert; - mask: url('$(res)/img/cancel.svg'); - mask-repeat: no-repeat; - mask-position: center; - mask-size: 10px; - width: 15px; - height: 15px; - cursor: pointer; -} diff --git a/src/Rooms.ts b/src/Rooms.ts index 57a4bf522ea..97cfb9c47f6 100644 --- a/src/Rooms.ts +++ b/src/Rooms.ts @@ -29,7 +29,7 @@ import AliasCustomisations from './customisations/Alias'; * @param {Object} room The room object * @returns {string} A display alias for the given room */ -export function getDisplayAliasForRoom(room: Room): string { +export function getDisplayAliasForRoom(room: Room): string | undefined { return getDisplayAliasForAliasSet( room.getCanonicalAlias(), room.getAltAliases(), ); @@ -41,7 +41,7 @@ export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: s if (AliasCustomisations.getDisplayAliasForAliasSet) { return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases); } - return canonicalAlias || altAliases?.[0]; + return (canonicalAlias || altAliases?.[0]) ?? ""; } export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 59c9ec32b83..04fb4a0fae5 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -95,7 +95,6 @@ import Spinner from "../views/elements/Spinner"; import QuestionDialog from "../views/dialogs/QuestionDialog"; import UserSettingsDialog from '../views/dialogs/UserSettingsDialog'; import CreateRoomDialog from '../views/dialogs/CreateRoomDialog'; -import RoomDirectory from './RoomDirectory'; import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog"; import IncomingSasDialog from "../views/dialogs/IncomingSasDialog"; import CompleteSecurity from "./auth/CompleteSecurity"; @@ -141,6 +140,7 @@ import { viewUserDeviceSettings } from '../../actions/handlers/viewUserDeviceSet import { VoiceBroadcastResumer } from '../../voice-broadcast'; import GenericToast from "../views/toasts/GenericToast"; import { Linkify } from "../views/elements/Linkify"; +import RovingSpotlightDialog, { Filter } from '../views/dialogs/spotlight/SpotlightDialog'; // legacy export export { default as Views } from "../../Views"; @@ -716,9 +716,10 @@ export default class MatrixChat extends React.PureComponent { this.viewSomethingBehindModal(); break; case Action.ViewRoomDirectory: { - Modal.createDialog(RoomDirectory, { + Modal.createDialog(RovingSpotlightDialog, { initialText: payload.initialText, - }, 'mx_RoomDirectory_dialogWrapper', false, true); + initialFilter: Filter.PublicRooms, + }, 'mx_SpotlightDialog_wrapper', false, true); // View the welcome or home page if we need something to look at this.viewSomethingBehindModal(); diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx deleted file mode 100644 index a1274fcee51..00000000000 --- a/src/components/structures/RoomDirectory.tsx +++ /dev/null @@ -1,560 +0,0 @@ -/* -Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2015, 2016, 2019, 2020, 2021 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 React from "react"; -import { IFieldType, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client"; -import { Visibility } from "matrix-js-sdk/src/@types/partials"; -import { IRoomDirectoryOptions } from "matrix-js-sdk/src/@types/requests"; -import { logger } from "matrix-js-sdk/src/logger"; - -import { MatrixClientPeg } from "../../MatrixClientPeg"; -import dis from "../../dispatcher/dispatcher"; -import Modal from "../../Modal"; -import { _t } from '../../languageHandler'; -import SdkConfig from '../../SdkConfig'; -import { instanceForInstanceId, protocolNameForInstanceId, ALL_ROOMS, Protocols } from '../../utils/DirectoryUtils'; -import SettingsStore from "../../settings/SettingsStore"; -import { IDialogProps } from "../views/dialogs/IDialogProps"; -import { IPublicRoomDirectoryConfig, NetworkDropdown } from "../views/directory/NetworkDropdown"; -import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton"; -import ErrorDialog from "../views/dialogs/ErrorDialog"; -import QuestionDialog from "../views/dialogs/QuestionDialog"; -import BaseDialog from "../views/dialogs/BaseDialog"; -import DirectorySearchBox from "../views/elements/DirectorySearchBox"; -import ScrollPanel from "./ScrollPanel"; -import Spinner from "../views/elements/Spinner"; -import { getDisplayAliasForAliasSet } from "../../Rooms"; -import PosthogTrackers from "../../PosthogTrackers"; -import { PublicRoomTile } from "../views/rooms/PublicRoomTile"; -import { getFieldsForThirdPartyLocation, joinRoomByAlias, showRoom } from "../../utils/rooms"; -import { GenericError } from "../../utils/error"; - -const LAST_SERVER_KEY = "mx_last_room_directory_server"; -const LAST_INSTANCE_KEY = "mx_last_room_directory_instance"; - -interface IProps extends IDialogProps { - initialText?: string; -} - -interface IState { - publicRooms: IPublicRoomsChunkRoom[]; - loading: boolean; - protocolsLoading: boolean; - error?: string | null; - serverConfig: IPublicRoomDirectoryConfig | null; - filterString: string; -} - -export default class RoomDirectory extends React.Component { - private unmounted = false; - private nextBatch: string | null = null; - private filterTimeout: number | null; - private protocols: Protocols; - - constructor(props) { - super(props); - - let protocolsLoading = true; - if (!MatrixClientPeg.get()) { - // We may not have a client yet when invoked from welcome page - protocolsLoading = false; - } else { - MatrixClientPeg.get().getThirdpartyProtocols().then((response) => { - this.protocols = response; - const myHomeserver = MatrixClientPeg.getHomeserverName(); - const lsRoomServer = localStorage.getItem(LAST_SERVER_KEY) ?? undefined; - const lsInstanceId = localStorage.getItem(LAST_INSTANCE_KEY) ?? undefined; - - let roomServer: string | undefined = myHomeserver; - if ( - SdkConfig.getObject("room_directory")?.get("servers")?.includes(lsRoomServer) || - SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer) - ) { - roomServer = lsRoomServer; - } - - let instanceId: string | undefined = undefined; - if (roomServer === myHomeserver && ( - lsInstanceId === ALL_ROOMS || - Object.values(this.protocols).some(p => p.instances.some(i => i.instance_id === lsInstanceId)) - )) { - instanceId = lsInstanceId; - } - - // Refresh the room list only if validation failed and we had to change these - if (this.state.serverConfig?.instanceId !== instanceId || - this.state.serverConfig?.roomServer !== roomServer) { - this.setState({ - protocolsLoading: false, - serverConfig: roomServer ? { instanceId, roomServer } : null, - }); - this.refreshRoomList(); - return; - } - this.setState({ protocolsLoading: false }); - }, (err) => { - logger.warn(`error loading third party protocols: ${err}`); - this.setState({ protocolsLoading: false }); - if (MatrixClientPeg.get().isGuest()) { - // Guests currently aren't allowed to use this API, so - // ignore this as otherwise this error is literally the - // thing you see when loading the client! - return; - } - const brand = SdkConfig.get().brand; - this.setState({ - error: _t( - '%(brand)s failed to get the protocol list from the homeserver. ' + - 'The homeserver may be too old to support third party networks.', - { brand }, - ), - }); - }); - } - - let serverConfig: IPublicRoomDirectoryConfig | null = null; - const roomServer = localStorage.getItem(LAST_SERVER_KEY); - if (roomServer) { - serverConfig = { - roomServer, - instanceId: localStorage.getItem(LAST_INSTANCE_KEY) ?? undefined, - }; - } - - this.state = { - publicRooms: [], - loading: true, - error: null, - serverConfig, - filterString: this.props.initialText || "", - protocolsLoading, - }; - } - - componentDidMount() { - this.refreshRoomList(); - } - - componentWillUnmount() { - if (this.filterTimeout) { - clearTimeout(this.filterTimeout); - } - this.unmounted = true; - } - - private refreshRoomList = () => { - this.nextBatch = null; - this.setState({ - publicRooms: [], - loading: true, - }); - this.getMoreRooms(); - }; - - private getMoreRooms(): Promise { - if (!MatrixClientPeg.get()) return Promise.resolve(false); - - this.setState({ - loading: true, - }); - - const filterString = this.state.filterString; - const roomServer = this.state.serverConfig?.roomServer; - // remember the next batch token when we sent the request - // too. If it's changed, appending to the list will corrupt it. - const nextBatch = this.nextBatch; - const opts: IRoomDirectoryOptions = { limit: 20 }; - if (roomServer != MatrixClientPeg.getHomeserverName()) { - opts.server = roomServer; - } - if (this.state.serverConfig?.instanceId === ALL_ROOMS) { - opts.include_all_networks = true; - } else if (this.state.serverConfig?.instanceId) { - opts.third_party_instance_id = this.state.serverConfig?.instanceId as string; - } - if (this.nextBatch) opts.since = this.nextBatch; - if (filterString) opts.filter = { generic_search_term: filterString }; - return MatrixClientPeg.get().publicRooms(opts).then((data) => { - if ( - filterString != this.state.filterString || - roomServer != this.state.serverConfig?.roomServer || - nextBatch != this.nextBatch) { - // if the filter or server has changed since this request was sent, - // throw away the result (don't even clear the busy flag - // since we must still have a request in flight) - return false; - } - - if (this.unmounted) { - // if we've been unmounted, we don't care either. - return false; - } - - this.nextBatch = data.next_batch ?? null; - this.setState((s) => ({ - ...s, - publicRooms: [...s.publicRooms, ...(data.chunk || [])], - loading: false, - })); - return Boolean(data.next_batch); - }, (err) => { - if ( - filterString != this.state.filterString || - roomServer != this.state.serverConfig?.roomServer || - nextBatch != this.nextBatch) { - // as above: we don't care about errors for old requests either - return false; - } - - if (this.unmounted) { - // if we've been unmounted, we don't care either. - return false; - } - - logger.error("Failed to get publicRooms: %s", JSON.stringify(err)); - const brand = SdkConfig.get().brand; - this.setState({ - loading: false, - error: ( - _t('%(brand)s failed to get the public room list.', { brand }) + - (err && err.message) ? err.message : _t('The homeserver may be unavailable or overloaded.') - ), - }); - return false; - }); - } - - /** - * A limited interface for removing rooms from the directory. - * Will set the room to not be publicly visible and delete the - * default alias. In the long term, it would be better to allow - * HS admins to do this through the RoomSettings interface, but - * this needs SPEC-417. - */ - private removeFromDirectory = (room: IPublicRoomsChunkRoom) => { - const alias = getDisplayAliasForRoom(room); - const name = room.name || alias || _t('Unnamed room'); - - let desc; - if (alias) { - desc = _t('Delete the room address %(alias)s and remove %(name)s from the directory?', { alias, name }); - } else { - desc = _t('Remove %(name)s from the directory?', { name: name }); - } - - Modal.createDialog(QuestionDialog, { - title: _t('Remove from Directory'), - description: desc, - onFinished: (shouldDelete: boolean) => { - if (!shouldDelete) return; - - const modal = Modal.createDialog(Spinner); - let step = _t('remove %(name)s from the directory.', { name: name }); - - MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, Visibility.Private).then(() => { - if (!alias) return; - step = _t('delete the address.'); - return MatrixClientPeg.get().deleteAlias(alias); - }).then(() => { - modal.close(); - this.refreshRoomList(); - }, (err) => { - modal.close(); - this.refreshRoomList(); - logger.error("Failed to " + step + ": " + err); - Modal.createDialog(ErrorDialog, { - title: _t('Error'), - description: (err && err.message) - ? err.message - : _t('The server may be unavailable or overloaded'), - }); - }); - }, - }); - }; - - private onOptionChange = (serverConfig: IPublicRoomDirectoryConfig) => { - // clear next batch so we don't try to load more rooms - this.nextBatch = null; - this.setState({ - // Clear the public rooms out here otherwise we needlessly - // spend time filtering lots of rooms when we're about to - // to clear the list anyway. - publicRooms: [], - serverConfig, - error: null, - }, this.refreshRoomList); - // We also refresh the room list each time even though this - // filtering is client-side. It hopefully won't be client side - // for very long, and we may have fetched a thousand rooms to - // find the five gitter ones, at which point we do not want - // to render all those rooms when switching back to 'all networks'. - // Easiest to just blow away the state & re-fetch. - - // We have to be careful here so that we don't set instanceId = "undefined" - localStorage.setItem(LAST_SERVER_KEY, serverConfig.roomServer); - if (serverConfig.instanceId) { - localStorage.setItem(LAST_INSTANCE_KEY, serverConfig.instanceId); - } else { - localStorage.removeItem(LAST_INSTANCE_KEY); - } - }; - - private onFillRequest = (backwards: boolean) => { - if (backwards || !this.nextBatch) return Promise.resolve(false); - - return this.getMoreRooms(); - }; - - private onFilterChange = (alias: string) => { - this.setState({ - filterString: alias?.trim() || "", - }); - - // don't send the request for a little bit, - // no point hammering the server with a - // request for every keystroke, let the - // user finish typing. - if (this.filterTimeout) { - clearTimeout(this.filterTimeout); - } - this.filterTimeout = setTimeout(() => { - this.filterTimeout = null; - this.refreshRoomList(); - }, 700); - }; - - private onFilterClear = () => { - // update immediately - this.setState({ - filterString: "", - }, this.refreshRoomList); - - if (this.filterTimeout) { - clearTimeout(this.filterTimeout); - } - }; - - private onJoinFromSearchClick = (alias: string) => { - const cli = MatrixClientPeg.get(); - try { - joinRoomByAlias(cli, alias, { - instanceId: this.state.serverConfig?.instanceId, - roomServer: this.state.serverConfig?.roomServer, - protocols: this.protocols, - metricsTrigger: "RoomDirectory", - }); - } catch (e) { - if (e instanceof GenericError) { - Modal.createDialog(ErrorDialog, { - title: e.message, - description: e.description, - }); - } else { - throw e; - } - } - }; - - private onCreateRoomClick = (ev: ButtonEvent) => { - this.onFinished(); - dis.dispatch({ - action: 'view_create_room', - public: true, - defaultName: this.state.filterString.trim(), - }); - PosthogTrackers.trackInteraction("WebRoomDirectoryCreateRoomButton", ev); - }; - - private onRoomClick = (room: IPublicRoomsChunkRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) => { - this.onFinished(); - const cli = MatrixClientPeg.get(); - showRoom(cli, room, { - roomAlias, - autoJoin, - shouldPeek, - roomServer: this.state.serverConfig?.roomServer, - metricsTrigger: "RoomDirectory", - }); - }; - - private stringLooksLikeId(s: string, fieldType: IFieldType) { - let pat = /^#[^\s]+:[^\s]/; - if (fieldType && fieldType.regexp) { - pat = new RegExp(fieldType.regexp); - } - - return pat.test(s); - } - - private onFinished = () => { - this.props.onFinished(false); - }; - - public render() { - let content; - if (this.state.error) { - content = this.state.error; - } else if (this.state.protocolsLoading) { - content = ; - } else { - const cells = (this.state.publicRooms || []) - .map(room => - , - ); - // we still show the scrollpanel, at least for now, because - // otherwise we don't fetch more because we don't get a fill - // request from the scrollpanel because there isn't one - - let spinner; - if (this.state.loading) { - spinner = ; - } - - const createNewButton = <> -
- - { _t("Create new room") } - - ; - - let scrollPanelContent; - let footer; - if (cells.length === 0 && !this.state.loading) { - footer = <> -
{ _t('No results for "%(query)s"', { query: this.state.filterString.trim() }) }
-

- { _t("Try different words or check for typos. " + - "Some results may not be visible as they're private and you need an invite to join them.") } -

- { createNewButton } - ; - } else { - scrollPanelContent =
- { cells } -
; - if (!this.state.loading && !this.nextBatch) { - footer = createNewButton; - } - } - content = - { scrollPanelContent } - { spinner } - { footer &&
- { footer } -
} -
; - } - - let listHeader; - if (!this.state.protocolsLoading) { - const protocolName = protocolNameForInstanceId(this.protocols, this.state.serverConfig?.instanceId); - let instanceExpectedFieldType; - if ( - protocolName && - this.protocols && - this.protocols[protocolName] && - this.protocols[protocolName].location_fields.length > 0 && - this.protocols[protocolName].field_types - ) { - const lastField = this.protocols[protocolName].location_fields.slice(-1)[0]; - instanceExpectedFieldType = this.protocols[protocolName].field_types[lastField]; - } - - let placeholder = _t('Find a room…'); - if (!this.state.serverConfig?.instanceId || this.state.serverConfig?.instanceId === ALL_ROOMS) { - placeholder = _t("Find a room… (e.g. %(exampleRoom)s)", { - exampleRoom: "#example:" + this.state.serverConfig?.roomServer, - }); - } else if (instanceExpectedFieldType) { - placeholder = instanceExpectedFieldType.placeholder; - } - - let showJoinButton = this.stringLooksLikeId(this.state.filterString, instanceExpectedFieldType); - if (protocolName) { - const instance = instanceForInstanceId(this.protocols, this.state.serverConfig?.instanceId); - if (!instance || getFieldsForThirdPartyLocation( - this.state.filterString, - this.protocols[protocolName], - instance, - ) === null) { - showJoinButton = false; - } - } - - listHeader =
- - -
; - } - const explanation = - _t("If you can't find the room you're looking for, ask for an invite or create a new room.", {}, - { a: sub => ( - - { sub } - - ) }, - ); - - const title = _t("Explore rooms"); - return ( - -
- { explanation } -
- { listHeader } - { content } -
-
-
- ); - } -} - -// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom -// but works with the objects we get from the public room list -export function getDisplayAliasForRoom(room: IPublicRoomsChunkRoom) { - return getDisplayAliasForAliasSet(room.canonical_alias, room.aliases); -} diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index b9c967f8d25..8bcbf0a45d8 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -55,7 +55,6 @@ import { linkifyElement, topicToHtml } from "../../HtmlUtils"; import { useDispatcher } from "../../hooks/useDispatcher"; import { Action } from "../../dispatcher/actions"; import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex"; -import { getDisplayAliasForRoom } from "./RoomDirectory"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import { useTypedEventEmitterState } from "../../hooks/useEventEmitter"; import { IOOBData } from "../../stores/ThreepidInviteStore"; @@ -67,6 +66,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager"; import { Alignment } from "../views/elements/Tooltip"; import { getTopic } from "../../hooks/room/useTopic"; import { SdkContextClass } from "../../contexts/SDKContext"; +import { getDisplayAliasForAliasSet } from "../../Rooms"; interface IProps { space: Room; @@ -342,7 +342,8 @@ export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st } } - const roomAlias = getDisplayAliasForRoom(room) || undefined; + const roomAlias = getDisplayAliasForAliasSet(room?.canonical_alias ?? "", room?.aliases ?? []) || undefined; + defaultDispatcher.dispatch({ action: Action.ViewRoom, should_peek: true, diff --git a/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx b/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx index 3f33a400cc8..b7ca8ef1e16 100644 --- a/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx +++ b/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx @@ -19,7 +19,7 @@ import { IPublicRoomsChunkRoom } from "matrix-js-sdk/src/matrix"; import { linkifyAndSanitizeHtml } from "../../../../HtmlUtils"; import { _t } from "../../../../languageHandler"; -import { getDisplayAliasForRoom } from "../../../structures/RoomDirectory"; +import { getDisplayAliasForAliasSet } from "../../../../Rooms"; const MAX_NAME_LENGTH = 80; const MAX_TOPIC_LENGTH = 800; @@ -32,7 +32,9 @@ interface Props { } export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsId }: Props): JSX.Element { - let name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room'); + let name = room.name + || getDisplayAliasForAliasSet(room.canonical_alias ?? "", room.aliases ?? []) + || _t('Unnamed room'); if (name.length > MAX_NAME_LENGTH) { name = `${name.substring(0, MAX_NAME_LENGTH)}...`; } diff --git a/src/components/views/elements/DirectorySearchBox.tsx b/src/components/views/elements/DirectorySearchBox.tsx deleted file mode 100644 index d4f20817e1e..00000000000 --- a/src/components/views/elements/DirectorySearchBox.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright 2016 OpenMarket Ltd - -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 React, { ChangeEvent, createRef } from 'react'; - -import { _t } from '../../../languageHandler'; -import AccessibleButton from "./AccessibleButton"; - -interface IProps { - className?: string; - onChange?: (value: string) => void; - onClear?: () => void; - onJoinClick?: (value: string) => void; - placeholder?: string; - showJoinButton?: boolean; - initialText?: string; -} - -interface IState { - value: string; -} - -export default class DirectorySearchBox extends React.Component { - private input = createRef(); - - constructor(props: IProps) { - super(props); - - this.state = { - value: this.props.initialText || '', - }; - } - - private onClearClick = (): void => { - this.setState({ value: '' }); - - if (this.input.current) { - this.input.current.focus(); - - if (this.props.onClear) { - this.props.onClear(); - } - } - }; - - private onChange = (ev: ChangeEvent): void => { - if (!this.input.current) return; - this.setState({ value: ev.target.value }); - - if (this.props.onChange) { - this.props.onChange(ev.target.value); - } - }; - - private onKeyUp = (ev: React.KeyboardEvent): void => { - if (ev.key == 'Enter' && this.props.showJoinButton) { - if (this.props.onJoinClick) { - this.props.onJoinClick(this.state.value); - } - } - }; - - private onJoinButtonClick = (): void => { - if (this.props.onJoinClick) { - this.props.onJoinClick(this.state.value); - } - }; - - public render(): JSX.Element { - const searchboxClasses = { - mx_DirectorySearchBox: true, - }; - searchboxClasses[this.props.className] = true; - - let joinButton; - if (this.props.showJoinButton) { - joinButton = { _t("Join") }; - } - - return
- - { joinButton } - -
; - } -} - diff --git a/src/components/views/rooms/PublicRoomTile.tsx b/src/components/views/rooms/PublicRoomTile.tsx deleted file mode 100644 index a0f3b89fae2..00000000000 --- a/src/components/views/rooms/PublicRoomTile.tsx +++ /dev/null @@ -1,179 +0,0 @@ -/* -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 React, { useCallback, useContext, useEffect, useState } from "react"; -import { IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client"; - -import BaseAvatar from "../avatars/BaseAvatar"; -import { mediaFromMxc } from "../../../customisations/Media"; -import { linkifyAndSanitizeHtml } from "../../../HtmlUtils"; -import { getDisplayAliasForRoom } from "../../structures/RoomDirectory"; -import AccessibleButton from "../elements/AccessibleButton"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import { _t } from "../../../languageHandler"; - -const MAX_NAME_LENGTH = 80; -const MAX_TOPIC_LENGTH = 800; - -interface IProps { - room: IPublicRoomsChunkRoom; - removeFromDirectory?: (room: IPublicRoomsChunkRoom) => void; - showRoom: (room: IPublicRoomsChunkRoom, roomAlias?: string, autoJoin?: boolean, shouldPeek?: boolean) => void; -} - -export const PublicRoomTile = ({ - room, - showRoom, - removeFromDirectory, -}: IProps) => { - const client = useContext(MatrixClientContext); - - const [avatarUrl, setAvatarUrl] = useState(null); - const [name, setName] = useState(""); - const [topic, setTopic] = useState(""); - - const [hasJoinedRoom, setHasJoinedRoom] = useState(false); - - const isGuest = client.isGuest(); - - useEffect(() => { - const clientRoom = client.getRoom(room.room_id); - - setHasJoinedRoom(clientRoom?.getMyMembership() === "join"); - - let name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room'); - if (name.length > MAX_NAME_LENGTH) { - name = `${name.substring(0, MAX_NAME_LENGTH)}...`; - } - setName(name); - - let topic = room.topic || ''; - // Additional truncation based on line numbers is done via CSS, - // but to ensure that the DOM is not polluted with a huge string - // we give it a hard limit before rendering. - if (topic.length > MAX_TOPIC_LENGTH) { - topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`; - } - topic = linkifyAndSanitizeHtml(topic); - setTopic(topic); - if (room.avatar_url) { - setAvatarUrl(mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32)); - } - }, [room, client]); - - const onRoomClicked = useCallback((ev: React.MouseEvent) => { - // If room was shift-clicked, remove it from the room directory - if (ev.shiftKey) { - ev.preventDefault(); - removeFromDirectory?.(room); - } - }, [room, removeFromDirectory]); - - const onPreviewClick = useCallback((ev: React.MouseEvent) => { - showRoom(room, null, false, true); - ev.stopPropagation(); - }, [room, showRoom]); - - const onViewClick = useCallback((ev: React.MouseEvent) => { - showRoom(room); - ev.stopPropagation(); - }, [room, showRoom]); - - const onJoinClick = useCallback((ev: React.MouseEvent) => { - showRoom(room, null, true); - ev.stopPropagation(); - }, [room, showRoom]); - - let previewButton; - let joinOrViewButton; - - // Element Web currently does not allow guests to join rooms, so we - // instead show them preview buttons for all rooms. If the room is not - // world readable, a modal will appear asking you to register first. If - // it is readable, the preview appears as normal. - if (!hasJoinedRoom && (room.world_readable || isGuest)) { - previewButton = ( - - { _t("Preview") } - - ); - } - if (hasJoinedRoom) { - joinOrViewButton = ( - - { _t("View") } - - ); - } else if (!isGuest) { - joinOrViewButton = ( - - { _t("Join") } - - ); - } - - return
-
- -
-
-
- { name } -
  -
-
- { getDisplayAliasForRoom(room) } -
-
-
- { room.num_joined_members } -
-
- { previewButton } -
-
- { joinOrViewButton } -
-
; -}; diff --git a/src/customisations/Alias.ts b/src/customisations/Alias.ts index fcf6742193f..06b80389d67 100644 --- a/src/customisations/Alias.ts +++ b/src/customisations/Alias.ts @@ -14,8 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string { - // E.g. prefer one of the aliases over another +function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string { // E.g. prefer one of the aliases over another return null; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 549a1b3e981..af1cab69dcb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -745,13 +745,6 @@ "Common names and surnames are easy to guess": "Common names and surnames are easy to guess", "Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess", "Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess", - "Unnamed room": "Unnamed room", - "Unable to join network": "Unable to join network", - "%(brand)s does not know how to join a room on this network": "%(brand)s does not know how to join a room on this network", - "Room not found": "Room not found", - "Couldn't find a matching Matrix room": "Couldn't find a matching Matrix room", - "Fetching third party location failed": "Fetching third party location failed", - "Unable to look up room ID from server": "Unable to look up room ID from server", "Error upgrading room": "Error upgrading room", "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.", "Invite to %(spaceName)s": "Invite to %(spaceName)s", @@ -1936,8 +1929,6 @@ "Idle": "Idle", "Offline": "Offline", "Unknown": "Unknown", - "Preview": "Preview", - "View": "View", "%(members)s and more": "%(members)s and more", "%(members)s and %(last)s": "%(members)s and %(last)s", "Seen by %(count)s people|other": "Seen by %(count)s people", @@ -3008,11 +2999,13 @@ "Allow this widget to verify your identity": "Allow this widget to verify your identity", "The widget will verify your user ID, but won't be able to perform actions for you:": "The widget will verify your user ID, but won't be able to perform actions for you:", "Remember this": "Remember this", + "Unnamed room": "Unnamed room", "%(count)s Members|other": "%(count)s Members", "%(count)s Members|one": "%(count)s Member", "Public rooms": "Public rooms", "Use \"%(query)s\" to search": "Use \"%(query)s\" to search", "Search for": "Search for", + "View": "View", "Spaces you're in": "Spaces you're in", "Show rooms": "Show rooms", "Show spaces": "Show spaces", @@ -3311,20 +3304,6 @@ "%(creator)s created and configured the room.": "%(creator)s created and configured the room.", "You're all caught up": "You're all caught up", "You have no visible notifications.": "You have no visible notifications.", - "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.", - "%(brand)s failed to get the public room list.": "%(brand)s failed to get the public room list.", - "The homeserver may be unavailable or overloaded.": "The homeserver may be unavailable or overloaded.", - "Delete the room address %(alias)s and remove %(name)s from the directory?": "Delete the room address %(alias)s and remove %(name)s from the directory?", - "Remove %(name)s from the directory?": "Remove %(name)s from the directory?", - "Remove from Directory": "Remove from Directory", - "remove %(name)s from the directory.": "remove %(name)s from the directory.", - "delete the address.": "delete the address.", - "The server may be unavailable or overloaded": "The server may be unavailable or overloaded", - "No results for \"%(query)s\"": "No results for \"%(query)s\"", - "Try different words or check for typos. Some results may not be visible as they're private and you need an invite to join them.": "Try different words or check for typos. Some results may not be visible as they're private and you need an invite to join them.", - "Find a room…": "Find a room…", - "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", - "If you can't find the room you're looking for, ask for an invite or create a new room.": "If you can't find the room you're looking for, ask for an invite or create a new room.", "Search failed": "Search failed", "Server may be unavailable, overloaded, or search timed out :(": "Server may be unavailable, overloaded, or search timed out :(", "No more results": "No more results", diff --git a/src/utils/DirectoryUtils.ts b/src/utils/DirectoryUtils.ts index c5bb9578299..a74cf6d7f9a 100644 --- a/src/utils/DirectoryUtils.ts +++ b/src/utils/DirectoryUtils.ts @@ -14,35 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { IInstance, IProtocol } from "matrix-js-sdk/src/client"; - -// XXX: We would ideally use a symbol here but we can't since we save this value to localStorage -export const ALL_ROOMS = "ALL_ROOMS"; +import { IProtocol } from "matrix-js-sdk/src/client"; export type Protocols = Record; - -// Find a protocol 'instance' with a given instance_id -// in the supplied protocols dict -export function instanceForInstanceId(protocols: Protocols, instanceId: string | null | undefined): IInstance | null { - if (!instanceId) return null; - for (const proto of Object.keys(protocols)) { - if (!Array.isArray(protocols[proto].instances)) continue; - for (const instance of protocols[proto].instances) { - if (instance.instance_id == instanceId) return instance; - } - } - return null; -} - -// given an instance_id, return the name of the protocol for -// that instance ID in the supplied protocols dict -export function protocolNameForInstanceId(protocols: Protocols, instanceId: string | null | undefined): string | null { - if (!instanceId) return null; - for (const proto of Object.keys(protocols)) { - if (!Array.isArray(protocols[proto].instances)) continue; - for (const instance of protocols[proto].instances) { - if (instance.instance_id == instanceId) return proto; - } - } - return null; -} diff --git a/src/utils/rooms.ts b/src/utils/rooms.ts index 3be6c00e677..bd6fcf9d971 100644 --- a/src/utils/rooms.ts +++ b/src/utils/rooms.ts @@ -14,18 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { IInstance, IProtocol, IPublicRoomsChunkRoom, MatrixClient } from "matrix-js-sdk/src/client"; -import { ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom"; - -import { Action } from "../dispatcher/actions"; -import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; import { getE2EEWellKnown } from "./WellKnownUtils"; -import dis from "../dispatcher/dispatcher"; -import { getDisplayAliasForAliasSet } from "../Rooms"; -import { _t } from "../languageHandler"; -import { instanceForInstanceId, protocolNameForInstanceId, ALL_ROOMS, Protocols } from "./DirectoryUtils"; -import SdkConfig from "../SdkConfig"; -import { GenericError } from "./error"; export function privateShouldBeEncrypted(): boolean { const e2eeWellKnown = getE2EEWellKnown(); @@ -35,146 +24,3 @@ export function privateShouldBeEncrypted(): boolean { } return true; } - -interface IShowRoomOpts { - roomAlias?: string; - autoJoin?: boolean; - shouldPeek?: boolean; - roomServer?: string; - metricsTrigger: ViewRoomEvent["trigger"]; -} - -export const showRoom = ( - client: MatrixClient, - room: IPublicRoomsChunkRoom | null, - { - roomAlias, - autoJoin = false, - shouldPeek = false, - roomServer, - }: IShowRoomOpts, -): void => { - const payload: ViewRoomPayload = { - action: Action.ViewRoom, - auto_join: autoJoin, - should_peek: shouldPeek, - metricsTrigger: "RoomDirectory", - }; - if (room) { - // Don't let the user view a room they won't be able to either - // peek or join: fail earlier so they don't have to click back - // to the directory. - if (client.isGuest()) { - if (!room.world_readable && !room.guest_can_join) { - dis.dispatch({ action: 'require_registration' }); - return; - } - } - - if (!roomAlias) { - roomAlias = getDisplayAliasForAliasSet(room.canonical_alias, room.aliases); - } - - payload.oob_data = { - avatarUrl: room.avatar_url, - // XXX: This logic is duplicated from the JS SDK which - // would normally decide what the name is. - name: room.name || roomAlias || _t('Unnamed room'), - }; - - if (roomServer) { - payload.via_servers = [roomServer]; - } - } - // It's not really possible to join Matrix rooms by ID because the HS has no way to know - // which servers to start querying. However, there's no other way to join rooms in - // this list without aliases at present, so if roomAlias isn't set here we have no - // choice but to supply the ID. - if (roomAlias) { - payload.room_alias = roomAlias; - } else { - payload.room_id = room.room_id; - } - dis.dispatch(payload); -}; - -interface IJoinRoomByAliasOpts { - instanceId?: string; - roomServer?: string; - protocols: Protocols; - metricsTrigger: ViewRoomEvent["trigger"]; -} - -export function joinRoomByAlias(cli: MatrixClient, alias: string, { - instanceId, - roomServer, - protocols, - metricsTrigger, -}: IJoinRoomByAliasOpts): void { - // If we don't have a particular instance id selected, just show that rooms alias - if (!instanceId || instanceId === ALL_ROOMS) { - // If the user specified an alias without a domain, add on whichever server is selected - // in the dropdown - if (!alias.includes(':')) { - alias = alias + ':' + roomServer; - } - showRoom(cli, null, { - roomAlias: alias, - autoJoin: true, - metricsTrigger, - }); - } else { - // This is a 3rd party protocol. Let's see if we can join it - const protocolName = protocolNameForInstanceId(protocols, instanceId); - const instance = instanceForInstanceId(protocols, instanceId); - const fields = protocolName - ? getFieldsForThirdPartyLocation(alias, protocols[protocolName], instance) - : null; - if (!fields) { - const brand = SdkConfig.get().brand; - throw new GenericError( - _t('Unable to join network'), - _t('%(brand)s does not know how to join a room on this network', { brand }), - ); - } - cli.getThirdpartyLocation(protocolName, fields).then((resp) => { - if (resp.length > 0 && resp[0].alias) { - showRoom(cli, null, { - roomAlias: resp[0].alias, - autoJoin: true, - metricsTrigger, - }); - } else { - throw new GenericError( - _t('Room not found'), - _t('Couldn\'t find a matching Matrix room'), - ); - } - }, (e) => { - throw new GenericError( - _t('Fetching third party location failed'), - _t('Unable to look up room ID from server'), - ); - }); - } -} - -export function getFieldsForThirdPartyLocation( - userInput: string, - protocol: IProtocol, - instance: IInstance, -): { searchFields?: string[] } | null { - // make an object with the fields specified by that protocol. We - // require that the values of all but the last field come from the - // instance. The last is the user input. - const requiredFields = protocol.location_fields; - if (!requiredFields) return null; - const fields = {}; - for (let i = 0; i < requiredFields.length - 1; ++i) { - const thisField = requiredFields[i]; - if (instance.fields[thisField] === undefined) return null; - fields[thisField] = instance.fields[thisField]; - } - fields[requiredFields[requiredFields.length - 1]] = userInput; - return fields; -} diff --git a/test/components/structures/SpaceHierarchy-test.tsx b/test/components/structures/SpaceHierarchy-test.tsx new file mode 100644 index 00000000000..ceaf723fc9a --- /dev/null +++ b/test/components/structures/SpaceHierarchy-test.tsx @@ -0,0 +1,70 @@ +/* +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 { MatrixClient } from "matrix-js-sdk/src/client"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy"; + +import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; +import { stubClient } from "../../test-utils"; +import dispatcher from "../../../src/dispatcher/dispatcher"; +import { showRoom } from "../../../src/components/structures/SpaceHierarchy"; +import { Action } from "../../../src/dispatcher/actions"; + +describe("SpaceHierarchy", () => { + describe("showRoom", () => { + let client: MatrixClient; + let hierarchy: RoomHierarchy; + let room: Room; + beforeEach(() => { + stubClient(); + client = MatrixClientPeg.get(); + room = new Room("room-id", client, "@alice:example.com"); + hierarchy = new RoomHierarchy(room); + + jest.spyOn(client, "isGuest").mockReturnValue(false); + + jest.spyOn(hierarchy.roomMap, "get").mockReturnValue({ + children_state: [], + room_id: "room-id2", + canonical_alias: "canonical-alias", + aliases: ["uncanonical-alias", "canonical-alias"], + world_readable: true, + guest_can_join: false, + num_joined_members: 35, + }); + + jest.spyOn(dispatcher, "dispatch"); + }); + + it("shows room", () => { + showRoom(client, hierarchy, "room-id2"); + expect(dispatcher.dispatch).toHaveBeenCalledWith({ + "action": Action.ViewRoom, + "should_peek": true, + "room_alias": "canonical-alias", + "room_id": "room-id2", + "via_servers": [], + "oob_data": { + avatarUrl: undefined, + name: "canonical-alias", + }, + "roomType": undefined, + "metricsTrigger": "RoomDirectory", + }); + }); + }); +}); diff --git a/test/components/views/dialogs/spotlight/PublicRoomResultDetails-test.tsx b/test/components/views/dialogs/spotlight/PublicRoomResultDetails-test.tsx new file mode 100644 index 00000000000..b5414fc19f3 --- /dev/null +++ b/test/components/views/dialogs/spotlight/PublicRoomResultDetails-test.tsx @@ -0,0 +1,69 @@ +/* +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 React from "react"; +import { render } from "@testing-library/react"; +import { IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client"; + +import { PublicRoomResultDetails } from "../../../../../src/components/views/dialogs/spotlight/PublicRoomResultDetails"; + +describe("PublicRoomResultDetails", () => { + it("renders", () => { + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); + }); + + it.each([ + { canonical_alias: "canonical-alias" }, + { aliases: ["alias-from-aliases"] }, + { name: "name over alias", canonical_alias: "canonical-alias" }, + { + name: "with an overly long name that will be truncated for sure, you can't say anything about it", + topic: "with a topic!", + }, + { topic: "Very long topic " + new Array(1337).join("a") }, + ])("Public room results", (partialPublicRoomChunk: Partial) => { + const roomChunk: IPublicRoomsChunkRoom = { + room_id: "room-id", + world_readable: true, + guest_can_join: false, + num_joined_members: 666, + ...partialPublicRoomChunk, + }; + + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/test/components/views/dialogs/spotlight/__snapshots__/PublicRoomResultDetails-test.tsx.snap b/test/components/views/dialogs/spotlight/__snapshots__/PublicRoomResultDetails-test.tsx.snap new file mode 100644 index 00000000000..380ceeae364 --- /dev/null +++ b/test/components/views/dialogs/spotlight/__snapshots__/PublicRoomResultDetails-test.tsx.snap @@ -0,0 +1,223 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PublicRoomResultDetails Public room results 1`] = ` + +
+
+ + canonical-alias + + + canonical-alias + +
+
+ + 666 Members + +
+
+
+`; + +exports[`PublicRoomResultDetails Public room results 2`] = ` + +
+
+ + alias-from-aliases + + + room-id + +
+
+ + 666 Members + +
+
+
+`; + +exports[`PublicRoomResultDetails Public room results 3`] = ` + +
+
+ + name over alias + + + canonical-alias + +
+
+ + 666 Members + +
+
+
+`; + +exports[`PublicRoomResultDetails Public room results 4`] = ` + +
+
+ + with an overly long name that will be truncated for sure, you can't say anything... + + + room-id + +
+
+ + 666 Members + +  ·  + + with a topic! + +
+
+
+`; + +exports[`PublicRoomResultDetails Public room results 5`] = ` + +
+
+ + Unnamed room + + + room-id + +
+
+ + 666 Members + +  ·  + + Very long topic aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... + +
+
+
+`; + +exports[`PublicRoomResultDetails renders 1`] = ` + +
+
+ + hello? + + + canonical-alias + +
+
+ + 666 Members + +
+
+
+`;