Full-text search within conversation
This commit is contained in:
parent
6292019d30
commit
c39d5a811a
26 changed files with 697 additions and 134 deletions
|
@ -1,13 +1,14 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { Avatar } from './Avatar';
|
||||
|
||||
import { cleanSearchTerm } from '../util/cleanSearchTerm';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
export interface Props {
|
||||
export interface PropsType {
|
||||
searchTerm: string;
|
||||
searchConversationName?: string;
|
||||
searchConversationId?: string;
|
||||
|
||||
// To be used as an ID
|
||||
ourNumber: string;
|
||||
|
@ -27,55 +28,73 @@ export interface Props {
|
|||
search: (
|
||||
query: string,
|
||||
options: {
|
||||
searchConversationId?: string;
|
||||
regionCode: string;
|
||||
ourNumber: string;
|
||||
noteToSelf: string;
|
||||
}
|
||||
) => void;
|
||||
|
||||
clearConversationSearch: () => void;
|
||||
clearSearch: () => void;
|
||||
}
|
||||
|
||||
export class MainHeader extends React.Component<Props> {
|
||||
private readonly updateSearchBound: (
|
||||
event: React.FormEvent<HTMLInputElement>
|
||||
) => void;
|
||||
private readonly clearSearchBound: () => void;
|
||||
private readonly handleKeyUpBound: (
|
||||
event: React.KeyboardEvent<HTMLInputElement>
|
||||
) => void;
|
||||
private readonly setFocusBound: () => void;
|
||||
export class MainHeader extends React.Component<PropsType> {
|
||||
private readonly inputRef: React.RefObject<HTMLInputElement>;
|
||||
private readonly debouncedSearch: (searchTerm: string) => void;
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
|
||||
this.updateSearchBound = this.updateSearch.bind(this);
|
||||
this.clearSearchBound = this.clearSearch.bind(this);
|
||||
this.handleKeyUpBound = this.handleKeyUp.bind(this);
|
||||
this.setFocusBound = this.setFocus.bind(this);
|
||||
this.inputRef = React.createRef();
|
||||
|
||||
this.debouncedSearch = debounce(this.search.bind(this), 20);
|
||||
}
|
||||
|
||||
public search() {
|
||||
const { searchTerm, search, i18n, ourNumber, regionCode } = this.props;
|
||||
public componentDidUpdate(prevProps: PropsType) {
|
||||
const { searchConversationId } = this.props;
|
||||
|
||||
// When user chooses to search in a given conversation we focus the field for them
|
||||
if (
|
||||
searchConversationId &&
|
||||
searchConversationId !== prevProps.searchConversationId
|
||||
) {
|
||||
this.setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line member-ordering
|
||||
public search = debounce((searchTerm: string) => {
|
||||
const {
|
||||
i18n,
|
||||
ourNumber,
|
||||
regionCode,
|
||||
search,
|
||||
searchConversationId,
|
||||
} = this.props;
|
||||
|
||||
if (search) {
|
||||
search(searchTerm, {
|
||||
searchConversationId,
|
||||
noteToSelf: i18n('noteToSelf').toLowerCase(),
|
||||
ourNumber,
|
||||
regionCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 50);
|
||||
|
||||
public updateSearch(event: React.FormEvent<HTMLInputElement>) {
|
||||
const { updateSearchTerm, clearSearch } = this.props;
|
||||
public updateSearch = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
const {
|
||||
updateSearchTerm,
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
searchConversationId,
|
||||
} = this.props;
|
||||
const searchTerm = event.currentTarget.value;
|
||||
|
||||
if (!searchTerm) {
|
||||
clearSearch();
|
||||
if (searchConversationId) {
|
||||
clearConversationSearch();
|
||||
} else {
|
||||
clearSearch();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -88,47 +107,82 @@ export class MainHeader extends React.Component<Props> {
|
|||
return;
|
||||
}
|
||||
|
||||
const cleanedTerm = cleanSearchTerm(searchTerm);
|
||||
if (!cleanedTerm) {
|
||||
return;
|
||||
}
|
||||
this.search(searchTerm);
|
||||
};
|
||||
|
||||
this.debouncedSearch(cleanedTerm);
|
||||
}
|
||||
|
||||
public clearSearch() {
|
||||
public clearSearch = () => {
|
||||
const { clearSearch } = this.props;
|
||||
|
||||
clearSearch();
|
||||
this.setFocus();
|
||||
}
|
||||
};
|
||||
|
||||
public handleKeyUp(event: React.KeyboardEvent<HTMLInputElement>) {
|
||||
const { clearSearch } = this.props;
|
||||
public clearConversationSearch = () => {
|
||||
const { clearConversationSearch } = this.props;
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
clearConversationSearch();
|
||||
this.setFocus();
|
||||
};
|
||||
|
||||
public handleKeyUp = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const {
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
searchConversationId,
|
||||
searchTerm,
|
||||
} = this.props;
|
||||
|
||||
if (event.key !== 'Escape') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchConversationId && searchTerm) {
|
||||
clearConversationSearch();
|
||||
} else {
|
||||
clearSearch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public setFocus() {
|
||||
public handleXButton = () => {
|
||||
const {
|
||||
searchConversationId,
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
} = this.props;
|
||||
|
||||
if (searchConversationId) {
|
||||
clearConversationSearch();
|
||||
} else {
|
||||
clearSearch();
|
||||
}
|
||||
|
||||
this.setFocus();
|
||||
};
|
||||
|
||||
public setFocus = () => {
|
||||
if (this.inputRef.current) {
|
||||
// @ts-ignore
|
||||
this.inputRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
const {
|
||||
searchTerm,
|
||||
avatarPath,
|
||||
i18n,
|
||||
color,
|
||||
i18n,
|
||||
name,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
searchConversationId,
|
||||
searchConversationName,
|
||||
searchTerm,
|
||||
} = this.props;
|
||||
|
||||
const placeholder = searchConversationName
|
||||
? i18n('searchIn', [searchConversationName])
|
||||
: i18n('search');
|
||||
|
||||
return (
|
||||
<div className="module-main-header">
|
||||
<Avatar
|
||||
|
@ -142,26 +196,42 @@ export class MainHeader extends React.Component<Props> {
|
|||
size={28}
|
||||
/>
|
||||
<div className="module-main-header__search">
|
||||
<div
|
||||
role="button"
|
||||
className="module-main-header__search__icon"
|
||||
onClick={this.setFocusBound}
|
||||
/>
|
||||
{searchConversationId ? (
|
||||
<div className="module-main-header__search__in-conversation-pill">
|
||||
<div className="module-main-header__search__in-conversation-pill__avatar-container">
|
||||
<div className="module-main-header__search__in-conversation-pill__avatar" />
|
||||
</div>
|
||||
<button
|
||||
className="module-main-header__search__in-conversation-pill__x-button"
|
||||
onClick={this.clearSearch}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="module-main-header__search__icon"
|
||||
onClick={this.setFocus}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
type="text"
|
||||
ref={this.inputRef}
|
||||
className="module-main-header__search__input"
|
||||
placeholder={i18n('search')}
|
||||
className={classNames(
|
||||
'module-main-header__search__input',
|
||||
searchConversationId
|
||||
? 'module-main-header__search__input--in-conversation'
|
||||
: null
|
||||
)}
|
||||
placeholder={placeholder}
|
||||
dir="auto"
|
||||
onKeyUp={this.handleKeyUpBound}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
value={searchTerm}
|
||||
onChange={this.updateSearchBound}
|
||||
onChange={this.updateSearch}
|
||||
/>
|
||||
{searchTerm ? (
|
||||
<div
|
||||
role="button"
|
||||
className="module-main-header__search__cancel-icon"
|
||||
onClick={this.clearSearchBound}
|
||||
onClick={this.handleXButton}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue