Add back support for Winning Uri

This commit is contained in:
Rafael 2021-12-07 15:17:29 -03:00 committed by Thomas Zarebczan
parent c2a3698015
commit 5feaa30e58
7 changed files with 64 additions and 34 deletions

View file

@ -32,6 +32,7 @@ declare type SearchState = {
results: Array<string>, results: Array<string>,
hasReachedMaxResultsLength: {}, hasReachedMaxResultsLength: {},
searching: boolean, searching: boolean,
mentionQuery: string,
}; };
declare type SearchSuccess = { declare type SearchSuccess = {
@ -42,6 +43,7 @@ declare type SearchSuccess = {
size: number, size: number,
uris: Array<string>, uris: Array<string>,
recsys: string, recsys: string,
query: string,
}, },
}; };

View file

@ -1,17 +1,18 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doResolveUris } from 'redux/actions/claims'; import { doResolveUris } from 'redux/actions/claims';
import { doSetMentionSearchResults } from 'redux/actions/search';
import { makeSelectWinningUriForQuery } from 'redux/selectors/search';
import { MAX_LIVESTREAM_COMMENTS } from 'constants/livestream'; import { MAX_LIVESTREAM_COMMENTS } from 'constants/livestream';
import { selectChannelMentionData } from 'redux/selectors/comments'; import { selectChannelMentionData } from 'redux/selectors/comments';
import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectShowMatureContent } from 'redux/selectors/settings';
import { doSetMentionSearchResults } from 'redux/actions/search';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import TextareaWithSuggestions from './view'; import TextareaWithSuggestions from './view';
const select = (state, props) => { const select = (state, props) => {
const { pathname } = props.location; const { pathname } = props.location;
const maxComments = props.isLivestream ? MAX_LIVESTREAM_COMMENTS : -1;
const uri = `lbry:/${pathname.replaceAll(':', '#')}`; const uri = `lbry:/${pathname.replaceAll(':', '#')}`;
const maxComments = props.isLivestream ? MAX_LIVESTREAM_COMMENTS : -1;
const data = selectChannelMentionData(state, uri, maxComments); const data = selectChannelMentionData(state, uri, maxComments);
const { const {
canonicalCommentors, canonicalCommentors,
@ -20,6 +21,7 @@ const select = (state, props) => {
canonicalSubscriptions, canonicalSubscriptions,
commentorUris, commentorUris,
hasNewResolvedResults, hasNewResolvedResults,
query,
} = data; } = data;
return { return {
@ -27,15 +29,17 @@ const select = (state, props) => {
canonicalCreatorUri, canonicalCreatorUri,
canonicalSearch, canonicalSearch,
canonicalSubscriptions, canonicalSubscriptions,
canonicalTop: makeSelectWinningUriForQuery(query)(state),
commentorUris, commentorUris,
hasNewResolvedResults, hasNewResolvedResults,
searchQuery: query,
showMature: selectShowMatureContent(state), showMature: selectShowMatureContent(state),
}; };
}; };
const perform = (dispatch) => ({ const perform = (dispatch) => ({
doResolveUris: (uris) => dispatch(doResolveUris(uris, true)), doResolveUris: (uris) => dispatch(doResolveUris(uris, true)),
doSetMentionSearchResults: (uris) => dispatch(doSetMentionSearchResults(uris)), doSetMentionSearchResults: (query, uris) => dispatch(doSetMentionSearchResults(query, uris)),
}); });
export default withRouter(connect(select, perform)(TextareaWithSuggestions)); export default withRouter(connect(select, perform)(TextareaWithSuggestions));

View file

@ -6,6 +6,7 @@ import * as KEYCODES from 'constants/keycodes';
import Autocomplete from '@mui/material/Autocomplete'; import Autocomplete from '@mui/material/Autocomplete';
import BusyIndicator from 'component/common/busy-indicator'; import BusyIndicator from 'component/common/busy-indicator';
import EMOJIS from 'emoji-dictionary'; import EMOJIS from 'emoji-dictionary';
import LbcSymbol from 'component/common/lbc-symbol';
import Popper from '@mui/material/Popper'; import Popper from '@mui/material/Popper';
import React from 'react'; import React from 'react';
import TextareaSuggestionsItem from 'component/textareaSuggestionsItem'; import TextareaSuggestionsItem from 'component/textareaSuggestionsItem';
@ -40,6 +41,7 @@ type Props = {
canonicalCreatorUri?: string, canonicalCreatorUri?: string,
canonicalSearch?: Array<string>, canonicalSearch?: Array<string>,
canonicalSubscriptions?: Array<string>, canonicalSubscriptions?: Array<string>,
canonicalTop?: string,
className?: string, className?: string,
commentorUris?: Array<string>, commentorUris?: Array<string>,
disabled?: boolean, disabled?: boolean,
@ -49,12 +51,13 @@ type Props = {
isLivestream?: boolean, isLivestream?: boolean,
maxLength?: number, maxLength?: number,
placeholder?: string, placeholder?: string,
searchQuery?: string,
showMature: boolean, showMature: boolean,
type?: string, type?: string,
uri?: string, uri?: string,
value: any, value: any,
doResolveUris: (Array<string>) => void, doResolveUris: (Array<string>) => void,
doSetMentionSearchResults: (Array<string>) => void, doSetMentionSearchResults: (string, Array<string>) => void,
onBlur: (any) => any, onBlur: (any) => any,
onChange: (any) => any, onChange: (any) => any,
onFocus: (any) => any, onFocus: (any) => any,
@ -66,6 +69,7 @@ export default function TextareaWithSuggestions(props: Props) {
canonicalCreatorUri, canonicalCreatorUri,
canonicalSearch, canonicalSearch,
canonicalSubscriptions: canonicalSubs, canonicalSubscriptions: canonicalSubs,
canonicalTop,
className, className,
commentorUris, commentorUris,
disabled, disabled,
@ -75,6 +79,7 @@ export default function TextareaWithSuggestions(props: Props) {
isLivestream, isLivestream,
maxLength, maxLength,
placeholder, placeholder,
searchQuery,
showMature, showMature,
type, type,
value: messageValue, value: messageValue,
@ -88,7 +93,6 @@ export default function TextareaWithSuggestions(props: Props) {
const inputDefaultProps = { className, placeholder, maxLength, type, disabled }; const inputDefaultProps = { className, placeholder, maxLength, type, disabled };
const [suggestionValue, setSuggestionValue] = React.useState(undefined); const [suggestionValue, setSuggestionValue] = React.useState(undefined);
const [selectedValue, setSelectedValue] = React.useState(undefined);
const [highlightedSuggestion, setHighlightedSuggestion] = React.useState(''); const [highlightedSuggestion, setHighlightedSuggestion] = React.useState('');
const [shouldClose, setClose] = React.useState(); const [shouldClose, setClose] = React.useState();
const [debouncedTerm, setDebouncedTerm] = React.useState(''); const [debouncedTerm, setDebouncedTerm] = React.useState('');
@ -110,9 +114,16 @@ export default function TextareaWithSuggestions(props: Props) {
const shouldFilter = (uri, previous) => uri !== canonicalCreatorUri && (!previous || !previous.includes(uri)); const shouldFilter = (uri, previous) => uri !== canonicalCreatorUri && (!previous || !previous.includes(uri));
const filteredCommentors = canonicalCommentors && canonicalCommentors.filter((uri) => shouldFilter(uri)); const filteredCommentors = canonicalCommentors && canonicalCommentors.filter((uri) => shouldFilter(uri));
const filteredSubs = canonicalSubs && canonicalSubs.filter((uri) => shouldFilter(uri, filteredCommentors)); const filteredSubs = canonicalSubs && canonicalSubs.filter((uri) => shouldFilter(uri, filteredCommentors));
const filteredTop =
canonicalTop &&
shouldFilter(canonicalTop, filteredSubs) &&
shouldFilter(canonicalTop, filteredCommentors) &&
canonicalTop;
const filteredSearch = const filteredSearch =
canonicalSearch && canonicalSearch &&
canonicalSearch.filter((uri) => shouldFilter(uri, filteredSubs) && shouldFilter(uri, filteredCommentors)); canonicalSearch.filter(
(uri) => shouldFilter(uri, filteredSubs) && shouldFilter(uri, filteredCommentors) && uri !== filteredTop
);
let emoteNames; let emoteNames;
let emojiNames; let emojiNames;
@ -128,6 +139,7 @@ export default function TextareaWithSuggestions(props: Props) {
if (canonicalCreatorUri) allOptions.push(canonicalCreatorUri); if (canonicalCreatorUri) allOptions.push(canonicalCreatorUri);
if (filteredSubs) allOptions.push(...filteredSubs); if (filteredSubs) allOptions.push(...filteredSubs);
if (filteredCommentors) allOptions.push(...filteredCommentors); if (filteredCommentors) allOptions.push(...filteredCommentors);
if (filteredTop) allOptions.push(filteredTop);
if (filteredSearch) allOptions.push(...filteredSearch); if (filteredSearch) allOptions.push(...filteredSearch);
} }
@ -139,6 +151,7 @@ export default function TextareaWithSuggestions(props: Props) {
: (canonicalCreatorUri === option && __('Creator')) || : (canonicalCreatorUri === option && __('Creator')) ||
(filteredSubs && filteredSubs.includes(option) && __('Following')) || (filteredSubs && filteredSubs.includes(option) && __('Following')) ||
(filteredCommentors && filteredCommentors.includes(option) && __('From Comments')) || (filteredCommentors && filteredCommentors.includes(option) && __('From Comments')) ||
(filteredTop && filteredTop === option && 'Top') ||
(filteredSearch && filteredSearch.includes(option) && __('From Search')); (filteredSearch && filteredSearch.includes(option) && __('From Search'));
let emoteLabel; let emoteLabel;
@ -154,10 +167,11 @@ export default function TextareaWithSuggestions(props: Props) {
}) })
: []; : [];
const allMatches = useSuggestionMatch( const allMatches =
suggestionTerm || '', useSuggestionMatch(
allOptionsGrouped.map(({ label }) => label) suggestionTerm || '',
); allOptionsGrouped.map(({ label }) => label)
) || [];
/** --------- **/ /** --------- **/
/** Functions **/ /** Functions **/
@ -171,7 +185,7 @@ export default function TextareaWithSuggestions(props: Props) {
const suggestionMatches = value.match(SUGGESTION_REGEX); const suggestionMatches = value.match(SUGGESTION_REGEX);
if (!suggestionMatches) { if (!suggestionMatches) {
if (suggestionValue) setSuggestionValue(undefined); if (suggestionValue) setSuggestionValue(null);
return; // Exit here and avoid unnecessary behavior return; // Exit here and avoid unnecessary behavior
} }
@ -226,13 +240,13 @@ export default function TextareaWithSuggestions(props: Props) {
lastIndex: currentLastIndex, lastIndex: currentLastIndex,
isEmote, isEmote,
}); });
} else if (suggestionValue) {
setSuggestionValue(null);
} }
} }
const handleSelect = React.useCallback( const handleSelect = React.useCallback(
(selectedValue: string) => { (selectedValue: string) => {
setSelectedValue(selectedValue);
if (!suggestionValue) return; if (!suggestionValue) return;
const elem = inputRef && inputRef.current; const elem = inputRef && inputRef.current;
@ -248,7 +262,7 @@ export default function TextareaWithSuggestions(props: Props) {
const newValue = contentBegin + replaceValue + contentEnd; const newValue = contentBegin + replaceValue + contentEnd;
onChange({ target: { value: newValue } }); onChange({ target: { value: newValue } });
setSuggestionValue(undefined); setSuggestionValue(null);
elem.focus(); elem.focus();
elem.setSelectionRange(newCursorPos, newCursorPos); elem.setSelectionRange(newCursorPos, newCursorPos);
}, },
@ -262,22 +276,24 @@ export default function TextareaWithSuggestions(props: Props) {
React.useEffect(() => { React.useEffect(() => {
if (!isMention) return; if (!isMention) return;
const timer = setTimeout(() => { if (isTyping && suggestionTerm) {
if (isTyping && suggestionTerm) setDebouncedTerm(!hasMinLength ? '' : suggestionTerm); const timer = setTimeout(() => {
}, INPUT_DEBOUNCE_MS); setDebouncedTerm(!hasMinLength ? '' : suggestionTerm);
}, INPUT_DEBOUNCE_MS);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}
}, [hasMinLength, isMention, isTyping, suggestionTerm]); }, [hasMinLength, isMention, isTyping, suggestionTerm]);
React.useEffect(() => { React.useEffect(() => {
if (!stringifiedResults) return; if (!stringifiedResults) return;
const arrayResults = JSON.parse(stringifiedResults); const arrayResults = JSON.parse(stringifiedResults);
if (arrayResults && arrayResults.length > 0) { if (debouncedTerm && arrayResults && arrayResults.length > 0) {
doResolveUris(arrayResults); doResolveUris([debouncedTerm, ...arrayResults]);
doSetMentionSearchResults(arrayResults); doSetMentionSearchResults(debouncedTerm, arrayResults);
} }
}, [doResolveUris, doSetMentionSearchResults, stringifiedResults]); }, [debouncedTerm, doResolveUris, doSetMentionSearchResults, stringifiedResults, suggestionTerm]);
// Disable sending on Enter on Livestream chat // Disable sending on Enter on Livestream chat
React.useEffect(() => { React.useEffect(() => {
@ -320,9 +336,13 @@ export default function TextareaWithSuggestions(props: Props) {
const renderGroup = (groupName: string, children: any) => ( const renderGroup = (groupName: string, children: any) => (
<div key={groupName} className="textareaSuggestions__group"> <div key={groupName} className="textareaSuggestions__group">
<label className="textareaSuggestions__label"> <label className="textareaSuggestions__label">
{suggestionTerm && suggestionTerm.length > 1 {groupName === 'Top' ? (
? __('%group_name% matching %matching_term%', { group_name: groupName, matching_term: suggestionTerm }) <LbcSymbol prefix={__('Winning Search for %matching_term%', { matching_term: searchQuery })} />
: groupName} ) : suggestionTerm && suggestionTerm.length > 1 ? (
__('%group_name% matching %matching_term%', { group_name: groupName, matching_term: suggestionTerm })
) : (
groupName
)}
</label> </label>
{children} {children}
<hr className="textareaSuggestions__topSeparator" /> <hr className="textareaSuggestions__topSeparator" />
@ -351,14 +371,14 @@ export default function TextareaWithSuggestions(props: Props) {
PopperComponent={AutocompletePopper} PopperComponent={AutocompletePopper}
autoHighlight autoHighlight
disableClearable disableClearable
filterOptions={(options) => options.filter(({ label }) => allMatches && allMatches.includes(label))} filterOptions={(options) => options.filter(({ label }) => allMatches.includes(label))}
freeSolo freeSolo
fullWidth fullWidth
getOptionLabel={(option) => option.label} getOptionLabel={(option) => option.label || ''}
groupBy={(option) => option.group} groupBy={(option) => option.group}
id={id} id={id}
inputValue={messageValue} inputValue={messageValue}
loading={!allMatches || allMatches.length === 0 || showPlaceholder} loading={allMatches.length === 0 || showPlaceholder}
loadingText={showPlaceholder ? <BusyIndicator message={__('Searching...')} /> : __('Nothing found')} loadingText={showPlaceholder ? <BusyIndicator message={__('Searching...')} /> : __('Nothing found')}
onBlur={() => onBlur && onBlur()} onBlur={() => onBlur && onBlur()}
/* Different from onInputChange, onChange is only used for the selected value, /* Different from onInputChange, onChange is only used for the selected value,
@ -376,7 +396,6 @@ export default function TextareaWithSuggestions(props: Props) {
renderGroup={({ group, children }) => renderGroup(group, children)} renderGroup={({ group, children }) => renderGroup(group, children)}
renderInput={(params) => renderInput(params)} renderInput={(params) => renderInput(params)}
renderOption={(optionProps, option) => renderOption(optionProps, option.label)} renderOption={(optionProps, option) => renderOption(optionProps, option.label)}
value={selectedValue}
/> />
); );
} }

View file

@ -147,10 +147,10 @@ export const doUpdateSearchOptions = (newOptions: SearchOptions, additionalOptio
} }
}; };
export const doSetMentionSearchResults = (uris: Array<string>) => (dispatch: Dispatch) => { export const doSetMentionSearchResults = (query: string, uris: Array<string>) => (dispatch: Dispatch) => {
dispatch({ dispatch({
type: ACTIONS.SET_MENTION_SEARCH_RESULTS, type: ACTIONS.SET_MENTION_SEARCH_RESULTS,
data: { uris }, data: { query, uris },
}); });
}; };

View file

@ -21,6 +21,7 @@ const defaultState: SearchState = {
hasReachedMaxResultsLength: {}, hasReachedMaxResultsLength: {},
searching: false, searching: false,
results: [], results: [],
mentionQuery: '',
}; };
export default handleActions( export default handleActions(
@ -71,6 +72,7 @@ export default handleActions(
[ACTIONS.SET_MENTION_SEARCH_RESULTS]: (state: SearchState, action: SearchSuccess): SearchState => ({ [ACTIONS.SET_MENTION_SEARCH_RESULTS]: (state: SearchState, action: SearchSuccess): SearchState => ({
...state, ...state,
results: action.data.uris, results: action.data.uris,
mentionQuery: action.data.query,
}), }),
}, },
defaultState defaultState

View file

@ -3,7 +3,7 @@ import { createSelector } from 'reselect';
import { createCachedSelector } from 're-reselect'; import { createCachedSelector } from 're-reselect';
import { selectMutedChannels } from 'redux/selectors/blocked'; import { selectMutedChannels } from 'redux/selectors/blocked';
import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectShowMatureContent } from 'redux/selectors/settings';
import { selectMentionSearchResults } from 'redux/selectors/search'; import { selectMentionSearchResults, selectMentionQuery } from 'redux/selectors/search';
import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc'; import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc';
import { import {
selectClaimsById, selectClaimsById,
@ -399,7 +399,8 @@ export const selectChannelMentionData = createCachedSelector(
selectTopLevelCommentsForUri, selectTopLevelCommentsForUri,
selectSubscriptionUris, selectSubscriptionUris,
selectMentionSearchResults, selectMentionSearchResults,
(uri, claimIdsByUri, claimsById, topLevelComments, subscriptionUris, searchUris) => { selectMentionQuery,
(uri, claimIdsByUri, claimsById, topLevelComments, subscriptionUris, searchUris, query) => {
let canonicalCreatorUri; let canonicalCreatorUri;
const commentorUris = []; const commentorUris = [];
const canonicalCommentors = []; const canonicalCommentors = [];
@ -453,10 +454,11 @@ export const selectChannelMentionData = createCachedSelector(
return { return {
canonicalCommentors, canonicalCommentors,
canonicalCreatorUri, canonicalCreatorUri,
canonicalSearch,
canonicalSubscriptions, canonicalSubscriptions,
commentorUris, commentorUris,
hasNewResolvedResults, hasNewResolvedResults,
canonicalSearch, query,
}; };
} }
)((state, uri, maxCount) => `${String(uri)}:${maxCount}`); )((state, uri, maxCount) => `${String(uri)}:${maxCount}`);

View file

@ -33,6 +33,7 @@ export const selectSearchResultByQuery: (state: State) => { [string]: Array<stri
export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Array<boolean> } = (state) => export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Array<boolean> } = (state) =>
selectState(state).hasReachedMaxResultsLength; selectState(state).hasReachedMaxResultsLength;
export const selectMentionSearchResults: (state: State) => Array<string> = (state) => selectState(state).results; export const selectMentionSearchResults: (state: State) => Array<string> = (state) => selectState(state).results;
export const selectMentionQuery: (state: State) => string = (state) => selectState(state).mentionQuery;
export const makeSelectSearchUrisForQuery = (query: string): ((state: State) => Array<string>) => export const makeSelectSearchUrisForQuery = (query: string): ((state: State) => Array<string>) =>
createSelector(selectSearchResultByQuery, (byQuery) => { createSelector(selectSearchResultByQuery, (byQuery) => {