diff --git a/ui/component/textareaWithSuggestions/index.js b/ui/component/textareaWithSuggestions/index.js index ed8afcd87..3bd51dfbd 100644 --- a/ui/component/textareaWithSuggestions/index.js +++ b/ui/component/textareaWithSuggestions/index.js @@ -3,7 +3,7 @@ import { doResolveUris } from 'redux/actions/claims'; import { MAX_LIVESTREAM_COMMENTS } from 'constants/livestream'; import { selectChannelMentionData } from 'redux/selectors/comments'; import { selectShowMatureContent } from 'redux/selectors/settings'; -import { doSetSearchResults } from 'redux/actions/search'; +import { doSetMentionSearchResults } from 'redux/actions/search'; import { withRouter } from 'react-router'; import TextareaWithSuggestions from './view'; @@ -13,7 +13,14 @@ const select = (state, props) => { const uri = `lbry:/${pathname.replaceAll(':', '#')}`; const data = selectChannelMentionData(state, uri, maxComments); - const { canonicalCommentors, canonicalCreatorUri, canonicalSubscriptions, commentorUris, canonicalSearch } = data; + const { + canonicalCommentors, + canonicalCreatorUri, + canonicalSearch, + canonicalSubscriptions, + commentorUris, + hasNewResolvedResults, + } = data; return { canonicalCommentors, @@ -21,13 +28,14 @@ const select = (state, props) => { canonicalSearch, canonicalSubscriptions, commentorUris, + hasNewResolvedResults, showMature: selectShowMatureContent(state), }; }; const perform = (dispatch) => ({ doResolveUris: (uris) => dispatch(doResolveUris(uris, true)), - doSetSearchResults: (uris) => dispatch(doSetSearchResults(uris)), + doSetMentionSearchResults: (uris) => dispatch(doSetMentionSearchResults(uris)), }); export default withRouter(connect(select, perform)(TextareaWithSuggestions)); diff --git a/ui/component/textareaWithSuggestions/view.jsx b/ui/component/textareaWithSuggestions/view.jsx index 05bd429d0..bd943a769 100644 --- a/ui/component/textareaWithSuggestions/view.jsx +++ b/ui/component/textareaWithSuggestions/view.jsx @@ -4,11 +4,12 @@ import { matchSorter } from 'match-sorter'; import { SEARCH_OPTIONS } from 'constants/search'; import * as KEYCODES from 'constants/keycodes'; import Autocomplete from '@mui/material/Autocomplete'; +import BusyIndicator from 'component/common/busy-indicator'; import EMOJIS from 'emoji-dictionary'; +import Popper from '@mui/material/Popper'; import React from 'react'; import TextareaSuggestionsItem from 'component/textareaSuggestionsItem'; import TextField from '@mui/material/TextField'; -import Popper from '@mui/material/Popper'; import useLighthouse from 'effects/use-lighthouse'; import useThrottle from 'effects/use-throttle'; @@ -42,6 +43,7 @@ type Props = { className?: string, commentorUris?: Array, disabled?: boolean, + hasNewResolvedResults?: boolean, id: string, inputRef: any, isLivestream?: boolean, @@ -52,7 +54,7 @@ type Props = { uri?: string, value: any, doResolveUris: (Array) => void, - doSetSearchResults: (Array) => void, + doSetMentionSearchResults: (Array) => void, onBlur: (any) => any, onChange: (any) => any, onFocus: (any) => any, @@ -67,6 +69,7 @@ export default function TextareaWithSuggestions(props: Props) { className, commentorUris, disabled, + hasNewResolvedResults, id, inputRef, isLivestream, @@ -76,7 +79,7 @@ export default function TextareaWithSuggestions(props: Props) { type, value: messageValue, doResolveUris, - doSetSearchResults, + doSetMentionSearchResults, onBlur, onChange, onFocus, @@ -101,7 +104,8 @@ export default function TextareaWithSuggestions(props: Props) { const hasMinLength = suggestionTerm && isMention && suggestionTerm.length >= LIGHTHOUSE_MIN_CHARACTERS; const isTyping = isMention && debouncedTerm !== suggestionTerm; - const showPlaceholder = isMention && (isTyping || loading); + const showPlaceholder = + isMention && (isTyping || loading || (results && results.length > 0 && !hasNewResolvedResults)); const shouldFilter = (uri, previous) => uri !== canonicalCreatorUri && (!previous || !previous.includes(uri)); const filteredCommentors = canonicalCommentors && canonicalCommentors.filter((uri) => shouldFilter(uri)); @@ -271,9 +275,9 @@ export default function TextareaWithSuggestions(props: Props) { const arrayResults = JSON.parse(stringifiedResults); if (arrayResults && arrayResults.length > 0) { doResolveUris(arrayResults); - doSetSearchResults(arrayResults); + doSetMentionSearchResults(arrayResults); } - }, [doResolveUris, doSetSearchResults, stringifiedResults]); + }, [doResolveUris, doSetMentionSearchResults, stringifiedResults]); // Disable sending on Enter on Livestream chat React.useEffect(() => { @@ -355,7 +359,7 @@ export default function TextareaWithSuggestions(props: Props) { id={id} inputValue={messageValue} loading={!allMatches || allMatches.length === 0 || showPlaceholder} - loadingText={results || showPlaceholder ? __('Searching...') : __('Nothing found')} + loadingText={showPlaceholder ? : __('Nothing found')} onBlur={() => onBlur && onBlur()} /* Different from onInputChange, onChange is only used for the selected value, so here it is acting simply as a selection handler (see it as onSelect) */ @@ -366,7 +370,7 @@ export default function TextareaWithSuggestions(props: Props) { onInputChange={(event, value, reason) => reason === 'input' && handleInputChange(value)} onOpen={() => suggestionTerm && setClose(false)} /* 'open' is for the popper box component, set to check for a valid term - or else it will be displayed all the time as empty */ + or else it will be displayed all the time as empty (no options) */ open={!!suggestionTerm && !shouldClose} options={allOptionsGrouped} renderGroup={({ group, children }) => renderGroup(group, children)} diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js index 62ce2ba7f..6054e5365 100644 --- a/ui/constants/action_types.js +++ b/ui/constants/action_types.js @@ -230,7 +230,7 @@ export const SEARCH_SUCCESS = 'SEARCH_SUCCESS'; export const SEARCH_FAIL = 'SEARCH_FAIL'; export const UPDATE_SEARCH_OPTIONS = 'UPDATE_SEARCH_OPTIONS'; export const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS'; -export const SET_SEARCH_RESULTS = 'SET_SEARCH_RESULTS'; +export const SET_MENTION_SEARCH_RESULTS = 'SET_MENTION_SEARCH_RESULTS'; // Settings export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED'; diff --git a/ui/redux/actions/search.js b/ui/redux/actions/search.js index d7b358ba5..e629b4514 100644 --- a/ui/redux/actions/search.js +++ b/ui/redux/actions/search.js @@ -147,9 +147,9 @@ export const doUpdateSearchOptions = (newOptions: SearchOptions, additionalOptio } }; -export const doSetSearchResults = (uris: Array) => (dispatch: Dispatch) => { +export const doSetMentionSearchResults = (uris: Array) => (dispatch: Dispatch) => { dispatch({ - type: ACTIONS.SET_SEARCH_RESULTS, + type: ACTIONS.SET_MENTION_SEARCH_RESULTS, data: { uris }, }); }; diff --git a/ui/redux/reducers/search.js b/ui/redux/reducers/search.js index 5973a376a..51eb96cff 100644 --- a/ui/redux/reducers/search.js +++ b/ui/redux/reducers/search.js @@ -68,7 +68,7 @@ export default handleActions( }; }, - [ACTIONS.SET_SEARCH_RESULTS]: (state: SearchState, action: SearchSuccess): SearchState => ({ + [ACTIONS.SET_MENTION_SEARCH_RESULTS]: (state: SearchState, action: SearchSuccess): SearchState => ({ ...state, results: action.data.uris, }), diff --git a/ui/redux/selectors/comments.js b/ui/redux/selectors/comments.js index d1cacea2c..83d1be4bb 100644 --- a/ui/redux/selectors/comments.js +++ b/ui/redux/selectors/comments.js @@ -3,7 +3,7 @@ import { createSelector } from 'reselect'; import { createCachedSelector } from 're-reselect'; import { selectMutedChannels } from 'redux/selectors/blocked'; import { selectShowMatureContent } from 'redux/selectors/settings'; -import { selectSearchResults } from 'redux/selectors/search'; +import { selectMentionSearchResults } from 'redux/selectors/search'; import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc'; import { selectClaimsById, @@ -398,7 +398,7 @@ export const selectChannelMentionData = createCachedSelector( selectClaimsById, selectTopLevelCommentsForUri, selectSubscriptionUris, - selectSearchResults, + selectMentionSearchResults, (uri, claimIdsByUri, claimsById, topLevelComments, subscriptionUris, searchUris) => { let canonicalCreatorUri; const commentorUris = []; @@ -437,6 +437,7 @@ export const selectChannelMentionData = createCachedSelector( } }); + let hasNewResolvedResults = false; if (searchUris && searchUris.length > 0) { searchUris.forEach((uri) => { // Update: canonicalSubscriptions @@ -446,8 +447,16 @@ export const selectChannelMentionData = createCachedSelector( canonicalSearch.push(claim.canonical_url); } }); + hasNewResolvedResults = canonicalSearch.length > 0; } - return { canonicalCommentors, canonicalCreatorUri, canonicalSubscriptions, commentorUris, canonicalSearch }; + return { + canonicalCommentors, + canonicalCreatorUri, + canonicalSubscriptions, + commentorUris, + hasNewResolvedResults, + canonicalSearch, + }; } )((state, uri, maxCount) => `${String(uri)}:${maxCount}`); diff --git a/ui/redux/selectors/search.js b/ui/redux/selectors/search.js index 19ba25a34..119d6e1ae 100644 --- a/ui/redux/selectors/search.js +++ b/ui/redux/selectors/search.js @@ -32,7 +32,7 @@ export const selectSearchResultByQuery: (state: State) => { [string]: Array { [boolean]: Array } = (state) => selectState(state).hasReachedMaxResultsLength; -export const selectSearchResults: (state: State) => Array = (state) => selectState(state).results; +export const selectMentionSearchResults: (state: State) => Array = (state) => selectState(state).results; export const makeSelectSearchUrisForQuery = (query: string): ((state: State) => Array) => createSelector(selectSearchResultByQuery, (byQuery) => { diff --git a/ui/scss/component/_textarea-suggestions.scss b/ui/scss/component/_textarea-suggestions.scss index 10bab7c88..7f35c3b4c 100644 --- a/ui/scss/component/_textarea-suggestions.scss +++ b/ui/scss/component/_textarea-suggestions.scss @@ -86,3 +86,7 @@ .textareaSuggestions__topSeparator { @extend .wunderbar__top-separator; } + +.MuiAutocomplete-loading { + color: var(--color-text) !important; +}