Implement MSC3827: Filtering of `/publicRooms` by room type (#8866)

pull/28788/head^2
Šimon Brandner 2022-06-24 17:08:00 +02:00 committed by GitHub
parent 18c21d77cd
commit 663bca559f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 96 additions and 21 deletions

View File

@ -168,6 +168,11 @@ limitations under the License.
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: $spacing-8; margin-bottom: $spacing-8;
.mx_SpotlightDialog_options {
display: flex;
gap: $spacing-4;
}
} }
& + .mx_SpotlightDialog_section { & + .mx_SpotlightDialog_section {

View File

@ -18,7 +18,7 @@ import classNames from "classnames";
import { capitalize, sum } from "lodash"; import { capitalize, sum } from "lodash";
import { WebSearch as WebSearchEvent } from "@matrix-org/analytics-events/types/typescript/WebSearch"; import { WebSearch as WebSearchEvent } from "@matrix-org/analytics-events/types/typescript/WebSearch";
import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces"; 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 { Room } from "matrix-js-sdk/src/models/room";
import { normalize } from "matrix-js-sdk/src/utils"; import { normalize } from "matrix-js-sdk/src/utils";
import React, { import React, {
@ -89,6 +89,8 @@ import { Option } from "./Option";
import { PublicRoomResultDetails } from "./PublicRoomResultDetails"; import { PublicRoomResultDetails } from "./PublicRoomResultDetails";
import { RoomResultDetails } from "./RoomResultDetails"; import { RoomResultDetails } from "./RoomResultDetails";
import { TooltipOption } from "./TooltipOption"; import { TooltipOption } from "./TooltipOption";
import LabelledCheckbox from "../../elements/LabelledCheckbox";
import { useFeatureEnabled } from "../../../../hooks/useSettings";
const MAX_RECENT_SEARCHES = 10; const MAX_RECENT_SEARCHES = 10;
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
@ -103,6 +105,18 @@ function refIsForRecentlyViewed(ref: RefObject<HTMLElement>): boolean {
return ref.current?.id?.startsWith("mx_SpotlightDialog_button_recentlyViewed_") === true; return ref.current?.id?.startsWith("mx_SpotlightDialog_button_recentlyViewed_") === true;
} }
function getRoomTypes(showRooms: boolean, showSpaces: boolean): Set<RoomType | null> | null {
const roomTypes = new Set<RoomType | null>();
// 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 { enum Section {
People, People,
Rooms, Rooms,
@ -277,14 +291,19 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
const [inviteLinkCopied, setInviteLinkCopied] = useState<boolean>(false); const [inviteLinkCopied, setInviteLinkCopied] = useState<boolean>(false);
const trimmedQuery = useMemo(() => query.trim(), [query]); const trimmedQuery = useMemo(() => query.trim(), [query]);
const exploringPublicSpacesEnabled = useFeatureEnabled("feature_exploring_public_spaces");
const { loading: publicRoomsLoading, publicRooms, protocols, config, setConfig, search: searchPublicRooms } = const { loading: publicRoomsLoading, publicRooms, protocols, config, setConfig, search: searchPublicRooms } =
usePublicRoomDirectory(); usePublicRoomDirectory();
const [showRooms, setShowRooms] = useState(true);
const [showSpaces, setShowSpaces] = useState(false);
const { loading: peopleLoading, users, search: searchPeople } = useUserDirectory(); const { loading: peopleLoading, users, search: searchPeople } = useUserDirectory();
const { loading: profileLoading, profile, search: searchProfileInfo } = useProfileInfo(); const { loading: profileLoading, profile, search: searchProfileInfo } = useProfileInfo();
const searchParams: [IDirectoryOpts] = useMemo(() => ([{ const searchParams: [IDirectoryOpts] = useMemo(() => ([{
query: trimmedQuery, query: trimmedQuery,
roomTypes: getRoomTypes(showRooms, showSpaces),
limit: SECTION_LIMIT, limit: SECTION_LIMIT,
}]), [trimmedQuery]); }]), [trimmedQuery, showRooms, showSpaces]);
useDebouncedCallback( useDebouncedCallback(
filter === Filter.PublicRooms, filter === Filter.PublicRooms,
searchPublicRooms, searchPublicRooms,
@ -624,15 +643,32 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_results" role="group"> <div className="mx_SpotlightDialog_section mx_SpotlightDialog_results" role="group">
<div className="mx_SpotlightDialog_sectionHeader"> <div className="mx_SpotlightDialog_sectionHeader">
<h4>{ _t("Suggestions") }</h4> <h4>{ _t("Suggestions") }</h4>
<NetworkDropdown <div className="mx_SpotlightDialog_options">
protocols={protocols} { exploringPublicSpacesEnabled && <>
config={config ?? null} <LabelledCheckbox
setConfig={setConfig} label={_t("Show rooms")}
/> value={showRooms}
</div> onChange={setShowRooms}
<div> />
{ results[Section.PublicRooms].slice(0, SECTION_LIMIT).map(resultMapper) } <LabelledCheckbox
label={_t("Show spaces")}
value={showSpaces}
onChange={setShowSpaces}
/>
</> }
<NetworkDropdown
protocols={protocols}
config={config ?? null}
setConfig={setConfig}
/>
</div>
</div> </div>
<div> { (showRooms || showSpaces)
? results[Section.PublicRooms].slice(0, SECTION_LIMIT).map(resultMapper)
: <div className="mx_SpotlightDialog_otherSearches_messageSearchText">
{ _t("You cannot search for rooms that are neither a room nor a space") }
</div>
} </div>
</div> </div>
); );
} }

View File

@ -49,23 +49,31 @@ export class LabsSettingToggle extends React.Component<ILabsSettingToggleProps>
interface IState { interface IState {
showHiddenReadReceipts: boolean; showHiddenReadReceipts: boolean;
showJumpToDate: boolean; showJumpToDate: boolean;
showExploringPublicSpaces: boolean;
} }
export default class LabsUserSettingsTab extends React.Component<{}, IState> { export default class LabsUserSettingsTab extends React.Component<{}, IState> {
constructor(props: {}) { constructor(props: {}) {
super(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 }); this.setState({ showHiddenReadReceipts });
}); });
MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc3030").then((showJumpToDate) => { cli.doesServerSupportUnstableFeature("org.matrix.msc3030").then((showJumpToDate) => {
this.setState({ showJumpToDate }); this.setState({ showJumpToDate });
}); });
cli.doesServerSupportUnstableFeature("org.matrix.msc3827").then((showExploringPublicSpaces) => {
this.setState({ showExploringPublicSpaces });
});
this.state = { this.state = {
showHiddenReadReceipts: false, showHiddenReadReceipts: false,
showJumpToDate: 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(
<SettingsFlag
key="feature_exploring_public_spaces"
name="feature_exploring_public_spaces"
level={SettingLevel.DEVICE}
/>,
);
}
labsSections = <> labsSections = <>
{ sortBy(Array.from(groups.entries()), "0").map(([group, flags]) => ( { sortBy(Array.from(groups.entries()), "0").map(([group, flags]) => (
<div className="mx_SettingsTab_section" key={group}> <div className="mx_SettingsTab_section" key={group}>

View File

@ -18,7 +18,7 @@ import React, { ComponentProps, RefObject, SyntheticEvent, KeyboardEvent, useCon
import classNames from "classnames"; import classNames from "classnames";
import { RoomType } from "matrix-js-sdk/src/@types/event"; import { RoomType } from "matrix-js-sdk/src/@types/event";
import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests"; 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 { logger } from "matrix-js-sdk/src/logger";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -37,6 +37,7 @@ import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialo
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
export const createSpace = async ( export const createSpace = async (
name: string, name: string,
@ -51,6 +52,9 @@ export const createSpace = async (
createOpts: { createOpts: {
name, name,
preset: isPublic ? Preset.PublicChat : Preset.PrivateChat, preset: isPublic ? Preset.PublicChat : Preset.PrivateChat,
visibility: (isPublic && await MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc3827"))
? Visibility.Public
: Visibility.Private,
power_level_content_override: { power_level_content_override: {
// Only allow Admins to write to the timeline to prevent hidden sync spam // Only allow Admins to write to the timeline to prevent hidden sync spam
events_default: 100, events_default: 100,
@ -80,11 +84,6 @@ const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
); );
}; };
enum Visibility {
Public,
Private,
}
const spaceNameValidator = withValidation({ const spaceNameValidator = withValidation({
rules: [ rules: [
{ {

View File

@ -29,6 +29,7 @@ import { useLocalEcho } from "../../../hooks/useLocalEcho";
import JoinRuleSettings from "../settings/JoinRuleSettings"; import JoinRuleSettings from "../settings/JoinRuleSettings";
import { useRoomState } from "../../../hooks/useRoomState"; import { useRoomState } from "../../../hooks/useRoomState";
import SettingsFieldset from "../settings/SettingsFieldset"; import SettingsFieldset from "../settings/SettingsFieldset";
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
interface IProps { interface IProps {
matrixClient: MatrixClient; matrixClient: MatrixClient;
@ -38,6 +39,9 @@ interface IProps {
const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space, closeSettingsFn }: IProps) => { const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space, closeSettingsFn }: IProps) => {
const [error, setError] = useState(""); const [error, setError] = useState("");
const serverSupportsExploringSpaces = useAsyncMemo<boolean>(async () => {
return cli.doesServerSupportUnstableFeature("org.matrix.msc3827");
}, [cli], false);
const userId = cli.getUserId(); const userId = cli.getUserId();
@ -103,7 +107,7 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space, closeSettingsFn
canSetCanonicalAlias={canSetCanonical} canSetCanonicalAlias={canSetCanonical}
canSetAliases={true} canSetAliases={true}
canonicalAliasEvent={canonicalAliasEv} canonicalAliasEvent={canonicalAliasEv}
hidePublishSetting={true} hidePublishSetting={!serverSupportsExploringSpaces}
/> />
</>; </>;
} }

View File

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { RoomType } from "matrix-js-sdk/src/@types/event";
import { IRoomDirectoryOptions } from "matrix-js-sdk/src/@types/requests"; import { IRoomDirectoryOptions } from "matrix-js-sdk/src/@types/requests";
import { IProtocol, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client"; import { IProtocol, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
@ -32,6 +33,7 @@ const LAST_INSTANCE_KEY = "mx_last_room_directory_instance";
export interface IPublicRoomsOpts { export interface IPublicRoomsOpts {
limit: number; limit: number;
query?: string; query?: string;
roomTypes?: Set<RoomType | null>;
} }
let thirdParty: Protocols; let thirdParty: Protocols;
@ -72,6 +74,7 @@ export const usePublicRoomDirectory = () => {
const search = useCallback(async ({ const search = useCallback(async ({
limit = 20, limit = 20,
query, query,
roomTypes,
}: IPublicRoomsOpts): Promise<boolean> => { }: IPublicRoomsOpts): Promise<boolean> => {
const opts: IRoomDirectoryOptions = { limit }; const opts: IRoomDirectoryOptions = { limit };
@ -85,9 +88,10 @@ export const usePublicRoomDirectory = () => {
opts.third_party_instance_id = config.instanceId; opts.third_party_instance_id = config.instanceId;
} }
if (query) { if (query || roomTypes) {
opts.filter = { opts.filter = {
generic_search_term: query, "generic_search_term": query,
"org.matrix.msc3827.room_types": roomTypes ? Array.from<RoomType | null>(roomTypes) : null,
}; };
} }

View File

@ -856,6 +856,7 @@
"Can I use text chat alongside the video call?": "Can I use text chat alongside the video call?", "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.", "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.", "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.", "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", "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", "Render LaTeX maths in messages": "Render LaTeX maths in messages",
@ -2803,6 +2804,9 @@
"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",
"Spaces you're in": "Spaces you're in", "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", "Other rooms in %(spaceName)s": "Other rooms in %(spaceName)s",
"Join %(roomAddress)s": "Join %(roomAddress)s", "Join %(roomAddress)s": "Join %(roomAddress)s",
"Some results may be hidden for privacy": "Some results may be hidden for privacy", "Some results may be hidden for privacy": "Some results may be hidden for privacy",

View File

@ -220,6 +220,11 @@ export const SETTINGS: {[setting: string]: ISetting} = {
requiresRefresh: true, 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": { "feature_msc3531_hide_messages_pending_moderation": {
isFeature: true, isFeature: true,
labsGroup: LabGroup.Moderation, labsGroup: LabGroup.Moderation,