diff --git a/cypress/e2e/room-directory/room-directory.spec.ts b/cypress/e2e/room-directory/room-directory.spec.ts index f179b0988c2..7276abf7554 100644 --- a/cypress/e2e/room-directory/room-directory.spec.ts +++ b/cypress/e2e/room-directory/room-directory.spec.ts @@ -72,32 +72,4 @@ describe("Room Directory", () => { expect(resp.chunk[0].room_id).to.equal(roomId); }); }); - - it("should allow finding published rooms in directory", () => { - const name = "This is a public room"; - cy.all([ - cy.window({ log: false }), - cy.get("@bot"), - ]).then(([win, bot]) => { - bot.createRoom({ - visibility: win.matrixcs.Visibility.Public, - name, - room_alias_name: "test1234", - }); - }); - - 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.url().should('contain', `/#/room/#test1234:localhost`); - }); }); diff --git a/src/components/structures/HomePage.tsx b/src/components/structures/HomePage.tsx index b2596cee435..90d1c5f2f57 100644 --- a/src/components/structures/HomePage.tsx +++ b/src/components/structures/HomePage.tsx @@ -32,6 +32,8 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import MiniAvatarUploader, { AVATAR_SIZE } from "../views/elements/MiniAvatarUploader"; import PosthogTrackers from "../../PosthogTrackers"; import EmbeddedPage from "./EmbeddedPage"; +import { OpenSpotlightPayload } from "../../dispatcher/payloads/OpenSpotlightPayload"; +import { Filter } from "../views/dialogs/spotlight/SpotlightDialog"; const onClickSendDm = (ev: ButtonEvent) => { PosthogTrackers.trackInteraction("WebHomeCreateChatButton", ev); @@ -40,7 +42,10 @@ const onClickSendDm = (ev: ButtonEvent) => { const onClickExplore = (ev: ButtonEvent) => { PosthogTrackers.trackInteraction("WebHomeExploreRoomsButton", ev); - dis.fire(Action.ViewRoomDirectory); + dis.dispatch({ + action: Action.OpenSpotlight, + initialFilter: Filter.PublicRooms, + }); }; const onClickNewRoom = (ev: ButtonEvent) => { diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 09136257f3d..c981c7f6542 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -1,5 +1,5 @@ /* -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2020, 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. @@ -19,7 +19,9 @@ import { createRef } from "react"; import classNames from "classnames"; import dis from "../../dispatcher/dispatcher"; +import { OpenSpotlightPayload } from "../../dispatcher/payloads/OpenSpotlightPayload"; import { _t } from "../../languageHandler"; +import { Filter } from "../views/dialogs/spotlight/SpotlightDialog"; import RoomList from "../views/rooms/RoomList"; import LegacyCallHandler from "../../LegacyCallHandler"; import { HEADER_HEIGHT } from "../views/rooms/RoomSublist"; @@ -121,8 +123,11 @@ export default class LeftPanel extends React.Component { }; private onExplore = (ev: ButtonEvent) => { - dis.fire(Action.ViewRoomDirectory); PosthogTrackers.trackInteraction("WebLeftPanelExploreRoomsButton", ev); + dis.dispatch({ + action: Action.OpenSpotlight, + initialFilter: Filter.PublicRooms, + }); }; private refreshStickyHeaders = () => { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 06a73ff605f..72ca5a547ca 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -96,7 +96,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"; @@ -135,6 +134,8 @@ import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases import RightPanelStore from "../../stores/right-panel/RightPanelStore"; import { TimelineRenderingType } from "../../contexts/RoomContext"; import { UseCaseSelection } from '../views/elements/UseCaseSelection'; +import SpotlightDialog, { Filter } from '../views/dialogs/spotlight/SpotlightDialog'; +import { OpenSpotlightPayload } from '../../dispatcher/payloads/OpenSpotlightPayload'; import { ValidatedServerConfig } from '../../utils/ValidatedServerConfig'; import { isLocalRoom } from '../../utils/localRoom/isLocalRoom'; import { SdkContextClass, SDKContext } from '../../contexts/SDKContext'; @@ -708,10 +709,11 @@ export default class MatrixChat extends React.PureComponent { // View the welcome or home page if we need something to look at this.viewSomethingBehindModal(); break; - case Action.ViewRoomDirectory: { - Modal.createDialog(RoomDirectory, { + case Action.OpenSpotlight: { + Modal.createDialog(SpotlightDialog, { initialText: payload.initialText, - }, 'mx_RoomDirectory_dialogWrapper', false, true); + initialFilter: payload.initialFilter, + }, "mx_SpotlightDialog_wrapper", false, true); // View the welcome or home page if we need something to look at this.viewSomethingBehindModal(); @@ -1711,7 +1713,10 @@ export default class MatrixChat extends React.PureComponent { action: 'require_registration', }); } else if (screen === 'directory') { - dis.fire(Action.ViewRoomDirectory); + dis.dispatch({ + action: Action.OpenSpotlight, + initialFilter: Filter.PublicRooms, + }); } else if (screen === "start_sso" || screen === "start_cas") { let cli = MatrixClientPeg.get(); if (!cli) { 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/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 4cc36e17943..dd686a69e7f 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -18,12 +18,11 @@ import classNames from "classnames"; import * as React from "react"; import { ALTERNATE_KEY_NAME } from "../../accessibility/KeyboardShortcuts"; +import { Action } from "../../dispatcher/actions"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { ActionPayload } from "../../dispatcher/payloads"; import { IS_MAC, Key } from "../../Keyboard"; import { _t } from "../../languageHandler"; -import Modal from "../../Modal"; -import SpotlightDialog from "../views/dialogs/spotlight/SpotlightDialog"; import AccessibleButton from "../views/elements/AccessibleButton"; interface IProps { @@ -44,7 +43,7 @@ export default class RoomSearch extends React.PureComponent { } private openSpotlight() { - Modal.createDialog(SpotlightDialog, {}, "mx_SpotlightDialog_wrapper", false, true); + defaultDispatcher.fire(Action.OpenSpotlight); } private onAction = (payload: ActionPayload) => { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 2dfe61aefa3..5ac76dc1a36 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -38,6 +38,7 @@ import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { THREAD_RELATION_TYPE } from 'matrix-js-sdk/src/models/thread'; import { HistoryVisibility } from 'matrix-js-sdk/src/@types/partials'; +import { OpenSpotlightPayload } from "../../dispatcher/payloads/OpenSpotlightPayload"; import shouldHideEvent from '../../shouldHideEvent'; import { _t } from '../../languageHandler'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; @@ -48,6 +49,7 @@ import { LegacyCallHandlerEvent } from '../../LegacyCallHandler'; import dis, { defaultDispatcher } from '../../dispatcher/dispatcher'; import * as Rooms from '../../Rooms'; import eventSearch, { searchPagination } from '../../Searching'; +import { Filter } from "../views/dialogs/spotlight/SpotlightDialog"; import MainSplit from './MainSplit'; import RightPanel from './RightPanel'; import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore'; @@ -1765,7 +1767,10 @@ export class RoomView extends React.Component { // using /leave rather than /join. In the short term though, we // just ignore them. // https://github.com/vector-im/vector-web/issues/1134 - dis.fire(Action.ViewRoomDirectory); + dis.dispatch({ + action: Action.OpenSpotlight, + initialFilter: Filter.PublicRooms, + }); }; private onSearchClick = () => { diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index 00ebfdacce0..cd133522ef2 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021-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. @@ -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 { getDisplayAliasForRoom } from "../../utils/rooms"; interface IProps { space: Room; diff --git a/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx b/src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx index 3f33a400cc8..0799deecdca 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 { getDisplayAliasForRoom } from "../../../../utils/rooms"; const MAX_NAME_LENGTH = 80; const MAX_TOPIC_LENGTH = 800; diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index dfec2ab5097..3e76bb0e567 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -98,7 +98,7 @@ const MAX_RECENT_SEARCHES = 10; const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons const AVATAR_SIZE = 24; -interface IProps extends IDialogProps { +interface SpotlightProps { initialText?: string; initialFilter?: Filter; } @@ -287,7 +287,9 @@ interface IDirectoryOpts { query: string; } -const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = null, onFinished }) => { +const SpotlightDialog: React.FC = ( + { initialText = "", initialFilter = null, onFinished }, +) => { const inputRef = useRef(); const scrollContainerRef = useRef(); const cli = MatrixClientPeg.get(); @@ -1265,7 +1267,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n ; }; -const RovingSpotlightDialog: React.FC = (props) => { +const RovingSpotlightDialog: React.FC = (props) => { return { () => } ; diff --git a/src/components/views/directory/NetworkDropdown.tsx b/src/components/views/directory/NetworkDropdown.tsx index 701e5e39b98..a54bca8a861 100644 --- a/src/components/views/directory/NetworkDropdown.tsx +++ b/src/components/views/directory/NetworkDropdown.tsx @@ -25,11 +25,11 @@ import Modal from "../../../Modal"; import SdkConfig from "../../../SdkConfig"; import { SettingLevel } from "../../../settings/SettingLevel"; import SettingsStore from "../../../settings/SettingsStore"; -import { Protocols } from "../../../utils/DirectoryUtils"; import { GenericDropdownMenu, GenericDropdownMenuItem } from "../../structures/GenericDropdownMenu"; import TextInputDialog from "../dialogs/TextInputDialog"; import AccessibleButton from "../elements/AccessibleButton"; import withValidation from "../elements/Validation"; +import { Protocols } from "../../../utils/rooms"; const SETTING_NAME = "room_directory_servers"; 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/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 0cd38f7b30d..61bcbf93633 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -1,5 +1,5 @@ /* -Copyright 2015-2018, 2020, 2021 The Matrix.org Foundation C.I.C. +Copyright 2015-2018, 2020-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. @@ -22,8 +22,10 @@ import { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../.. import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { Action } from "../../../dispatcher/actions"; +import dis from "../../../dispatcher/dispatcher"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import { ActionPayload } from "../../../dispatcher/payloads"; +import { OpenSpotlightPayload } from "../../../dispatcher/payloads/OpenSpotlightPayload"; import { ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; @@ -58,6 +60,7 @@ import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList, } from "../context_menus/IconizedContextMenu"; +import { Filter } from "../dialogs/spotlight/SpotlightDialog"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import ExtraTile from "./ExtraTile"; import RoomSublist, { IAuxButtonProps } from "./RoomSublist"; @@ -319,7 +322,10 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => { e.stopPropagation(); closeMenu(); PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuExploreRoomsItem", e); - defaultDispatcher.fire(Action.ViewRoomDirectory); + dis.dispatch({ + action: Action.OpenSpotlight, + initialFilter: Filter.PublicRooms, + }); }} /> ; diff --git a/src/components/views/rooms/RoomListHeader.tsx b/src/components/views/rooms/RoomListHeader.tsx index f783e628f30..01480e7b1a9 100644 --- a/src/components/views/rooms/RoomListHeader.tsx +++ b/src/components/views/rooms/RoomListHeader.tsx @@ -22,7 +22,9 @@ import React, { useContext, useEffect, useState } from "react"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { Action } from "../../../dispatcher/actions"; +import dis from "../../../dispatcher/dispatcher"; import defaultDispatcher from "../../../dispatcher/dispatcher"; +import { OpenSpotlightPayload } from "../../../dispatcher/payloads/OpenSpotlightPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { useDispatcher } from "../../../hooks/useDispatcher"; import { useEventEmitterState, useTypedEventEmitter, useTypedEventEmitterState } from "../../../hooks/useEventEmitter"; @@ -52,6 +54,7 @@ import IconizedContextMenu, { IconizedContextMenuOptionList, } from "../context_menus/IconizedContextMenu"; import SpaceContextMenu from "../context_menus/SpaceContextMenu"; +import { Filter } from "../dialogs/spotlight/SpotlightDialog"; import InlineSpinner from "../elements/InlineSpinner"; import TooltipTarget from "../elements/TooltipTarget"; import { HomeButtonContextMenu } from "../spaces/SpacePanel"; @@ -332,8 +335,11 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - defaultDispatcher.dispatch({ action: Action.ViewRoomDirectory }); PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuExploreRoomsItem", e); + dis.dispatch({ + action: Action.OpenSpotlight, + initialFilter: Filter.PublicRooms, + }); closePlusMenu(); }} /> diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 2b2e443e81d..64a34abf120 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -46,9 +46,9 @@ export enum Action { ViewUserDeviceSettings = "view_user_device_settings", /** - * Opens the room directory. No additional payload information required. + * Opens the spotlight search. Can be used with a OpenSpotlightPayload */ - ViewRoomDirectory = "view_room_directory", + OpenSpotlight = "open_spotlight", /** * Fires when viewing room by room_alias fails to find room diff --git a/src/dispatcher/payloads/OpenSpotlightPayload.ts b/src/dispatcher/payloads/OpenSpotlightPayload.ts new file mode 100644 index 00000000000..7e006414126 --- /dev/null +++ b/src/dispatcher/payloads/OpenSpotlightPayload.ts @@ -0,0 +1,25 @@ +/* +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 { Filter } from "../../components/views/dialogs/spotlight/SpotlightDialog"; +import { Action } from "../actions"; +import { ActionPayload } from "../payloads"; + +export interface OpenSpotlightPayload extends ActionPayload { + action: Action.OpenSpotlight; + initialText?: string; + initialFilter?: Filter; +} diff --git a/src/hooks/usePublicRoomDirectory.ts b/src/hooks/usePublicRoomDirectory.ts index de848398d02..af8bb7a0a5d 100644 --- a/src/hooks/usePublicRoomDirectory.ts +++ b/src/hooks/usePublicRoomDirectory.ts @@ -23,7 +23,7 @@ import { IPublicRoomDirectoryConfig } from "../components/views/directory/Networ import { MatrixClientPeg } from "../MatrixClientPeg"; import SdkConfig from "../SdkConfig"; import SettingsStore from "../settings/SettingsStore"; -import { Protocols } from "../utils/DirectoryUtils"; +import { Protocols } from "../utils/rooms"; import { useLatestResult } from "./useLatestResult"; export const ALL_ROOMS = "ALL_ROOMS"; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f322c5de8d8..5f2d98d27eb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -738,13 +738,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", @@ -1908,8 +1901,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", @@ -2978,11 +2969,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", @@ -3281,20 +3274,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.", "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", "Your message wasn't sent because this homeserver has been blocked by its administrator. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has been blocked by its administrator. Please contact your service administrator to continue using the service.", diff --git a/src/utils/DirectoryUtils.ts b/src/utils/DirectoryUtils.ts deleted file mode 100644 index 429c54dd4fb..00000000000 --- a/src/utils/DirectoryUtils.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2018, 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 { 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"; - -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 (!protocols[proto].instances && protocols[proto].instances instanceof Array) 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 (!protocols[proto].instances && protocols[proto].instances instanceof Array) 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..190bf17db88 100644 --- a/src/utils/rooms.ts +++ b/src/utils/rooms.ts @@ -14,18 +14,12 @@ 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 { IProtocol, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client"; -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"; +import { getE2EEWellKnown } from "./WellKnownUtils"; + +export type Protocols = Record; export function privateShouldBeEncrypted(): boolean { const e2eeWellKnown = getE2EEWellKnown(); @@ -36,145 +30,8 @@ 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; +// 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); }