Consolidate public room search experience (#9605)
This commit is contained in:
parent
041bb46284
commit
40cbee60db
18 changed files with 388 additions and 1348 deletions
|
@ -88,15 +88,16 @@ describe("Room Directory", () => {
|
||||||
|
|
||||||
cy.get('[role="button"][aria-label="Explore rooms"]').click();
|
cy.get('[role="button"][aria-label="Explore rooms"]').click();
|
||||||
|
|
||||||
cy.get('.mx_RoomDirectory_dialogWrapper [name="dirsearch"]').type("Unknown Room");
|
cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("Unknown Room");
|
||||||
cy.get(".mx_RoomDirectory_dialogWrapper h5").should("contain", 'No results for "Unknown Room"');
|
cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_otherSearches_messageSearchText")
|
||||||
cy.get(".mx_RoomDirectory_dialogWrapper").percySnapshotElement("Room Directory - filtered no results");
|
.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_RoomDirectory_dialogWrapper [name="dirsearch"]').type("{selectAll}{backspace}test1234");
|
cy.get('.mx_SpotlightDialog [aria-label="Search"]').type("{selectAll}{backspace}test1234");
|
||||||
cy.contains(".mx_RoomDirectory_dialogWrapper .mx_RoomDirectory_listItem", name)
|
cy.contains(".mx_SpotlightDialog .mx_SpotlightDialog_result_publicRoomName", name)
|
||||||
.should("exist").as("resultRow");
|
.should("exist");
|
||||||
cy.get(".mx_RoomDirectory_dialogWrapper").percySnapshotElement("Room Directory - filtered one result");
|
cy.get(".mx_SpotlightDialog_wrapper").percySnapshotElement("Room Directory - filtered one result");
|
||||||
cy.get("@resultRow").find(".mx_AccessibleButton").contains("Join").click();
|
cy.get(".mx_SpotlightDialog .mx_SpotlightDialog_option").find(".mx_AccessibleButton").contains("Join").click();
|
||||||
|
|
||||||
cy.url().should('contain', `/#/room/#test1234:localhost`);
|
cy.url().should('contain', `/#/room/#test1234:localhost`);
|
||||||
});
|
});
|
||||||
|
|
|
@ -64,7 +64,6 @@
|
||||||
@import "./structures/_NotificationPanel.pcss";
|
@import "./structures/_NotificationPanel.pcss";
|
||||||
@import "./structures/_QuickSettingsButton.pcss";
|
@import "./structures/_QuickSettingsButton.pcss";
|
||||||
@import "./structures/_RightPanel.pcss";
|
@import "./structures/_RightPanel.pcss";
|
||||||
@import "./structures/_RoomDirectory.pcss";
|
|
||||||
@import "./structures/_RoomSearch.pcss";
|
@import "./structures/_RoomSearch.pcss";
|
||||||
@import "./structures/_RoomStatusBar.pcss";
|
@import "./structures/_RoomStatusBar.pcss";
|
||||||
@import "./structures/_RoomView.pcss";
|
@import "./structures/_RoomView.pcss";
|
||||||
|
@ -171,7 +170,6 @@
|
||||||
@import "./views/elements/_CopyableText.pcss";
|
@import "./views/elements/_CopyableText.pcss";
|
||||||
@import "./views/elements/_DesktopCapturerSourcePicker.pcss";
|
@import "./views/elements/_DesktopCapturerSourcePicker.pcss";
|
||||||
@import "./views/elements/_DialPadBackspaceButton.pcss";
|
@import "./views/elements/_DialPadBackspaceButton.pcss";
|
||||||
@import "./views/elements/_DirectorySearchBox.pcss";
|
|
||||||
@import "./views/elements/_Dropdown.pcss";
|
@import "./views/elements/_Dropdown.pcss";
|
||||||
@import "./views/elements/_EditableItemList.pcss";
|
@import "./views/elements/_EditableItemList.pcss";
|
||||||
@import "./views/elements/_ErrorBoundary.pcss";
|
@import "./views/elements/_ErrorBoundary.pcss";
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -29,7 +29,7 @@ import AliasCustomisations from './customisations/Alias';
|
||||||
* @param {Object} room The room object
|
* @param {Object} room The room object
|
||||||
* @returns {string} A display alias for the given room
|
* @returns {string} A display alias for the given room
|
||||||
*/
|
*/
|
||||||
export function getDisplayAliasForRoom(room: Room): string {
|
export function getDisplayAliasForRoom(room: Room): string | undefined {
|
||||||
return getDisplayAliasForAliasSet(
|
return getDisplayAliasForAliasSet(
|
||||||
room.getCanonicalAlias(), room.getAltAliases(),
|
room.getCanonicalAlias(), room.getAltAliases(),
|
||||||
);
|
);
|
||||||
|
@ -41,7 +41,7 @@ export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: s
|
||||||
if (AliasCustomisations.getDisplayAliasForAliasSet) {
|
if (AliasCustomisations.getDisplayAliasForAliasSet) {
|
||||||
return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases);
|
return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases);
|
||||||
}
|
}
|
||||||
return canonicalAlias || altAliases?.[0];
|
return (canonicalAlias || altAliases?.[0]) ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void> {
|
export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void> {
|
||||||
|
|
|
@ -95,7 +95,6 @@ import Spinner from "../views/elements/Spinner";
|
||||||
import QuestionDialog from "../views/dialogs/QuestionDialog";
|
import QuestionDialog from "../views/dialogs/QuestionDialog";
|
||||||
import UserSettingsDialog from '../views/dialogs/UserSettingsDialog';
|
import UserSettingsDialog from '../views/dialogs/UserSettingsDialog';
|
||||||
import CreateRoomDialog from '../views/dialogs/CreateRoomDialog';
|
import CreateRoomDialog from '../views/dialogs/CreateRoomDialog';
|
||||||
import RoomDirectory from './RoomDirectory';
|
|
||||||
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
|
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
|
||||||
import IncomingSasDialog from "../views/dialogs/IncomingSasDialog";
|
import IncomingSasDialog from "../views/dialogs/IncomingSasDialog";
|
||||||
import CompleteSecurity from "./auth/CompleteSecurity";
|
import CompleteSecurity from "./auth/CompleteSecurity";
|
||||||
|
@ -141,6 +140,7 @@ import { viewUserDeviceSettings } from '../../actions/handlers/viewUserDeviceSet
|
||||||
import { VoiceBroadcastResumer } from '../../voice-broadcast';
|
import { VoiceBroadcastResumer } from '../../voice-broadcast';
|
||||||
import GenericToast from "../views/toasts/GenericToast";
|
import GenericToast from "../views/toasts/GenericToast";
|
||||||
import { Linkify } from "../views/elements/Linkify";
|
import { Linkify } from "../views/elements/Linkify";
|
||||||
|
import RovingSpotlightDialog, { Filter } from '../views/dialogs/spotlight/SpotlightDialog';
|
||||||
|
|
||||||
// legacy export
|
// legacy export
|
||||||
export { default as Views } from "../../Views";
|
export { default as Views } from "../../Views";
|
||||||
|
@ -716,9 +716,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.viewSomethingBehindModal();
|
this.viewSomethingBehindModal();
|
||||||
break;
|
break;
|
||||||
case Action.ViewRoomDirectory: {
|
case Action.ViewRoomDirectory: {
|
||||||
Modal.createDialog(RoomDirectory, {
|
Modal.createDialog(RovingSpotlightDialog, {
|
||||||
initialText: payload.initialText,
|
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
|
// View the welcome or home page if we need something to look at
|
||||||
this.viewSomethingBehindModal();
|
this.viewSomethingBehindModal();
|
||||||
|
|
|
@ -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<IProps, IState> {
|
|
||||||
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<boolean> {
|
|
||||||
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 = <Spinner />;
|
|
||||||
} else {
|
|
||||||
const cells = (this.state.publicRooms || [])
|
|
||||||
.map(room =>
|
|
||||||
<PublicRoomTile
|
|
||||||
key={room.room_id}
|
|
||||||
room={room}
|
|
||||||
showRoom={this.onRoomClick}
|
|
||||||
removeFromDirectory={this.removeFromDirectory}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
// 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 = <Spinner />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const createNewButton = <>
|
|
||||||
<hr />
|
|
||||||
<AccessibleButton kind="primary" onClick={this.onCreateRoomClick} className="mx_RoomDirectory_newRoom">
|
|
||||||
{ _t("Create new room") }
|
|
||||||
</AccessibleButton>
|
|
||||||
</>;
|
|
||||||
|
|
||||||
let scrollPanelContent;
|
|
||||||
let footer;
|
|
||||||
if (cells.length === 0 && !this.state.loading) {
|
|
||||||
footer = <>
|
|
||||||
<h5>{ _t('No results for "%(query)s"', { query: this.state.filterString.trim() }) }</h5>
|
|
||||||
<p>
|
|
||||||
{ _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.") }
|
|
||||||
</p>
|
|
||||||
{ createNewButton }
|
|
||||||
</>;
|
|
||||||
} else {
|
|
||||||
scrollPanelContent = <div className="mx_RoomDirectory_table">
|
|
||||||
{ cells }
|
|
||||||
</div>;
|
|
||||||
if (!this.state.loading && !this.nextBatch) {
|
|
||||||
footer = createNewButton;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
content = <ScrollPanel
|
|
||||||
className="mx_RoomDirectory_tableWrapper"
|
|
||||||
onFillRequest={this.onFillRequest}
|
|
||||||
stickyBottom={false}
|
|
||||||
startAtBottom={false}
|
|
||||||
>
|
|
||||||
{ scrollPanelContent }
|
|
||||||
{ spinner }
|
|
||||||
{ footer && <div className="mx_RoomDirectory_footer">
|
|
||||||
{ footer }
|
|
||||||
</div> }
|
|
||||||
</ScrollPanel>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = <div className="mx_RoomDirectory_listheader">
|
|
||||||
<DirectorySearchBox
|
|
||||||
className="mx_RoomDirectory_searchbox"
|
|
||||||
onChange={this.onFilterChange}
|
|
||||||
onClear={this.onFilterClear}
|
|
||||||
onJoinClick={this.onJoinFromSearchClick}
|
|
||||||
placeholder={placeholder}
|
|
||||||
showJoinButton={showJoinButton}
|
|
||||||
initialText={this.props.initialText}
|
|
||||||
/>
|
|
||||||
<NetworkDropdown
|
|
||||||
protocols={this.protocols}
|
|
||||||
config={this.state.serverConfig}
|
|
||||||
setConfig={this.onOptionChange}
|
|
||||||
/>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
const explanation =
|
|
||||||
_t("If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.", {},
|
|
||||||
{ a: sub => (
|
|
||||||
<AccessibleButton kind="link_inline" onClick={this.onCreateRoomClick}>
|
|
||||||
{ sub }
|
|
||||||
</AccessibleButton>
|
|
||||||
) },
|
|
||||||
);
|
|
||||||
|
|
||||||
const title = _t("Explore rooms");
|
|
||||||
return (
|
|
||||||
<BaseDialog
|
|
||||||
className="mx_RoomDirectory_dialog"
|
|
||||||
hasCancel={true}
|
|
||||||
onFinished={this.onFinished}
|
|
||||||
title={title}
|
|
||||||
screenName="RoomDirectory"
|
|
||||||
>
|
|
||||||
<div className="mx_RoomDirectory">
|
|
||||||
{ explanation }
|
|
||||||
<div className="mx_RoomDirectory_list">
|
|
||||||
{ listHeader }
|
|
||||||
{ content }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
|
@ -55,7 +55,6 @@ import { linkifyElement, topicToHtml } from "../../HtmlUtils";
|
||||||
import { useDispatcher } from "../../hooks/useDispatcher";
|
import { useDispatcher } from "../../hooks/useDispatcher";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex";
|
import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex";
|
||||||
import { getDisplayAliasForRoom } from "./RoomDirectory";
|
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
|
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
|
||||||
import { IOOBData } from "../../stores/ThreepidInviteStore";
|
import { IOOBData } from "../../stores/ThreepidInviteStore";
|
||||||
|
@ -67,6 +66,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||||
import { Alignment } from "../views/elements/Tooltip";
|
import { Alignment } from "../views/elements/Tooltip";
|
||||||
import { getTopic } from "../../hooks/room/useTopic";
|
import { getTopic } from "../../hooks/room/useTopic";
|
||||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||||
|
import { getDisplayAliasForAliasSet } from "../../Rooms";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
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<ViewRoomPayload>({
|
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||||
action: Action.ViewRoom,
|
action: Action.ViewRoom,
|
||||||
should_peek: true,
|
should_peek: true,
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { IPublicRoomsChunkRoom } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { linkifyAndSanitizeHtml } from "../../../../HtmlUtils";
|
import { linkifyAndSanitizeHtml } from "../../../../HtmlUtils";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import { getDisplayAliasForRoom } from "../../../structures/RoomDirectory";
|
import { getDisplayAliasForAliasSet } from "../../../../Rooms";
|
||||||
|
|
||||||
const MAX_NAME_LENGTH = 80;
|
const MAX_NAME_LENGTH = 80;
|
||||||
const MAX_TOPIC_LENGTH = 800;
|
const MAX_TOPIC_LENGTH = 800;
|
||||||
|
@ -32,7 +32,9 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsId }: Props): JSX.Element {
|
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) {
|
if (name.length > MAX_NAME_LENGTH) {
|
||||||
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
|
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<IProps, IState> {
|
|
||||||
private input = createRef<HTMLInputElement>();
|
|
||||||
|
|
||||||
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<HTMLInputElement>): 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 = <AccessibleButton className="mx_DirectorySearchBox_joinButton"
|
|
||||||
onClick={this.onJoinButtonClick}
|
|
||||||
>{ _t("Join") }</AccessibleButton>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className={`mx_DirectorySearchBox ${this.props.className} mx_textinput`}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="dirsearch"
|
|
||||||
value={this.state.value}
|
|
||||||
className="mx_textinput_icon mx_textinput_search"
|
|
||||||
ref={this.input}
|
|
||||||
onChange={this.onChange}
|
|
||||||
onKeyUp={this.onKeyUp}
|
|
||||||
placeholder={this.props.placeholder}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
{ joinButton }
|
|
||||||
<AccessibleButton className="mx_DirectorySearchBox_clear" onClick={this.onClearClick} />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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<string | null>(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 = (
|
|
||||||
<AccessibleButton kind="secondary" onClick={onPreviewClick}>
|
|
||||||
{ _t("Preview") }
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (hasJoinedRoom) {
|
|
||||||
joinOrViewButton = (
|
|
||||||
<AccessibleButton kind="secondary" onClick={onViewClick}>
|
|
||||||
{ _t("View") }
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
} else if (!isGuest) {
|
|
||||||
joinOrViewButton = (
|
|
||||||
<AccessibleButton kind="primary" onClick={onJoinClick}>
|
|
||||||
{ _t("Join") }
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div
|
|
||||||
role="listitem"
|
|
||||||
className="mx_RoomDirectory_listItem"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onMouseDown={onRoomClicked}
|
|
||||||
className="mx_RoomDirectory_roomAvatar"
|
|
||||||
>
|
|
||||||
<BaseAvatar
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
resizeMethod='crop'
|
|
||||||
name={name}
|
|
||||||
idName={name}
|
|
||||||
url={avatarUrl}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
onMouseDown={onRoomClicked}
|
|
||||||
className="mx_RoomDirectory_roomDescription"
|
|
||||||
>
|
|
||||||
<div className="mx_RoomDirectory_name">
|
|
||||||
{ name }
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="mx_RoomDirectory_topic"
|
|
||||||
dangerouslySetInnerHTML={{ __html: topic }}
|
|
||||||
/>
|
|
||||||
<div className="mx_RoomDirectory_alias">
|
|
||||||
{ getDisplayAliasForRoom(room) }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
onMouseDown={onRoomClicked}
|
|
||||||
className="mx_RoomDirectory_roomMemberCount"
|
|
||||||
>
|
|
||||||
{ room.num_joined_members }
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
onMouseDown={onRoomClicked}
|
|
||||||
className="mx_RoomDirectory_preview"
|
|
||||||
>
|
|
||||||
{ previewButton }
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
onMouseDown={onRoomClicked}
|
|
||||||
className="mx_RoomDirectory_join"
|
|
||||||
>
|
|
||||||
{ joinOrViewButton }
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
};
|
|
|
@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string {
|
function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string { // E.g. prefer one of the aliases over another
|
||||||
// E.g. prefer one of the aliases over another
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -745,13 +745,6 @@
|
||||||
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
|
"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",
|
"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",
|
"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",
|
"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.",
|
"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",
|
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
|
||||||
|
@ -1936,8 +1929,6 @@
|
||||||
"Idle": "Idle",
|
"Idle": "Idle",
|
||||||
"Offline": "Offline",
|
"Offline": "Offline",
|
||||||
"Unknown": "Unknown",
|
"Unknown": "Unknown",
|
||||||
"Preview": "Preview",
|
|
||||||
"View": "View",
|
|
||||||
"%(members)s and more": "%(members)s and more",
|
"%(members)s and more": "%(members)s and more",
|
||||||
"%(members)s and %(last)s": "%(members)s and %(last)s",
|
"%(members)s and %(last)s": "%(members)s and %(last)s",
|
||||||
"Seen by %(count)s people|other": "Seen by %(count)s people",
|
"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",
|
"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:",
|
"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",
|
"Remember this": "Remember this",
|
||||||
|
"Unnamed room": "Unnamed room",
|
||||||
"%(count)s Members|other": "%(count)s Members",
|
"%(count)s Members|other": "%(count)s Members",
|
||||||
"%(count)s Members|one": "%(count)s Member",
|
"%(count)s Members|one": "%(count)s Member",
|
||||||
"Public rooms": "Public rooms",
|
"Public rooms": "Public rooms",
|
||||||
"Use \"%(query)s\" to search": "Use \"%(query)s\" to search",
|
"Use \"%(query)s\" to search": "Use \"%(query)s\" to search",
|
||||||
"Search for": "Search for",
|
"Search for": "Search for",
|
||||||
|
"View": "View",
|
||||||
"Spaces you're in": "Spaces you're in",
|
"Spaces you're in": "Spaces you're in",
|
||||||
"Show rooms": "Show rooms",
|
"Show rooms": "Show rooms",
|
||||||
"Show spaces": "Show spaces",
|
"Show spaces": "Show spaces",
|
||||||
|
@ -3311,20 +3304,6 @@
|
||||||
"%(creator)s created and configured the room.": "%(creator)s created and configured the room.",
|
"%(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're all caught up": "You're all caught up",
|
||||||
"You have no visible notifications.": "You have no visible notifications.",
|
"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 <a>create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.",
|
|
||||||
"Search failed": "Search failed",
|
"Search failed": "Search failed",
|
||||||
"Server may be unavailable, overloaded, or search timed out :(": "Server may be unavailable, overloaded, or search timed out :(",
|
"Server may be unavailable, overloaded, or search timed out :(": "Server may be unavailable, overloaded, or search timed out :(",
|
||||||
"No more results": "No more results",
|
"No more results": "No more results",
|
||||||
|
|
|
@ -14,35 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IInstance, IProtocol } from "matrix-js-sdk/src/client";
|
import { 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<string, IProtocol>;
|
export type Protocols = Record<string, IProtocol>;
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,18 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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 { 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 {
|
export function privateShouldBeEncrypted(): boolean {
|
||||||
const e2eeWellKnown = getE2EEWellKnown();
|
const e2eeWellKnown = getE2EEWellKnown();
|
||||||
|
@ -35,146 +24,3 @@ export function privateShouldBeEncrypted(): boolean {
|
||||||
}
|
}
|
||||||
return true;
|
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;
|
|
||||||
}
|
|
||||||
|
|
70
test/components/structures/SpaceHierarchy-test.tsx
Normal file
70
test/components/structures/SpaceHierarchy-test.tsx
Normal file
|
@ -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",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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(<PublicRoomResultDetails
|
||||||
|
room={{
|
||||||
|
room_id: "room-id",
|
||||||
|
name: "hello?",
|
||||||
|
canonical_alias: "canonical-alias",
|
||||||
|
world_readable: true,
|
||||||
|
guest_can_join: false,
|
||||||
|
num_joined_members: 666,
|
||||||
|
}}
|
||||||
|
labelId="label-id"
|
||||||
|
descriptionId="description-id"
|
||||||
|
detailsId="details-id"
|
||||||
|
/>);
|
||||||
|
|
||||||
|
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<IPublicRoomsChunkRoom>) => {
|
||||||
|
const roomChunk: IPublicRoomsChunkRoom = {
|
||||||
|
room_id: "room-id",
|
||||||
|
world_readable: true,
|
||||||
|
guest_can_join: false,
|
||||||
|
num_joined_members: 666,
|
||||||
|
...partialPublicRoomChunk,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { asFragment } = render(<PublicRoomResultDetails
|
||||||
|
room={roomChunk}
|
||||||
|
labelId="label-id"
|
||||||
|
descriptionId="description-id"
|
||||||
|
detailsId="details-id"
|
||||||
|
/>);
|
||||||
|
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,223 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`PublicRoomResultDetails Public room results 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomName"
|
||||||
|
id="label-id"
|
||||||
|
>
|
||||||
|
canonical-alias
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||||
|
id="description-id"
|
||||||
|
>
|
||||||
|
canonical-alias
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||||
|
id="details-id"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||||
|
>
|
||||||
|
666 Members
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PublicRoomResultDetails Public room results 2`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomName"
|
||||||
|
id="label-id"
|
||||||
|
>
|
||||||
|
alias-from-aliases
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||||
|
id="description-id"
|
||||||
|
>
|
||||||
|
room-id
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||||
|
id="details-id"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||||
|
>
|
||||||
|
666 Members
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PublicRoomResultDetails Public room results 3`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomName"
|
||||||
|
id="label-id"
|
||||||
|
>
|
||||||
|
name over alias
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||||
|
id="description-id"
|
||||||
|
>
|
||||||
|
canonical-alias
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||||
|
id="details-id"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||||
|
>
|
||||||
|
666 Members
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PublicRoomResultDetails Public room results 4`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomName"
|
||||||
|
id="label-id"
|
||||||
|
>
|
||||||
|
with an overly long name that will be truncated for sure, you can't say anything...
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||||
|
id="description-id"
|
||||||
|
>
|
||||||
|
room-id
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||||
|
id="details-id"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||||
|
>
|
||||||
|
666 Members
|
||||||
|
</span>
|
||||||
|
·
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomTopic"
|
||||||
|
>
|
||||||
|
with a topic!
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PublicRoomResultDetails Public room results 5`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomName"
|
||||||
|
id="label-id"
|
||||||
|
>
|
||||||
|
Unnamed room
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||||
|
id="description-id"
|
||||||
|
>
|
||||||
|
room-id
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||||
|
id="details-id"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||||
|
>
|
||||||
|
666 Members
|
||||||
|
</span>
|
||||||
|
·
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomTopic"
|
||||||
|
>
|
||||||
|
Very long topic aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PublicRoomResultDetails renders 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomName"
|
||||||
|
id="label-id"
|
||||||
|
>
|
||||||
|
hello?
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||||
|
id="description-id"
|
||||||
|
>
|
||||||
|
canonical-alias
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||||
|
id="details-id"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||||
|
>
|
||||||
|
666 Members
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
Loading…
Add table
Add a link
Reference in a new issue