@@ -91,14 +113,26 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
{ titleOverride || _t(title) }
-
{ _t(caption) }
+
{ caption() }
{ feedbackButton }
SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)}
+ onClick={async () => {
+ setBusy(true);
+ // make it look like we're doing something for two seconds,
+ // otherwise users think clicking did nothing
+ if (!requiresRefresh) {
+ await sleep(2000);
+ }
+ await SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value);
+ if (!requiresRefresh) {
+ setBusy(false);
+ }
+ }}
kind={feedbackButton ? "primary_outline" : "primary"}
+ disabled={busy}
>
- { value ? _t("Leave the beta") : _t("Join the beta") }
+ { content }
{ disclaimer &&
diff --git a/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx b/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx
index d68569b1265..17fab187d0a 100644
--- a/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx
+++ b/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx
@@ -52,8 +52,8 @@ const GenericFeatureFeedbackDialog: React.FC = ({
Modal.createTrackedDialog("Feedback Sent", rageshakeLabel, InfoDialog, {
title,
- description: _t("Thank you for your feedback, we really appreciate it."),
- button: _t("Done"),
+ description: _t("Feedback sent! Thanks, we appreciate it!"),
+ button: _t("Close"),
hasCloseButton: false,
fixedWidth: false,
});
@@ -68,7 +68,7 @@ const GenericFeatureFeedbackDialog: React.FC = ({
{ subheading }
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") }
-
+
{ children }
diff --git a/src/components/views/dialogs/SpotlightDialog.tsx b/src/components/views/dialogs/SpotlightDialog.tsx
index 6ec05124def..f18d8206d00 100644
--- a/src/components/views/dialogs/SpotlightDialog.tsx
+++ b/src/components/views/dialogs/SpotlightDialog.tsx
@@ -18,6 +18,7 @@ import React, {
ChangeEvent,
ComponentProps,
KeyboardEvent,
+ RefObject,
useCallback,
useContext,
useEffect,
@@ -52,11 +53,10 @@ import DMRoomMap from "../../../utils/DMRoomMap";
import { mediaFromMxc } from "../../../customisations/Media";
import BaseAvatar from "../avatars/BaseAvatar";
import Spinner from "../elements/Spinner";
-import { roomContextDetailsText } from "../../../Rooms";
+import { roomContextDetailsText, spaceContextDetailsText } from "../../../Rooms";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import { Action } from "../../../dispatcher/actions";
import Modal from "../../../Modal";
-import GenericFeatureFeedbackDialog from "./GenericFeatureFeedbackDialog";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import RoomViewStore from "../../../stores/RoomViewStore";
import { showStartChatInviteDialog } from "../../../RoomInvite";
@@ -64,6 +64,10 @@ import SettingsStore from "../../../settings/SettingsStore";
import { SettingLevel } from "../../../settings/SettingLevel";
import NotificationBadge from "../rooms/NotificationBadge";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
+import { BetaPill } from "../beta/BetaCard";
+import { UserTab } from "./UserSettingsDialog";
+import BetaFeedbackDialog from "./BetaFeedbackDialog";
+import SdkConfig from "../../../SdkConfig";
const MAX_RECENT_SEARCHES = 10;
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
@@ -106,10 +110,10 @@ const useRecentSearches = (): [Room[], () => void] => {
};
const ResultDetails = ({ room }: { room: Room }) => {
- const roomContextDetails = roomContextDetailsText(room);
- if (roomContextDetails) {
+ const contextDetails = room.isSpaceRoom() ? spaceContextDetailsText(room) : roomContextDetailsText(room);
+ if (contextDetails) {
return
- { roomContextDetails }
+ { contextDetails }
;
}
@@ -166,6 +170,10 @@ const useSpaceResults = (space?: Room, query?: string): [IHierarchyRoom[], boole
return [results, hierarchy?.loading ?? false];
};
+function refIsForRecentlyViewed(ref: RefObject
): boolean {
+ return ref.current?.id.startsWith("mx_SpotlightDialog_button_recentlyViewed_");
+}
+
const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => {
const cli = MatrixClientPeg.get();
const rovingContext = useContext(RovingTabIndexContext);
@@ -245,7 +253,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) =>
viewRoom(room.roomId, true);
}}
>
-
+
{ room.name }
@@ -385,9 +393,10 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) =>
viewRoom(room.roomId, true);
}}
>
-
+
{ room.name }
+
↵
)) }
@@ -450,6 +459,8 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) =>
};
const onKeyDown = (ev: KeyboardEvent) => {
+ let ref: RefObject;
+
switch (ev.key) {
case Key.ARROW_UP:
case Key.ARROW_DOWN:
@@ -457,18 +468,36 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) =>
ev.preventDefault();
if (rovingContext.state.refs.length > 0) {
- const idx = rovingContext.state.refs.indexOf(rovingContext.state.activeRef);
- const ref = findSiblingElement(rovingContext.state.refs, idx + (ev.key === Key.ARROW_UP ? -1 : 1));
-
- if (ref) {
- rovingContext.dispatch({
- type: Type.SetFocus,
- payload: { ref },
- });
- ref.current?.scrollIntoView({
- block: "nearest",
- });
+ let refs = rovingContext.state.refs;
+ if (!query) {
+ // If the current selection is not in the recently viewed row then only include the
+ // first recently viewed so that is the target when the user is switching into recently viewed.
+ const keptRecentlyViewedRef = refIsForRecentlyViewed(rovingContext.state.activeRef)
+ ? rovingContext.state.activeRef
+ : refs.find(refIsForRecentlyViewed);
+ // exclude all other recently viewed items from the list so up/down arrows skip them
+ refs = refs.filter(ref => ref === keptRecentlyViewedRef || !refIsForRecentlyViewed(ref));
}
+
+ const idx = refs.indexOf(rovingContext.state.activeRef);
+ ref = findSiblingElement(refs, idx + (ev.key === Key.ARROW_UP ? -1 : 1));
+ }
+ break;
+
+ case Key.ARROW_LEFT:
+ case Key.ARROW_RIGHT:
+ // only handle these keys when we are in the recently viewed row of options
+ if (!query &&
+ rovingContext.state.refs.length > 0 &&
+ refIsForRecentlyViewed(rovingContext.state.activeRef)
+ ) {
+ // we only intercept left/right arrows when the field is empty, and they'd do nothing anyway
+ ev.stopPropagation();
+ ev.preventDefault();
+
+ const refs = rovingContext.state.refs.filter(refIsForRecentlyViewed);
+ const idx = refs.indexOf(rovingContext.state.activeRef);
+ ref = findSiblingElement(refs, idx + (ev.key === Key.ARROW_LEFT ? -1 : 1));
}
break;
@@ -478,16 +507,34 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) =>
rovingContext.state.activeRef?.current?.click();
break;
}
+
+ if (ref) {
+ rovingContext.dispatch({
+ type: Type.SetFocus,
+ payload: { ref },
+ });
+ ref.current?.scrollIntoView({
+ block: "nearest",
+ });
+ }
};
+ const openFeedback = SdkConfig.get().bug_report_endpoint_url ? () => {
+ Modal.createTrackedDialog("Spotlight Feedback", "feature_spotlight", BetaFeedbackDialog, {
+ featureId: "feature_spotlight",
+ });
+ } : null;
+
const activeDescendant = rovingContext.state.activeRef?.current?.id;
return <>
- { _t("Use
to scroll results", {}, {
+ { _t("Use
to scroll", {}, {
arrows: () => <>
↓
↑
+ { !query &&
←
}
+ { !query &&
→
}
>,
}) }
@@ -517,24 +564,24 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) =>
-
- { activeSpace
- ? _t("Searching rooms and chats you're in and %(spaceName)s", { spaceName: activeSpace.name })
- : _t("Searching rooms and chats you're in") }
-
-
{
+ defaultDispatcher.dispatch({
+ action: Action.ViewUserSettings,
+ initialTabId: UserTab.Labs,
+ });
+ onFinished();
+ }} />
+ { openFeedback && _t("Results not as expected? Please give feedback.", {}, {
+ a: sub =>
+ { sub }
+ ,
+ }) }
+ { openFeedback && {
- Modal.createTrackedDialog("Spotlight Feedback", "", GenericFeatureFeedbackDialog, {
- title: _t("Spotlight search feedback"),
- subheading: _t("Thank you for trying Spotlight search. " +
- "Your feedback will help inform the next versions."),
- rageshakeLabel: "spotlight-feedback",
- });
- }}
+ onClick={openFeedback}
>
{ _t("Feedback") }
-
+ }
>;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 8a3aa5c1653..40bf22704cb 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -885,7 +885,15 @@
"Show extensible event representation of events": "Show extensible event representation of events",
"Show info about bridges in room settings": "Show info about bridges in room settings",
"Use new room breadcrumbs": "Use new room breadcrumbs",
- "New spotlight search experience": "New spotlight search experience",
+ "New search experience": "New search experience",
+ "The new search": "The new search",
+ "A new, quick way to search spaces and rooms you're in.": "A new, quick way to search spaces and rooms you're in.",
+ "This feature is a work in progress, we'd love to hear your feedback.": "This feature is a work in progress, we'd love to hear your feedback.",
+ "How can I give feedback?": "How can I give feedback?",
+ "To feedback, join the beta, start a search and click on feedback.": "To feedback, join the beta, start a search and click on feedback.",
+ "How can I leave the beta?": "How can I leave the beta?",
+ "To leave, just return to this page or click on the beta badge when you search.": "To leave, just return to this page or click on the beta badge when you search.",
+ "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.",
"Right panel stays open (defaults to room member list)": "Right panel stays open (defaults to room member list)",
"Jump to date (adds /jumptodate and jump to date headers)": "Jump to date (adds /jumptodate and jump to date headers)",
"Don't send read receipts": "Don't send read receipts",
@@ -2570,8 +2578,7 @@
"Forward message": "Forward message",
"Message preview": "Message preview",
"Search for rooms or people": "Search for rooms or people",
- "Thank you for your feedback, we really appreciate it.": "Thank you for your feedback, we really appreciate it.",
- "Done": "Done",
+ "Feedback sent! Thanks, we appreciate it!": "Feedback sent! Thanks, we appreciate it!",
"You may contact me if you have any follow up questions": "You may contact me if you have any follow up questions",
"Confirm abort of host creation": "Confirm abort of host creation",
"Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.",
@@ -2779,11 +2786,8 @@
"To search messages, look for this icon at the top of a room
": "To search messages, look for this icon at the top of a room
",
"Recent searches": "Recent searches",
"Clear": "Clear",
- "Use
to scroll results": "Use
to scroll results",
- "Searching rooms and chats you're in and %(spaceName)s": "Searching rooms and chats you're in and %(spaceName)s",
- "Searching rooms and chats you're in": "Searching rooms and chats you're in",
- "Spotlight search feedback": "Spotlight search feedback",
- "Thank you for trying Spotlight search. Your feedback will help inform the next versions.": "Thank you for trying Spotlight search. Your feedback will help inform the next versions.",
+ "Use
to scroll": "Use
to scroll",
+ "Results not as expected? Please
give feedback.": "Results not as expected? Please
give feedback.",
"To help us prevent this in future, please
send us logs.": "To help us prevent this in future, please
send us logs.",
"Missing session data": "Missing session data",
"Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.",
@@ -2803,6 +2807,7 @@
"Not Trusted": "Not Trusted",
"Manually Verify by Text": "Manually Verify by Text",
"Interactively verify by Emoji": "Interactively verify by Emoji",
+ "Done": "Done",
"Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)",
"Upload files": "Upload files",
"Upload all": "Upload all",
@@ -2900,8 +2905,8 @@
"Revoke permissions": "Revoke permissions",
"Move left": "Move left",
"Move right": "Move right",
- "Spaces is a beta feature": "Spaces is a beta feature",
- "Tap for more info": "Tap for more info",
+ "This is a beta feature": "This is a beta feature",
+ "Click for more info": "Click for more info",
"Beta": "Beta",
"Leave the beta": "Leave the beta",
"Join the beta": "Join the beta",
@@ -3037,6 +3042,8 @@
"Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s",
"Unable to copy room link": "Unable to copy room link",
"Unable to copy a link to the room to the clipboard.": "Unable to copy a link to the room to the clipboard.",
+ "New search beta available": "New search beta available",
+ "We're testing a new search to make finding what you want quicker.\n": "We're testing a new search to make finding what you want quicker.\n",
"Signed Out": "Signed Out",
"For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.",
"Terms and Conditions": "Terms and Conditions",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index f60587a7e7f..d3400129394 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -16,9 +16,9 @@ limitations under the License.
*/
import { MatrixClient } from 'matrix-js-sdk/src/client';
-import { ReactNode } from "react";
+import React, { ReactNode } from "react";
-import { _td } from '../languageHandler';
+import { _t, _td } from '../languageHandler';
import {
NotificationBodyEnabledController,
NotificationsEnabledController,
@@ -41,6 +41,7 @@ import ReducedMotionController from './controllers/ReducedMotionController';
import IncompatibleController from "./controllers/IncompatibleController";
import { ImageSize } from "./enums/ImageSize";
import { MetaSpace } from "../stores/spaces";
+import SdkConfig from "../SdkConfig";
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
const LEVELS_ROOM_SETTINGS = [
@@ -155,12 +156,13 @@ interface IBaseSetting {
// XXX: Keep this around for re-use in future Betas
betaInfo?: {
title: string; // _td
- caption: string; // _td
+ caption: () => ReactNode;
disclaimer?: (enabled: boolean) => ReactNode;
image: string; // require(...)
feedbackSubheading?: string;
feedbackLabel?: string;
extraSettings?: string[];
+ requiresRefresh?: boolean;
};
}
@@ -336,8 +338,27 @@ export const SETTINGS: {[setting: string]: ISetting} = {
isFeature: true,
labsGroup: LabGroup.Rooms,
supportedLevels: LEVELS_FEATURE,
- displayName: _td("New spotlight search experience"),
- default: false,
+ displayName: _td("New search experience"),
+ default: false,
+ betaInfo: {
+ title: _td("The new search"),
+ caption: () => <>
+
{ _t("A new, quick way to search spaces and rooms you're in.") }
+
{ _t("This feature is a work in progress, we'd love to hear your feedback.") }
+ >,
+ disclaimer: () => <>
+ { SdkConfig.get().bug_report_endpoint_url && <>
+
{ _t("How can I give feedback?") }
+
{ _t("To feedback, join the beta, start a search and click on feedback.") }
+ > }
+
{ _t("How can I leave the beta?") }
+
{ _t("To leave, just return to this page or click on the beta badge when you search.") }
+ >,
+ feedbackLabel: "spotlight-feedback",
+ feedbackSubheading: _td("Thank you for trying the beta, " +
+ "please go into as much detail as you can so we can improve it."),
+ image: require("../../res/img/betas/new_search_experience.gif"),
+ },
},
"feature_right_panel_default_open": {
isFeature: true,
diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts
index 63b00731916..35961b104b8 100644
--- a/src/stores/spaces/SpaceStore.ts
+++ b/src/stores/spaces/SpaceStore.ts
@@ -95,7 +95,7 @@ const getRoomFn: FetchRoomFn = (room: Room) => {
export class SpaceStoreClass extends AsyncStoreWithClient
{
// The spaces representing the roots of the various tree-like hierarchies
private rootSpaces: Room[] = [];
- // Map from room ID to set of spaces which list it as a child
+ // Map from room/space ID to set of spaces which list it as a child
private parentMap = new EnhancedMap>();
// Map from SpaceKey to SpaceNotificationState instance representing that space
private notificationStateMap = new Map();