mirror of https://github.com/vector-im/riot-web
Implement MSC3827: Filtering of `/publicRooms` by room type (#8866)
parent
18c21d77cd
commit
663bca559f
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue