2022-02-14 17:57:11 +00:00
|
|
|
// Copyright 2021-2022 Signal Messenger, LLC
|
2021-11-01 18:43:02 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2022-02-14 17:57:11 +00:00
|
|
|
import React, { useEffect, useRef } from 'react';
|
2022-04-30 05:24:20 +00:00
|
|
|
import type {
|
|
|
|
ConversationType,
|
|
|
|
OpenConversationInternalType,
|
|
|
|
} from '../state/ducks/conversations';
|
2021-11-01 18:43:02 +00:00
|
|
|
import type { LocalizerType } from '../types/Util';
|
|
|
|
import { Avatar, AvatarSize } from './Avatar';
|
2022-02-14 17:57:11 +00:00
|
|
|
import { SearchInput } from './SearchInput';
|
|
|
|
import { usePrevious } from '../hooks/usePrevious';
|
2021-11-01 18:43:02 +00:00
|
|
|
|
|
|
|
type PropsType = {
|
2022-02-14 17:57:11 +00:00
|
|
|
clearConversationSearch: () => void;
|
|
|
|
clearSearch: () => void;
|
2021-11-01 18:43:02 +00:00
|
|
|
disabled?: boolean;
|
|
|
|
i18n: LocalizerType;
|
|
|
|
searchConversation?: ConversationType;
|
2022-02-14 17:57:11 +00:00
|
|
|
searchTerm: string;
|
|
|
|
startSearchCounter: number;
|
|
|
|
updateSearchTerm: (searchTerm: string) => void;
|
2022-04-30 05:24:20 +00:00
|
|
|
openConversationInternal: OpenConversationInternalType;
|
|
|
|
onEnterKeyDown?: (
|
|
|
|
clearSearch: () => void,
|
|
|
|
openConversationInternal: OpenConversationInternalType
|
|
|
|
) => void;
|
2021-11-01 18:43:02 +00:00
|
|
|
};
|
|
|
|
|
2022-02-14 17:57:11 +00:00
|
|
|
export const LeftPaneSearchInput = ({
|
|
|
|
clearConversationSearch,
|
|
|
|
clearSearch,
|
|
|
|
disabled,
|
|
|
|
i18n,
|
|
|
|
searchConversation,
|
|
|
|
searchTerm,
|
|
|
|
startSearchCounter,
|
|
|
|
updateSearchTerm,
|
2022-04-30 05:24:20 +00:00
|
|
|
openConversationInternal,
|
|
|
|
onEnterKeyDown,
|
2022-02-14 17:57:11 +00:00
|
|
|
}: PropsType): JSX.Element => {
|
|
|
|
const inputRef = useRef<null | HTMLInputElement>(null);
|
2021-11-01 18:43:02 +00:00
|
|
|
|
2022-02-14 17:57:11 +00:00
|
|
|
const prevSearchConversationId = usePrevious(
|
|
|
|
undefined,
|
|
|
|
searchConversation?.id
|
|
|
|
);
|
|
|
|
const prevSearchCounter = usePrevious(startSearchCounter, startSearchCounter);
|
2021-11-01 18:43:02 +00:00
|
|
|
|
2022-02-14 17:57:11 +00:00
|
|
|
useEffect(() => {
|
|
|
|
// When user chooses to search in a given conversation we focus the field for them
|
|
|
|
if (
|
|
|
|
searchConversation &&
|
|
|
|
searchConversation.id !== prevSearchConversationId
|
|
|
|
) {
|
|
|
|
inputRef.current?.focus();
|
|
|
|
}
|
|
|
|
// When user chooses to start a new search, we focus the field
|
|
|
|
if (startSearchCounter !== prevSearchCounter) {
|
|
|
|
inputRef.current?.select();
|
|
|
|
}
|
|
|
|
}, [
|
|
|
|
prevSearchConversationId,
|
|
|
|
prevSearchCounter,
|
|
|
|
searchConversation,
|
|
|
|
startSearchCounter,
|
|
|
|
]);
|
2021-11-01 18:43:02 +00:00
|
|
|
|
2022-02-14 17:57:11 +00:00
|
|
|
const changeValue = (nextSearchTerm: string) => {
|
|
|
|
if (!nextSearchTerm) {
|
|
|
|
if (searchConversation) {
|
|
|
|
clearConversationSearch();
|
|
|
|
} else {
|
|
|
|
clearSearch();
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (updateSearchTerm) {
|
|
|
|
updateSearchTerm(nextSearchTerm);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const clearAndFocus = () => {
|
|
|
|
clearSearch();
|
|
|
|
inputRef.current?.focus();
|
|
|
|
};
|
2021-11-01 18:43:02 +00:00
|
|
|
|
2022-02-14 17:57:11 +00:00
|
|
|
const label = i18n(searchConversation ? 'searchIn' : 'search');
|
|
|
|
|
|
|
|
return (
|
|
|
|
<SearchInput
|
|
|
|
disabled={disabled}
|
|
|
|
label={label}
|
|
|
|
hasSearchIcon={!searchConversation}
|
|
|
|
i18n={i18n}
|
|
|
|
moduleClassName="LeftPaneSearchInput"
|
|
|
|
onBlur={() => {
|
|
|
|
if (!searchConversation && !searchTerm) {
|
|
|
|
clearSearch();
|
|
|
|
}
|
|
|
|
}}
|
2022-04-30 05:24:20 +00:00
|
|
|
onKeyDown={event => {
|
|
|
|
if (onEnterKeyDown && event.key === 'Enter') {
|
|
|
|
onEnterKeyDown(clearSearch, openConversationInternal);
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
}
|
|
|
|
}}
|
2022-02-14 17:57:11 +00:00
|
|
|
onChange={event => {
|
|
|
|
changeValue(event.currentTarget.value);
|
|
|
|
}}
|
|
|
|
onClear={() => {
|
|
|
|
if (searchConversation && searchTerm) {
|
|
|
|
changeValue('');
|
|
|
|
} else {
|
|
|
|
clearAndFocus();
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
ref={inputRef}
|
|
|
|
placeholder={label}
|
|
|
|
value={searchTerm}
|
|
|
|
>
|
|
|
|
{searchConversation && (
|
|
|
|
// Clicking the non-X part of the pill should focus the input but have a normal
|
|
|
|
// cursor. This effectively simulates `pointer-events: none` while still
|
|
|
|
// letting us change the cursor.
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
|
|
<div
|
|
|
|
className="LeftPaneSearchInput__in-conversation-pill"
|
|
|
|
onClick={() => {
|
|
|
|
inputRef.current?.focus();
|
2021-11-01 18:43:02 +00:00
|
|
|
}}
|
2022-02-14 17:57:11 +00:00
|
|
|
>
|
|
|
|
<Avatar
|
|
|
|
acceptedMessageRequest={searchConversation.acceptedMessageRequest}
|
|
|
|
avatarPath={searchConversation.avatarPath}
|
|
|
|
badge={undefined}
|
|
|
|
color={searchConversation.color}
|
|
|
|
conversationType={searchConversation.type}
|
|
|
|
i18n={i18n}
|
|
|
|
isMe={searchConversation.isMe}
|
|
|
|
noteToSelf={searchConversation.isMe}
|
|
|
|
sharedGroupNames={searchConversation.sharedGroupNames}
|
|
|
|
size={AvatarSize.SIXTEEN}
|
|
|
|
title={searchConversation.title}
|
|
|
|
unblurredAvatarPath={searchConversation.unblurredAvatarPath}
|
|
|
|
/>
|
2021-11-01 18:43:02 +00:00
|
|
|
<button
|
2022-02-14 17:57:11 +00:00
|
|
|
aria-label={i18n('clearSearch')}
|
|
|
|
className="LeftPaneSearchInput__in-conversation-pill__x-button"
|
|
|
|
onClick={clearAndFocus}
|
2021-11-01 18:43:02 +00:00
|
|
|
type="button"
|
|
|
|
/>
|
2022-02-14 17:57:11 +00:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</SearchInput>
|
|
|
|
);
|
|
|
|
};
|