Various search UI improvements
This commit is contained in:
parent
630394d91d
commit
a9cb621eb6
25 changed files with 835 additions and 577 deletions
|
@ -2,8 +2,6 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { debounce, get } from 'lodash';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
|
@ -13,20 +11,14 @@ import { AvatarPopup } from './AvatarPopup';
|
|||
import type { LocalizerType } from '../types/Util';
|
||||
import type { AvatarColorType } from '../types/Colors';
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import { LeftPaneSearchInput } from './LeftPaneSearchInput';
|
||||
|
||||
export type PropsType = {
|
||||
searchTerm: string;
|
||||
searchConversationName?: string;
|
||||
searchConversationId?: string;
|
||||
searchConversation: undefined | ConversationType;
|
||||
startSearchCounter: number;
|
||||
selectedConversation: undefined | ConversationType;
|
||||
|
||||
// To be used as an ID
|
||||
ourConversationId: string;
|
||||
ourUuid: string;
|
||||
ourNumber: string;
|
||||
regionCode: string;
|
||||
|
||||
// For display
|
||||
phoneNumber?: string;
|
||||
isMe?: boolean;
|
||||
|
@ -42,24 +34,6 @@ export type PropsType = {
|
|||
i18n: LocalizerType;
|
||||
|
||||
updateSearchTerm: (searchTerm: string) => void;
|
||||
startSearch: () => void;
|
||||
searchInConversation: (id: string, name: string) => void;
|
||||
searchMessages: (
|
||||
query: string,
|
||||
options: {
|
||||
searchConversationId?: string;
|
||||
regionCode: string;
|
||||
}
|
||||
) => void;
|
||||
searchDiscussions: (
|
||||
query: string,
|
||||
options: {
|
||||
ourConversationId: string;
|
||||
ourNumber: string;
|
||||
ourUuid: string;
|
||||
noteToSelf: string;
|
||||
}
|
||||
) => void;
|
||||
startUpdate: () => unknown;
|
||||
clearConversationSearch: () => void;
|
||||
clearSearch: () => void;
|
||||
|
@ -89,12 +63,12 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
|
||||
public componentDidUpdate(prevProps: PropsType): void {
|
||||
const { searchConversationId, startSearchCounter } = this.props;
|
||||
const { searchConversation, startSearchCounter } = this.props;
|
||||
|
||||
// When user chooses to search in a given conversation we focus the field for them
|
||||
if (
|
||||
searchConversationId &&
|
||||
searchConversationId !== prevProps.searchConversationId
|
||||
searchConversation &&
|
||||
searchConversation.id !== prevProps.searchConversation?.id
|
||||
) {
|
||||
this.setFocus();
|
||||
}
|
||||
|
@ -157,46 +131,16 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
}
|
||||
|
||||
public search = debounce((searchTerm: string): void => {
|
||||
const {
|
||||
i18n,
|
||||
ourConversationId,
|
||||
ourNumber,
|
||||
ourUuid,
|
||||
regionCode,
|
||||
searchDiscussions,
|
||||
searchMessages,
|
||||
searchConversationId,
|
||||
} = this.props;
|
||||
|
||||
if (searchDiscussions && !searchConversationId) {
|
||||
searchDiscussions(searchTerm, {
|
||||
noteToSelf: i18n('noteToSelf').toLowerCase(),
|
||||
ourConversationId,
|
||||
ourNumber,
|
||||
ourUuid,
|
||||
});
|
||||
}
|
||||
|
||||
if (searchMessages) {
|
||||
searchMessages(searchTerm, {
|
||||
searchConversationId,
|
||||
regionCode,
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
|
||||
public updateSearch = (event: React.FormEvent<HTMLInputElement>): void => {
|
||||
private updateSearch = (searchTerm: string): void => {
|
||||
const {
|
||||
updateSearchTerm,
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
searchConversationId,
|
||||
searchConversation,
|
||||
} = this.props;
|
||||
const searchTerm = event.currentTarget.value;
|
||||
|
||||
if (!searchTerm) {
|
||||
if (searchConversationId) {
|
||||
if (searchConversation) {
|
||||
clearConversationSearch();
|
||||
} else {
|
||||
clearSearch();
|
||||
|
@ -208,132 +152,30 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
if (updateSearchTerm) {
|
||||
updateSearchTerm(searchTerm);
|
||||
}
|
||||
|
||||
if (searchTerm.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.search(searchTerm);
|
||||
};
|
||||
|
||||
public clearSearch = (): void => {
|
||||
const { clearSearch } = this.props;
|
||||
|
||||
clearSearch();
|
||||
this.setFocus();
|
||||
};
|
||||
|
||||
public clearConversationSearch = (): void => {
|
||||
const { clearConversationSearch } = this.props;
|
||||
|
||||
clearConversationSearch();
|
||||
this.setFocus();
|
||||
};
|
||||
|
||||
private handleInputBlur = (): void => {
|
||||
const { clearSearch, searchConversationId, searchTerm } = this.props;
|
||||
if (!searchConversationId && !searchTerm) {
|
||||
const { clearSearch, searchConversation, searchTerm } = this.props;
|
||||
if (!searchConversation && !searchTerm) {
|
||||
clearSearch();
|
||||
}
|
||||
};
|
||||
|
||||
public handleInputKeyDown = (
|
||||
event: React.KeyboardEvent<HTMLInputElement>
|
||||
): void => {
|
||||
const {
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
searchConversationId,
|
||||
searchTerm,
|
||||
} = this.props;
|
||||
|
||||
const { ctrlKey, metaKey, key } = event;
|
||||
const commandKey = get(window, 'platform') === 'darwin' && metaKey;
|
||||
const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey;
|
||||
const commandOrCtrl = commandKey || controlKey;
|
||||
|
||||
// On linux, this keyboard combination selects all text
|
||||
if (commandOrCtrl && key === '/') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (key !== 'Escape') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchConversationId && searchTerm) {
|
||||
clearConversationSearch();
|
||||
} else {
|
||||
clearSearch();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
public handleGlobalKeyDown = (event: KeyboardEvent): void => {
|
||||
const { showingAvatarPopup } = this.state;
|
||||
const {
|
||||
i18n,
|
||||
selectedConversation,
|
||||
startSearch,
|
||||
searchInConversation,
|
||||
} = this.props;
|
||||
|
||||
const { ctrlKey, metaKey, shiftKey, key } = event;
|
||||
const commandKey = get(window, 'platform') === 'darwin' && metaKey;
|
||||
const controlKey = get(window, 'platform') !== 'darwin' && ctrlKey;
|
||||
const commandOrCtrl = commandKey || controlKey;
|
||||
const commandAndCtrl = commandKey && ctrlKey;
|
||||
const { key } = event;
|
||||
|
||||
if (showingAvatarPopup && key === 'Escape') {
|
||||
this.hideAvatarPopup();
|
||||
} else if (
|
||||
commandOrCtrl &&
|
||||
!commandAndCtrl &&
|
||||
!shiftKey &&
|
||||
(key === 'f' || key === 'F')
|
||||
) {
|
||||
startSearch();
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else if (
|
||||
selectedConversation &&
|
||||
commandOrCtrl &&
|
||||
!commandAndCtrl &&
|
||||
shiftKey &&
|
||||
(key === 'f' || key === 'F')
|
||||
) {
|
||||
const name = selectedConversation.isMe
|
||||
? i18n('noteToSelf')
|
||||
: selectedConversation.title;
|
||||
searchInConversation(selectedConversation.id, name);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
public handleXButton = (): void => {
|
||||
const {
|
||||
searchConversationId,
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
} = this.props;
|
||||
|
||||
if (searchConversationId) {
|
||||
clearConversationSearch();
|
||||
} else {
|
||||
clearSearch();
|
||||
}
|
||||
|
||||
this.setFocus();
|
||||
};
|
||||
|
||||
public setFocus = (): void => {
|
||||
if (this.inputRef.current) {
|
||||
this.inputRef.current.focus();
|
||||
|
@ -356,8 +198,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
name,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
searchConversationId,
|
||||
searchConversationName,
|
||||
searchConversation,
|
||||
searchTerm,
|
||||
showArchivedConversations,
|
||||
startComposing,
|
||||
|
@ -367,13 +208,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
} = this.props;
|
||||
const { showingAvatarPopup, popperRoot } = this.state;
|
||||
|
||||
const placeholder = searchConversationName
|
||||
? i18n('searchIn', [searchConversationName])
|
||||
: i18n('search');
|
||||
|
||||
const isSearching = Boolean(
|
||||
searchConversationId || searchTerm.trim().length
|
||||
);
|
||||
const isSearching = Boolean(searchConversation || searchTerm.trim().length);
|
||||
|
||||
return (
|
||||
<div className="module-main-header">
|
||||
|
@ -447,59 +282,16 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
)
|
||||
: null}
|
||||
</Manager>
|
||||
<div className="module-main-header__search">
|
||||
{searchConversationId ? (
|
||||
<button
|
||||
className="module-main-header__search__in-conversation-pill"
|
||||
onClick={this.clearSearch}
|
||||
tabIndex={-1}
|
||||
type="button"
|
||||
aria-label={i18n('clearSearch')}
|
||||
>
|
||||
<div className="module-main-header__search__in-conversation-pill__avatar-container">
|
||||
<div className="module-main-header__search__in-conversation-pill__avatar" />
|
||||
</div>
|
||||
<div className="module-main-header__search__in-conversation-pill__x-button" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="module-main-header__search__icon"
|
||||
onClick={this.setFocus}
|
||||
tabIndex={-1}
|
||||
type="button"
|
||||
aria-label={i18n('search')}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
disabled={disabled}
|
||||
type="text"
|
||||
ref={this.inputRef}
|
||||
className={classNames(
|
||||
'module-main-header__search__input',
|
||||
searchTerm
|
||||
? 'module-main-header__search__input--with-text'
|
||||
: null,
|
||||
searchConversationId
|
||||
? 'module-main-header__search__input--in-conversation'
|
||||
: null
|
||||
)}
|
||||
placeholder={placeholder}
|
||||
dir="auto"
|
||||
onBlur={this.handleInputBlur}
|
||||
onKeyDown={this.handleInputKeyDown}
|
||||
value={searchTerm}
|
||||
onChange={this.updateSearch}
|
||||
/>
|
||||
{searchTerm ? (
|
||||
<button
|
||||
tabIndex={-1}
|
||||
className="module-main-header__search__cancel-icon"
|
||||
onClick={this.handleXButton}
|
||||
type="button"
|
||||
aria-label={i18n('cancel')}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<LeftPaneSearchInput
|
||||
disabled={disabled}
|
||||
i18n={i18n}
|
||||
onBlur={this.handleInputBlur}
|
||||
onChangeValue={this.updateSearch}
|
||||
onClear={this.clearSearch}
|
||||
ref={this.inputRef}
|
||||
searchConversation={searchConversation}
|
||||
value={searchTerm}
|
||||
/>
|
||||
{!isSearching && (
|
||||
<button
|
||||
aria-label={i18n('newConversation')}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue