Allow jumping to message search from spotlight (#29850)

* Allow jumping to message search from spotlight

replaces the message search hint which referenced the old UX

Fixes #29831

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update RoomSummaryCard.tsx

* Update actions.ts

* Delete src/hooks/useTransition.ts

* Update RoomSummaryCard.tsx

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2025-04-30 12:23:35 +01:00
committed by GitHub
parent 23597e959b
commit 4bf28f8159
14 changed files with 212 additions and 240 deletions

View File

@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type ChangeEvent } from "react";
import React from "react";
import { type Room, type RoomState, RoomStateEvent, RoomMember, type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { throttle } from "lodash";
@@ -49,8 +49,9 @@ interface RoomlessProps extends BaseProps {
interface RoomProps extends BaseProps {
room: Room;
permalinkCreator: RoomPermalinkCreator;
onSearchChange?: (e: ChangeEvent) => void;
onSearchChange?: (term: string) => void;
onSearchCancel?: () => void;
searchTerm?: string;
}
type Props = XOR<RoomlessProps, RoomProps>;
@@ -260,6 +261,7 @@ export default class RightPanel extends React.Component<Props, IState> {
permalinkCreator={this.props.permalinkCreator!}
onSearchChange={this.props.onSearchChange}
onSearchCancel={this.props.onSearchCancel}
searchTerm={this.props.searchTerm}
focusRoomSearch={cardState?.focusRoomSearch}
/>
);

View File

@@ -10,7 +10,6 @@ Please see LICENSE files in the repository root for full details.
*/
import React, {
type ChangeEvent,
type ComponentProps,
createRef,
type ReactElement,
@@ -133,6 +132,7 @@ import RoomSearchAuxPanel from "../views/rooms/RoomSearchAuxPanel";
import { PinnedMessageBanner } from "../views/rooms/PinnedMessageBanner";
import { ScopedRoomContextProvider, useScopedRoomContext } from "../../contexts/ScopedRoomContext";
import { DeclineAndBlockInviteDialog } from "../views/dialogs/DeclineAndBlockInviteDialog";
import { type FocusMessageSearchPayload } from "../../dispatcher/payloads/FocusMessageSearchPayload.ts";
const DEBUG = false;
const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000;
@@ -1244,6 +1244,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
case Action.View3pidInvite:
onView3pidInvite(payload, RightPanelStore.instance);
break;
case Action.FocusMessageSearch:
if ((payload as FocusMessageSearchPayload).initialText) {
this.onSearch(payload.initialText);
}
break;
}
};
@@ -1806,8 +1811,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
defaultDispatcher.fire(Action.ViewRoomDirectory);
};
private onSearchChange = debounce((e: ChangeEvent): void => {
const term = (e.target as HTMLInputElement).value;
private onSearchChange = debounce((term: string): void => {
this.onSearch(term);
}, 300);
@@ -2487,6 +2491,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
e2eStatus={this.state.e2eStatus}
onSearchChange={this.onSearchChange}
onSearchCancel={this.onCancelSearchClick}
searchTerm={this.state.search?.term ?? ""}
/>
) : undefined;

View File

@@ -608,6 +608,21 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
{filterToLabel(Filter.People)}
</Option>
)}
{filter === null && (
<Option
id="mx_SpotlightDialog_button_searchMessages"
className="mx_SpotlightDialog_searchMessages"
onClick={() => {
defaultDispatcher.dispatch({
action: Action.FocusMessageSearch,
initialText: trimmedQuery,
});
onFinished();
}}
>
{_t("spotlight_dialog|messages_label")}
</Option>
)}
</div>
</div>
);
@@ -997,28 +1012,6 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
);
}
let messageSearchSection: JSX.Element | undefined;
if (filter === null) {
messageSearchSection = (
<div
className="mx_SpotlightDialog_section mx_SpotlightDialog_otherSearches"
role="group"
aria-labelledby="mx_SpotlightDialog_section_messageSearch"
>
<h4 id="mx_SpotlightDialog_section_messageSearch">
{_t("spotlight_dialog|message_search_section_title")}
</h4>
<div className="mx_SpotlightDialog_otherSearches_messageSearchText">
{_t(
"spotlight_dialog|search_messages_hint",
{},
{ icon: () => <div className="mx_SpotlightDialog_otherSearches_messageSearchIcon" /> },
)}
</div>
</div>
);
}
content = (
<>
{peopleSection}
@@ -1031,7 +1024,6 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
{hiddenResultsSection}
{otherSearchesSection}
{groupChatSection}
{messageSearchSection}
</>
);
} else {

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type ChangeEvent, useContext, useEffect, useRef, useState } from "react";
import React, { type JSX, useContext, useEffect, useRef, useState } from "react";
import classNames from "classnames";
import {
MenuItem,
@@ -52,7 +52,6 @@ import { ShareDialog } from "../dialogs/ShareDialog";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import { E2EStatus } from "../../../utils/ShieldUtils";
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { TimelineRenderingType } from "../../../contexts/RoomContext";
import RoomName from "../elements/RoomName";
import ExportDialog from "../dialogs/ExportDialog";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
@@ -71,7 +70,6 @@ import { Box } from "../../utils/Box";
import { useDispatcher } from "../../../hooks/useDispatcher";
import { Action } from "../../../dispatcher/actions";
import { Key } from "../../../Keyboard";
import { useTransition } from "../../../hooks/useTransition";
import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
@@ -82,9 +80,10 @@ import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryC
interface IProps {
room: Room;
permalinkCreator: RoomPermalinkCreator;
onSearchChange?: (e: ChangeEvent) => void;
onSearchChange?: (term: string) => void;
onSearchCancel?: () => void;
focusRoomSearch?: boolean;
searchTerm?: string;
}
const onRoomMembersClick = (): void => {
@@ -180,6 +179,7 @@ const RoomSummaryCard: React.FC<IProps> = ({
onSearchChange,
onSearchCancel,
focusRoomSearch,
searchTerm,
}) => {
const cli = useContext(MatrixClientContext);
@@ -244,19 +244,6 @@ const RoomSummaryCard: React.FC<IProps> = ({
searchInputRef.current?.focus();
}
});
// Clear the search field when the user leaves the search view
useTransition(
(prevTimelineRenderingType) => {
if (
prevTimelineRenderingType === TimelineRenderingType.Search &&
roomContext.timelineRenderingType !== TimelineRenderingType.Search &&
searchInputRef.current
) {
searchInputRef.current.value = "";
}
},
[roomContext.timelineRenderingType],
);
const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
const roomInfo = (
@@ -332,7 +319,10 @@ const RoomSummaryCard: React.FC<IProps> = ({
<Search
placeholder={_t("room|search|placeholder")}
name="room_message_search"
onChange={onSearchChange}
onChange={(e) => {
onSearchChange(e.currentTarget.value);
}}
value={searchTerm}
className="mx_no_textinput"
ref={searchInputRef}
autoFocus={focusRoomSearch}