From 663bca559f7cd1e1b7cb44255e0c9c2f0b788010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 24 Jun 2022 17:08:00 +0200 Subject: [PATCH] Implement MSC3827: Filtering of `/publicRooms` by room type (#8866) --- res/css/views/dialogs/_SpotlightDialog.scss | 5 ++ .../dialogs/spotlight/SpotlightDialog.tsx | 56 +++++++++++++++---- .../tabs/user/LabsUserSettingsTab.tsx | 22 +++++++- .../views/spaces/SpaceCreateMenu.tsx | 11 ++-- .../spaces/SpaceSettingsVisibilityTab.tsx | 6 +- src/hooks/usePublicRoomDirectory.ts | 8 ++- src/i18n/strings/en_EN.json | 4 ++ src/settings/Settings.tsx | 5 ++ 8 files changed, 96 insertions(+), 21 deletions(-) diff --git a/res/css/views/dialogs/_SpotlightDialog.scss b/res/css/views/dialogs/_SpotlightDialog.scss index b3446b068cf..a803d3473b7 100644 --- a/res/css/views/dialogs/_SpotlightDialog.scss +++ b/res/css/views/dialogs/_SpotlightDialog.scss @@ -168,6 +168,11 @@ limitations under the License. justify-content: space-between; align-items: center; margin-bottom: $spacing-8; + + .mx_SpotlightDialog_options { + display: flex; + gap: $spacing-4; + } } & + .mx_SpotlightDialog_section { diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index 679f611696d..cfe97ef7101 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -18,7 +18,7 @@ import classNames from "classnames"; import { capitalize, sum } from "lodash"; import { WebSearch as WebSearchEvent } from "@matrix-org/analytics-events/types/typescript/WebSearch"; import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces"; -import { IPublicRoomsChunkRoom, MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix"; +import { IPublicRoomsChunkRoom, MatrixClient, RoomMember, RoomType } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/models/room"; import { normalize } from "matrix-js-sdk/src/utils"; import React, { @@ -89,6 +89,8 @@ import { Option } from "./Option"; import { PublicRoomResultDetails } from "./PublicRoomResultDetails"; import { RoomResultDetails } from "./RoomResultDetails"; import { TooltipOption } from "./TooltipOption"; +import LabelledCheckbox from "../../elements/LabelledCheckbox"; +import { useFeatureEnabled } from "../../../../hooks/useSettings"; const MAX_RECENT_SEARCHES = 10; const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons @@ -103,6 +105,18 @@ function refIsForRecentlyViewed(ref: RefObject): boolean { return ref.current?.id?.startsWith("mx_SpotlightDialog_button_recentlyViewed_") === true; } +function getRoomTypes(showRooms: boolean, showSpaces: boolean): Set | null { + const roomTypes = new Set(); + + // This is what servers not implementing MSC3827 are expecting + if (showRooms && !showSpaces) return null; + + if (showRooms) roomTypes.add(null); + if (showSpaces) roomTypes.add(RoomType.Space); + + return roomTypes; +} + enum Section { People, Rooms, @@ -277,14 +291,19 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n const [inviteLinkCopied, setInviteLinkCopied] = useState(false); const trimmedQuery = useMemo(() => query.trim(), [query]); + const exploringPublicSpacesEnabled = useFeatureEnabled("feature_exploring_public_spaces"); + const { loading: publicRoomsLoading, publicRooms, protocols, config, setConfig, search: searchPublicRooms } = usePublicRoomDirectory(); + const [showRooms, setShowRooms] = useState(true); + const [showSpaces, setShowSpaces] = useState(false); const { loading: peopleLoading, users, search: searchPeople } = useUserDirectory(); const { loading: profileLoading, profile, search: searchProfileInfo } = useProfileInfo(); const searchParams: [IDirectoryOpts] = useMemo(() => ([{ query: trimmedQuery, + roomTypes: getRoomTypes(showRooms, showSpaces), limit: SECTION_LIMIT, - }]), [trimmedQuery]); + }]), [trimmedQuery, showRooms, showSpaces]); useDebouncedCallback( filter === Filter.PublicRooms, searchPublicRooms, @@ -624,15 +643,32 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n

{ _t("Suggestions") }

- -
-
- { results[Section.PublicRooms].slice(0, SECTION_LIMIT).map(resultMapper) } +
+ { exploringPublicSpacesEnabled && <> + + + } + +
+
{ (showRooms || showSpaces) + ? results[Section.PublicRooms].slice(0, SECTION_LIMIT).map(resultMapper) + :
+ { _t("You cannot search for rooms that are neither a room nor a space") } +
+ }
); } diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx index 14b27b0515e..94f65d9de2d 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx @@ -49,23 +49,31 @@ export class LabsSettingToggle extends React.Component interface IState { showHiddenReadReceipts: boolean; showJumpToDate: boolean; + showExploringPublicSpaces: boolean; } export default class LabsUserSettingsTab extends React.Component<{}, IState> { constructor(props: {}) { super(props); - MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc2285").then((showHiddenReadReceipts) => { + const cli = MatrixClientPeg.get(); + + cli.doesServerSupportUnstableFeature("org.matrix.msc2285").then((showHiddenReadReceipts) => { this.setState({ showHiddenReadReceipts }); }); - MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc3030").then((showJumpToDate) => { + cli.doesServerSupportUnstableFeature("org.matrix.msc3030").then((showJumpToDate) => { this.setState({ showJumpToDate }); }); + cli.doesServerSupportUnstableFeature("org.matrix.msc3827").then((showExploringPublicSpaces) => { + this.setState({ showExploringPublicSpaces }); + }); + this.state = { showHiddenReadReceipts: false, showJumpToDate: false, + showExploringPublicSpaces: false, }; } @@ -133,6 +141,16 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> { ); } + if (this.state.showExploringPublicSpaces) { + groups.getOrCreate(LabGroup.Spaces, []).push( + , + ); + } + labsSections = <> { sortBy(Array.from(groups.entries()), "0").map(([group, flags]) => (
diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 3d0af8f6fc3..5b93d5e001b 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -18,7 +18,7 @@ import React, { ComponentProps, RefObject, SyntheticEvent, KeyboardEvent, useCon import classNames from "classnames"; import { RoomType } from "matrix-js-sdk/src/@types/event"; import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests"; -import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials"; +import { HistoryVisibility, Preset, Visibility } from "matrix-js-sdk/src/@types/partials"; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../languageHandler"; @@ -37,6 +37,7 @@ import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialo import SettingsStore from "../../../settings/SettingsStore"; import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; export const createSpace = async ( name: string, @@ -51,6 +52,9 @@ export const createSpace = async ( createOpts: { name, preset: isPublic ? Preset.PublicChat : Preset.PrivateChat, + visibility: (isPublic && await MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc3827")) + ? Visibility.Public + : Visibility.Private, power_level_content_override: { // Only allow Admins to write to the timeline to prevent hidden sync spam events_default: 100, @@ -80,11 +84,6 @@ const SpaceCreateMenuType = ({ title, description, className, onClick }) => { ); }; -enum Visibility { - Public, - Private, -} - const spaceNameValidator = withValidation({ rules: [ { diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx index 690900c3b72..a59ad39f626 100644 --- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx +++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx @@ -29,6 +29,7 @@ import { useLocalEcho } from "../../../hooks/useLocalEcho"; import JoinRuleSettings from "../settings/JoinRuleSettings"; import { useRoomState } from "../../../hooks/useRoomState"; import SettingsFieldset from "../settings/SettingsFieldset"; +import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; interface IProps { matrixClient: MatrixClient; @@ -38,6 +39,9 @@ interface IProps { const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space, closeSettingsFn }: IProps) => { const [error, setError] = useState(""); + const serverSupportsExploringSpaces = useAsyncMemo(async () => { + return cli.doesServerSupportUnstableFeature("org.matrix.msc3827"); + }, [cli], false); const userId = cli.getUserId(); @@ -103,7 +107,7 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space, closeSettingsFn canSetCanonicalAlias={canSetCanonical} canSetAliases={true} canonicalAliasEvent={canonicalAliasEv} - hidePublishSetting={true} + hidePublishSetting={!serverSupportsExploringSpaces} /> ; } diff --git a/src/hooks/usePublicRoomDirectory.ts b/src/hooks/usePublicRoomDirectory.ts index fd0b7643725..895c448834f 100644 --- a/src/hooks/usePublicRoomDirectory.ts +++ b/src/hooks/usePublicRoomDirectory.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { RoomType } from "matrix-js-sdk/src/@types/event"; import { IRoomDirectoryOptions } from "matrix-js-sdk/src/@types/requests"; import { IProtocol, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client"; import { useCallback, useEffect, useState } from "react"; @@ -32,6 +33,7 @@ const LAST_INSTANCE_KEY = "mx_last_room_directory_instance"; export interface IPublicRoomsOpts { limit: number; query?: string; + roomTypes?: Set; } let thirdParty: Protocols; @@ -72,6 +74,7 @@ export const usePublicRoomDirectory = () => { const search = useCallback(async ({ limit = 20, query, + roomTypes, }: IPublicRoomsOpts): Promise => { const opts: IRoomDirectoryOptions = { limit }; @@ -85,9 +88,10 @@ export const usePublicRoomDirectory = () => { opts.third_party_instance_id = config.instanceId; } - if (query) { + if (query || roomTypes) { opts.filter = { - generic_search_term: query, + "generic_search_term": query, + "org.matrix.msc3827.room_types": roomTypes ? Array.from(roomTypes) : null, }; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4e0042bbe36..9982b505424 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -856,6 +856,7 @@ "Can I use text chat alongside the video call?": "Can I use text chat alongside the video call?", "Yes, the chat timeline is displayed alongside the video.": "Yes, the chat timeline is displayed alongside the video.", "Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.", + "Explore public spaces in the new search dialog": "Explore public spaces in the new search dialog", "Let moderators hide messages pending moderation.": "Let moderators hide messages pending moderation.", "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators", "Render LaTeX maths in messages": "Render LaTeX maths in messages", @@ -2803,6 +2804,9 @@ "Use \"%(query)s\" to search": "Use \"%(query)s\" to search", "Search for": "Search for", "Spaces you're in": "Spaces you're in", + "Show rooms": "Show rooms", + "Show spaces": "Show spaces", + "You cannot search for rooms that are neither a room nor a space": "You cannot search for rooms that are neither a room nor a space", "Other rooms in %(spaceName)s": "Other rooms in %(spaceName)s", "Join %(roomAddress)s": "Join %(roomAddress)s", "Some results may be hidden for privacy": "Some results may be hidden for privacy", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 202a5037bdd..000b832cb08 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -220,6 +220,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { requiresRefresh: true, }, }, + "feature_exploring_public_spaces": { + displayName: _td("Explore public spaces in the new search dialog"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_msc3531_hide_messages_pending_moderation": { isFeature: true, labsGroup: LabGroup.Moderation,