Move new search experience to a Beta (#7718)
This commit is contained in:
parent
5201c9b285
commit
ed185240a5
16 changed files with 329 additions and 116 deletions
|
@ -91,7 +91,7 @@ import { RoomUpdateCause } from "../../stores/room-list/models";
|
|||
import SecurityCustomisations from "../../customisations/Security";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import QuestionDialog from "../views/dialogs/QuestionDialog";
|
||||
import UserSettingsDialog from '../views/dialogs/UserSettingsDialog';
|
||||
import UserSettingsDialog, { UserTab } from '../views/dialogs/UserSettingsDialog';
|
||||
import CreateGroupDialog from '../views/dialogs/CreateGroupDialog';
|
||||
import CreateRoomDialog from '../views/dialogs/CreateRoomDialog';
|
||||
import RoomDirectory from './RoomDirectory';
|
||||
|
@ -117,6 +117,7 @@ import { showSpaceInvite } from "../../utils/space";
|
|||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { SummarizedNotificationState } from "../../stores/notifications/SummarizedNotificationState";
|
||||
import GenericToast from '../views/toasts/GenericToast';
|
||||
|
||||
/** constants for MatrixChat.state.view */
|
||||
export enum Views {
|
||||
|
@ -1551,6 +1552,42 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
showNotificationsToast(false);
|
||||
}
|
||||
|
||||
if (!localStorage.getItem("mx_seen_feature_spotlight_toast")) {
|
||||
setTimeout(() => {
|
||||
// Skip the toast if the beta is already enabled or the user has changed the setting from default
|
||||
if (SettingsStore.getValue("feature_spotlight") ||
|
||||
SettingsStore.getValue("feature_spotlight", null, true) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = "BETA_SPOTLIGHT_TOAST";
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key,
|
||||
title: _t("New search beta available"),
|
||||
props: {
|
||||
description: _t("We're testing a new search to make finding what you want quicker.\n"),
|
||||
acceptLabel: _t("Learn more"),
|
||||
onAccept: () => {
|
||||
dis.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Labs,
|
||||
});
|
||||
localStorage.setItem("mx_seen_feature_spotlight_toast", "true");
|
||||
ToastStore.sharedInstance().dismissToast(key);
|
||||
},
|
||||
rejectLabel: _t("Dismiss"),
|
||||
onReject: () => {
|
||||
localStorage.setItem("mx_seen_feature_spotlight_toast", "true");
|
||||
ToastStore.sharedInstance().dismissToast(key);
|
||||
},
|
||||
},
|
||||
icon: "labs",
|
||||
component: GenericToast,
|
||||
priority: 9,
|
||||
});
|
||||
}, 5 * 60 * 1000); // show after 5 minutes to not overload user with toasts on launch
|
||||
}
|
||||
|
||||
dis.fire(Action.FocusSendMessageComposer);
|
||||
this.setState({
|
||||
ready: true,
|
||||
|
|
|
@ -34,6 +34,7 @@ import SettingsStore from "../../settings/SettingsStore";
|
|||
import Modal from "../../Modal";
|
||||
import SpotlightDialog from "../views/dialogs/SpotlightDialog";
|
||||
import { ALTERNATE_KEY_NAME, KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
import ToastStore from "../../stores/ToastStore";
|
||||
|
||||
interface IProps {
|
||||
isMinimized: boolean;
|
||||
|
@ -46,11 +47,13 @@ interface IProps {
|
|||
interface IState {
|
||||
query: string;
|
||||
focused: boolean;
|
||||
spotlightBetaEnabled: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomSearch")
|
||||
export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||
private readonly dispatcherRef: string;
|
||||
private readonly betaRef: string;
|
||||
private inputRef: React.RefObject<HTMLInputElement> = createRef();
|
||||
private searchFilter: NameFilterCondition = new NameFilterCondition();
|
||||
|
||||
|
@ -60,11 +63,13 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
this.state = {
|
||||
query: "",
|
||||
focused: false,
|
||||
spotlightBetaEnabled: SettingsStore.getValue("feature_spotlight"),
|
||||
};
|
||||
|
||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||
// clear filter when changing spaces, in future we may wish to maintain a filter per-space
|
||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.clearInput);
|
||||
this.betaRef = SettingsStore.watchSetting("feature_spotlight", null, this.onSpotlightChange);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void {
|
||||
|
@ -85,8 +90,18 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
public componentWillUnmount() {
|
||||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.clearInput);
|
||||
SettingsStore.unwatchSetting(this.betaRef);
|
||||
}
|
||||
|
||||
private onSpotlightChange = () => {
|
||||
const spotlightBetaEnabled = SettingsStore.getValue("feature_spotlight");
|
||||
if (this.state.spotlightBetaEnabled !== spotlightBetaEnabled) {
|
||||
this.setState({ spotlightBetaEnabled });
|
||||
}
|
||||
// in case the user was in settings at the 5-minute mark, dismiss the toast
|
||||
ToastStore.sharedInstance().dismissToast("BETA_SPOTLIGHT_TOAST");
|
||||
};
|
||||
|
||||
private openSpotlight() {
|
||||
Modal.createTrackedDialog("Spotlight", "", SpotlightDialog, {}, "mx_SpotlightDialog_wrapper", false, true);
|
||||
}
|
||||
|
@ -95,11 +110,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
if (payload.action === Action.ViewRoom && payload.clear_search) {
|
||||
this.clearInput();
|
||||
} else if (payload.action === 'focus_room_filter') {
|
||||
if (SettingsStore.getValue("feature_spotlight")) {
|
||||
this.openSpotlight();
|
||||
} else {
|
||||
this.inputRef.current?.focus();
|
||||
}
|
||||
this.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -110,10 +121,10 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
private openSearch = () => {
|
||||
if (SettingsStore.getValue("feature_spotlight")) {
|
||||
if (this.state.spotlightBetaEnabled) {
|
||||
this.openSpotlight();
|
||||
} else {
|
||||
defaultDispatcher.dispatch({ action: "show_left_panel" });
|
||||
// dispatched as it needs handling by MatrixChat too
|
||||
defaultDispatcher.dispatch({ action: "focus_room_filter" });
|
||||
}
|
||||
};
|
||||
|
@ -124,14 +135,8 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
private onFocus = (ev: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (SettingsStore.getValue("feature_spotlight")) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.openSpotlight();
|
||||
} else {
|
||||
this.setState({ focused: true });
|
||||
ev.target.select();
|
||||
}
|
||||
this.setState({ focused: true });
|
||||
ev.target.select();
|
||||
};
|
||||
|
||||
private onBlur = (ev: React.FocusEvent<HTMLInputElement>) => {
|
||||
|
@ -159,7 +164,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
public focus = (): void => {
|
||||
if (SettingsStore.getValue("feature_spotlight")) {
|
||||
if (this.state.spotlightBetaEnabled) {
|
||||
this.openSpotlight();
|
||||
} else {
|
||||
this.inputRef.current?.focus();
|
||||
|
@ -172,6 +177,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
'mx_RoomSearch_hasQuery': this.state.query,
|
||||
'mx_RoomSearch_focused': this.state.focused,
|
||||
'mx_RoomSearch_minimized': this.props.isMinimized,
|
||||
'mx_RoomSearch_spotlightTrigger': this.state.spotlightBetaEnabled,
|
||||
});
|
||||
|
||||
const inputClasses = classNames({
|
||||
|
@ -180,8 +186,9 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
});
|
||||
|
||||
let icon = (
|
||||
<div className="mx_RoomSearch_icon" onClick={this.focus} />
|
||||
<div className="mx_RoomSearch_icon" />
|
||||
);
|
||||
|
||||
let input = (
|
||||
<input
|
||||
type="text"
|
||||
|
@ -192,10 +199,11 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
onBlur={this.onBlur}
|
||||
onChange={this.onChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={SettingsStore.getValue("feature_spotlight") ? _t("Search") : _t("Filter")}
|
||||
placeholder={_t("Filter")}
|
||||
autoComplete="off"
|
||||
/>
|
||||
);
|
||||
|
||||
let clearButton = (
|
||||
<AccessibleButton
|
||||
tabIndex={-1}
|
||||
|
@ -204,7 +212,8 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
onClick={this.clearInput}
|
||||
/>
|
||||
);
|
||||
let shortcutPrompt = <div className="mx_RoomSearch_shortcutPrompt" onClick={this.focus}>
|
||||
|
||||
let shortcutPrompt = <div className="mx_RoomSearch_shortcutPrompt">
|
||||
{ isMac ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K" }
|
||||
</div>;
|
||||
|
||||
|
@ -221,8 +230,18 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
shortcutPrompt = null;
|
||||
}
|
||||
|
||||
if (this.state.spotlightBetaEnabled) {
|
||||
return <AccessibleButton onClick={this.openSpotlight} className={classes}>
|
||||
{ icon }
|
||||
{ input && <div className="mx_RoomSearch_spotlightTriggerText">
|
||||
{ _t("Search") }
|
||||
</div> }
|
||||
{ shortcutPrompt }
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={classes} onClick={this.focus}>
|
||||
{ icon }
|
||||
{ input }
|
||||
{ shortcutPrompt }
|
||||
|
|
|
@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ReactNode, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
@ -26,6 +27,8 @@ import Modal from "../../../Modal";
|
|||
import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import SettingsFlag from "../elements/SettingsFlag";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import InlineSpinner from "../elements/InlineSpinner";
|
||||
|
||||
// XXX: Keep this around for re-use in future Betas
|
||||
|
||||
|
@ -42,10 +45,10 @@ export const BetaPill = ({ onClick }: { onClick?: () => void }) => {
|
|||
})}
|
||||
tooltip={<div>
|
||||
<div className="mx_Tooltip_title">
|
||||
{ _t("Spaces is a beta feature") }
|
||||
{ _t("This is a beta feature") }
|
||||
</div>
|
||||
<div className="mx_Tooltip_sub">
|
||||
{ _t("Tap for more info") }
|
||||
{ _t("Click for more info") }
|
||||
</div>
|
||||
</div>}
|
||||
onClick={onClick}
|
||||
|
@ -67,10 +70,20 @@ export const BetaPill = ({ onClick }: { onClick?: () => void }) => {
|
|||
|
||||
const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
|
||||
const info = SettingsStore.getBetaInfo(featureId);
|
||||
const value = useFeatureEnabled(featureId);
|
||||
const [busy, setBusy] = useState(false);
|
||||
if (!info) return null; // Beta is invalid/disabled
|
||||
|
||||
const { title, caption, disclaimer, image, feedbackLabel, feedbackSubheading, extraSettings } = info;
|
||||
const value = SettingsStore.getValue(featureId);
|
||||
const {
|
||||
title,
|
||||
caption,
|
||||
disclaimer,
|
||||
image,
|
||||
feedbackLabel,
|
||||
feedbackSubheading,
|
||||
extraSettings,
|
||||
requiresRefresh,
|
||||
} = info;
|
||||
|
||||
let feedbackButton;
|
||||
if (value && feedbackLabel && feedbackSubheading && SdkConfig.get().bug_report_endpoint_url) {
|
||||
|
@ -84,6 +97,15 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
|
|||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
let content: ReactNode;
|
||||
if (busy) {
|
||||
content = <InlineSpinner />;
|
||||
} else if (value) {
|
||||
content = _t("Leave the beta");
|
||||
} else {
|
||||
content = _t("Join the beta");
|
||||
}
|
||||
|
||||
return <div className="mx_BetaCard">
|
||||
<div className="mx_BetaCard_columns">
|
||||
<div>
|
||||
|
@ -91,14 +113,26 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
|
|||
{ titleOverride || _t(title) }
|
||||
<BetaPill />
|
||||
</h3>
|
||||
<span className="mx_BetaCard_caption">{ _t(caption) }</span>
|
||||
<span className="mx_BetaCard_caption">{ caption() }</span>
|
||||
<div className="mx_BetaCard_buttons">
|
||||
{ feedbackButton }
|
||||
<AccessibleButton
|
||||
onClick={() => 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 }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
{ disclaimer && <div className="mx_BetaCard_disclaimer">
|
||||
|
|
|
@ -52,8 +52,8 @@ const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
|
|||
|
||||
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<IProps> = ({
|
|||
{ subheading }
|
||||
|
||||
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") }
|
||||
|
||||
|
||||
{ children }
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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 <div className="mx_SpotlightDialog_result_details">
|
||||
{ roomContextDetails }
|
||||
{ contextDetails }
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
@ -166,6 +170,10 @@ const useSpaceResults = (space?: Room, query?: string): [IHierarchyRoom[], boole
|
|||
return [results, hierarchy?.loading ?? false];
|
||||
};
|
||||
|
||||
function refIsForRecentlyViewed(ref: RefObject<HTMLElement>): boolean {
|
||||
return ref.current?.id.startsWith("mx_SpotlightDialog_button_recentlyViewed_");
|
||||
}
|
||||
|
||||
const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const rovingContext = useContext(RovingTabIndexContext);
|
||||
|
@ -245,7 +253,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) =>
|
|||
viewRoom(room.roomId, true);
|
||||
}}
|
||||
>
|
||||
<DecoratedRoomAvatar room={room} avatarSize={20} />
|
||||
<DecoratedRoomAvatar room={room} avatarSize={20} tooltipProps={{ tabIndex: -1 }} />
|
||||
{ room.name }
|
||||
<NotificationBadge notification={RoomNotificationStateStore.instance.getRoomState(room)} />
|
||||
<ResultDetails room={room} />
|
||||
|
@ -385,9 +393,10 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) =>
|
|||
viewRoom(room.roomId, true);
|
||||
}}
|
||||
>
|
||||
<DecoratedRoomAvatar room={room} avatarSize={20} />
|
||||
<DecoratedRoomAvatar room={room} avatarSize={20} tooltipProps={{ tabIndex: -1 }} />
|
||||
{ room.name }
|
||||
<NotificationBadge notification={RoomNotificationStateStore.instance.getRoomState(room)} />
|
||||
<ResultDetails room={room} />
|
||||
<div className="mx_SpotlightDialog_enterPrompt">↵</div>
|
||||
</Option>
|
||||
)) }
|
||||
|
@ -450,6 +459,8 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) =>
|
|||
};
|
||||
|
||||
const onKeyDown = (ev: KeyboardEvent) => {
|
||||
let ref: RefObject<HTMLElement>;
|
||||
|
||||
switch (ev.key) {
|
||||
case Key.ARROW_UP:
|
||||
case Key.ARROW_DOWN:
|
||||
|
@ -457,18 +468,36 @@ const SpotlightDialog: React.FC<IProps> = ({ 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<IProps> = ({ 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 <>
|
||||
<div className="mx_SpotlightDialog_keyboardPrompt">
|
||||
{ _t("Use <arrows/> to scroll results", {}, {
|
||||
{ _t("Use <arrows/> to scroll", {}, {
|
||||
arrows: () => <>
|
||||
<div>↓</div>
|
||||
<div>↑</div>
|
||||
{ !query && <div>←</div> }
|
||||
{ !query && <div>→</div> }
|
||||
</>,
|
||||
}) }
|
||||
</div>
|
||||
|
@ -517,24 +564,24 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) =>
|
|||
</div>
|
||||
|
||||
<div className="mx_SpotlightDialog_footer">
|
||||
<span>
|
||||
{ activeSpace
|
||||
? _t("Searching rooms and chats you're in and %(spaceName)s", { spaceName: activeSpace.name })
|
||||
: _t("Searching rooms and chats you're in") }
|
||||
</span>
|
||||
<AccessibleButton
|
||||
<BetaPill onClick={() => {
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Labs,
|
||||
});
|
||||
onFinished();
|
||||
}} />
|
||||
{ openFeedback && _t("Results not as expected? Please <a>give feedback</a>.", {}, {
|
||||
a: sub => <AccessibleButton kind="link_inline" onClick={openFeedback}>
|
||||
{ sub }
|
||||
</AccessibleButton>,
|
||||
}) }
|
||||
{ openFeedback && <AccessibleButton
|
||||
kind="primary_outline"
|
||||
onClick={() => {
|
||||
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") }
|
||||
</AccessibleButton>
|
||||
</AccessibleButton> }
|
||||
</div>
|
||||
</BaseDialog>
|
||||
</>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue