From dc6f19bb7dae22ee3150503da70dddd6b7f32ca7 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 14 Aug 2018 23:57:35 -0400 Subject: [PATCH] add search suggestions selector --- dist/bundle.js | 275 ++++++++++++++++++---------------- src/index.js | 1 + src/redux/actions/search.js | 98 ++---------- src/redux/reducers/search.js | 10 +- src/redux/selectors/search.js | 80 ++++++++++ 5 files changed, 247 insertions(+), 217 deletions(-) diff --git a/dist/bundle.js b/dist/bundle.js index 99cf246..f03b307 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -104,7 +104,7 @@ return /******/ (function(modules) { // webpackBootstrap Object.defineProperty(exports, "__esModule", { value: true }); -exports.selectWalletUnlockResult = exports.selectWalletUnlockSucceeded = exports.selectWalletUnlockPending = exports.selectWalletDecryptResult = exports.selectWalletDecryptSucceeded = exports.selectWalletDecryptPending = exports.selectWalletEncryptResult = exports.selectWalletEncryptSucceeded = exports.selectWalletEncryptPending = exports.selectWalletState = exports.selectWalletIsEncrypted = exports.selectBlocks = exports.selectDraftTransactionError = exports.selectDraftTransactionAddress = exports.selectDraftTransactionAmount = exports.selectDraftTransaction = exports.selectGettingNewAddress = exports.selectReceiveAddress = exports.selectIsSendingSupport = exports.selectIsFetchingTransactions = exports.selectHasTransactions = exports.selectRecentTransactions = exports.selectTransactionItems = exports.selectTransactionsById = exports.selectBalance = exports.makeSelectBlockDate = exports.selectSearchBarFocused = exports.selectWunderBarAddress = exports.selectSearchUrisByQuery = exports.selectIsSearching = exports.selectSearchValue = exports.selectSearchQuery = exports.makeSelectSearchUris = exports.selectSearchState = exports.selectNavLinks = exports.selectActiveHistoryEntry = exports.selectHistoryStack = exports.selectHistoryIndex = exports.selectIsForwardDisabled = exports.selectIsBackDisabled = exports.selectPathAfterAuth = exports.selectPageTitle = exports.selectHeaderLinks = exports.selectCurrentParams = exports.selectCurrentPage = exports.selectCurrentPath = exports.makeSelectCurrentParam = exports.computePageFromPath = exports.selectSearchDownloadUris = exports.selectTotalDownloadProgress = exports.selectDownloadingFileInfos = exports.selectFileInfosDownloaded = exports.selectUrisLoading = exports.selectDownloadingByOutpoint = exports.selectIsFetchingFileListDownloadedOrPublished = exports.selectIsFetchingFileList = exports.selectFileInfosByOutpoint = exports.makeSelectLoadingForUri = exports.makeSelectDownloadingForUri = exports.makeSelectFileInfoForUri = exports.selectFetchingCostInfo = exports.selectCostForCurrentPageUri = exports.selectAllCostInfoByUri = exports.makeSelectCostInfoForUri = exports.makeSelectFetchingCostInfoForUri = exports.selectRewardContentClaimIds = exports.selectChannelClaimCounts = exports.selectPlayingUri = exports.selectFetchingTrendingUris = exports.selectTrendingUris = undefined; +exports.selectWalletUnlockResult = exports.selectWalletUnlockSucceeded = exports.selectWalletUnlockPending = exports.selectWalletDecryptResult = exports.selectWalletDecryptSucceeded = exports.selectWalletDecryptPending = exports.selectWalletEncryptResult = exports.selectWalletEncryptSucceeded = exports.selectWalletEncryptPending = exports.selectWalletState = exports.selectWalletIsEncrypted = exports.selectBlocks = exports.selectDraftTransactionError = exports.selectDraftTransactionAddress = exports.selectDraftTransactionAmount = exports.selectDraftTransaction = exports.selectGettingNewAddress = exports.selectReceiveAddress = exports.selectIsSendingSupport = exports.selectIsFetchingTransactions = exports.selectHasTransactions = exports.selectRecentTransactions = exports.selectTransactionItems = exports.selectTransactionsById = exports.selectBalance = exports.makeSelectBlockDate = exports.selectSearchSuggestions = exports.selectSearchBarFocused = exports.selectWunderBarAddress = exports.selectSearchUrisByQuery = exports.selectIsSearching = exports.selectSearchValue = exports.selectSearchQuery = exports.makeSelectSearchUris = exports.selectSearchState = exports.selectNavLinks = exports.selectActiveHistoryEntry = exports.selectHistoryStack = exports.selectHistoryIndex = exports.selectIsForwardDisabled = exports.selectIsBackDisabled = exports.selectPathAfterAuth = exports.selectPageTitle = exports.selectHeaderLinks = exports.selectCurrentParams = exports.selectCurrentPage = exports.selectCurrentPath = exports.makeSelectCurrentParam = exports.computePageFromPath = exports.selectSearchDownloadUris = exports.selectTotalDownloadProgress = exports.selectDownloadingFileInfos = exports.selectFileInfosDownloaded = exports.selectUrisLoading = exports.selectDownloadingByOutpoint = exports.selectIsFetchingFileListDownloadedOrPublished = exports.selectIsFetchingFileList = exports.selectFileInfosByOutpoint = exports.makeSelectLoadingForUri = exports.makeSelectDownloadingForUri = exports.makeSelectFileInfoForUri = exports.selectFetchingCostInfo = exports.selectCostForCurrentPageUri = exports.selectAllCostInfoByUri = exports.makeSelectCostInfoForUri = exports.makeSelectFetchingCostInfoForUri = exports.selectRewardContentClaimIds = exports.selectChannelClaimCounts = exports.selectPlayingUri = exports.selectFetchingTrendingUris = exports.selectTrendingUris = undefined; exports.selectFetchingFeaturedUris = exports.selectFeaturedUris = exports.selectResolvingUris = exports.selectMyChannelClaims = exports.selectFetchingMyChannels = exports.selectMyClaimsOutpoints = exports.selectAllMyClaimsByOutpoint = exports.selectMyClaimsWithoutChannels = exports.selectMyClaims = exports.selectPendingClaims = exports.selectIsFetchingClaimListMine = exports.selectAllFetchingChannelClaims = exports.selectMyActiveClaims = exports.selectAbandoningIds = exports.selectMyClaimsRaw = exports.selectAllClaimsByChannel = exports.selectClaimsByUri = exports.selectClaimsById = exports.makeSelectRecommendedContentForUri = exports.makeSelectNsfwCountForChannel = exports.makeSelectNsfwCountFromUris = exports.makeSelectTotalPagesForChannel = exports.makeSelectTotalItemsForChannel = exports.makeSelectIsUriResolving = exports.makeSelectContentTypeForUri = exports.makeSelectTitleForUri = exports.makeSelectMetadataForUri = exports.makeSelectClaimsInChannelForPage = exports.makeSelectClaimsInChannelForCurrentPage = exports.makeSelectFetchingChannelClaims = exports.makeSelectClaimIsMine = exports.makeSelectClaimForUri = exports.selectSnack = exports.selectNotificationProps = exports.selectNotification = exports.selectBlackListedOutpoints = exports.blacklistReducer = exports.walletReducer = exports.searchReducer = exports.notificationsReducer = exports.fileInfoReducer = exports.costInfoReducer = exports.claimsReducer = exports.formatFullPrice = exports.formatCredits = exports.toQueryString = exports.parseQueryParams = exports.batchActions = exports.doWalletStatus = exports.doWalletUnlock = exports.doWalletDecrypt = exports.doWalletEncrypt = exports.doSendSupport = exports.doSetDraftTransactionAddress = exports.doSetDraftTransactionAmount = exports.doSendDraftTransaction = exports.doCheckAddressIsMine = exports.doGetNewAddress = exports.doFetchBlock = exports.doFetchTransactions = exports.doBalanceSubscribe = exports.doUpdateBalance = exports.doBlackListedOutpointsSubscribe = exports.doBlurSearchInput = exports.doFocusSearchInput = exports.doUpdateSearchQuery = exports.doSearch = exports.doFetchFileInfosAndPublishedClaims = exports.doFileList = exports.doFetchFileInfo = exports.doFetchCostInfoForUri = exports.doFetchRewardedContent = exports.doFetchTrendingUris = exports.doFetchFeaturedUris = exports.doResolveUri = exports.doResolveUris = exports.doAbandonClaim = exports.doFetchClaimListMine = exports.doFetchClaimCountByChannel = exports.doFetchClaimsByChannel = exports.doHideNotification = exports.doNotify = exports.convertToShareLink = exports.isNameValid = exports.isURIClaimable = exports.isURIValid = exports.normalizeURI = exports.buildURI = exports.parseURI = exports.regexAddress = exports.regexInvalidURI = exports.Lbryapi = exports.Lbry = exports.TRANSACTIONS = exports.SETTINGS = exports.SEARCH_TYPES = exports.THUMBNAIL_STATUSES = exports.MODALS = exports.ACTIONS = exports.Notification = undefined; var _Notification = __webpack_require__(1); @@ -245,7 +245,7 @@ Object.defineProperty(exports, 'doFetchRewardedContent', { } }); -var _cost_info = __webpack_require__(21); +var _cost_info = __webpack_require__(22); Object.defineProperty(exports, 'doFetchCostInfoForUri', { enumerable: true, @@ -254,7 +254,7 @@ Object.defineProperty(exports, 'doFetchCostInfoForUri', { } }); -var _file_info = __webpack_require__(22); +var _file_info = __webpack_require__(23); Object.defineProperty(exports, 'doFetchFileInfo', { enumerable: true, @@ -275,7 +275,7 @@ Object.defineProperty(exports, 'doFetchFileInfosAndPublishedClaims', { } }); -var _search = __webpack_require__(24); +var _search = __webpack_require__(25); Object.defineProperty(exports, 'doSearch', { enumerable: true, @@ -398,7 +398,7 @@ Object.defineProperty(exports, 'doWalletStatus', { } }); -var _batchActions = __webpack_require__(20); +var _batchActions = __webpack_require__(21); Object.defineProperty(exports, 'batchActions', { enumerable: true, @@ -788,7 +788,7 @@ Object.defineProperty(exports, 'selectFetchingCostInfo', { } }); -var _file_info3 = __webpack_require__(23); +var _file_info3 = __webpack_require__(24); Object.defineProperty(exports, 'makeSelectFileInfoForUri', { enumerable: true, @@ -994,6 +994,12 @@ Object.defineProperty(exports, 'selectSearchBarFocused', { return _search3.selectSearchBarFocused; } }); +Object.defineProperty(exports, 'selectSearchSuggestions', { + enumerable: true, + get: function get() { + return _search3.selectSearchSuggestions; + } +}); var _wallet3 = __webpack_require__(29); @@ -1166,7 +1172,7 @@ var _thumbnail_upload_statuses = __webpack_require__(44); var THUMBNAIL_STATUSES = _interopRequireWildcard(_thumbnail_upload_statuses); -var _search4 = __webpack_require__(25); +var _search4 = __webpack_require__(19); var SEARCH_TYPES = _interopRequireWildcard(_search4); @@ -1794,7 +1800,7 @@ var _notifications = __webpack_require__(3); var _claims = __webpack_require__(14); -var _batchActions = __webpack_require__(20); +var _batchActions = __webpack_require__(21); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -3026,7 +3032,7 @@ var _search = __webpack_require__(18); var _reselect = __webpack_require__(16); -var _claim = __webpack_require__(19); +var _claim = __webpack_require__(20); function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } @@ -3704,12 +3710,20 @@ function toQueryString(params) { Object.defineProperty(exports, "__esModule", { value: true }); -exports.selectSearchBarFocused = exports.selectWunderBarAddress = exports.makeSelectSearchUris = exports.selectSearchUrisByQuery = exports.selectIsSearching = exports.selectSearchQuery = exports.selectSearchValue = exports.selectState = undefined; +exports.selectSearchSuggestions = exports.selectSearchBarFocused = exports.selectWunderBarAddress = exports.makeSelectSearchUris = exports.selectSearchUrisByQuery = exports.selectIsSearching = exports.selectSearchQuery = exports.selectSuggestions = exports.selectSearchValue = exports.selectState = undefined; + +var _search = __webpack_require__(19); + +var SEARCH_TYPES = _interopRequireWildcard(_search); + +var _lbryURI = __webpack_require__(2); var _navigation = __webpack_require__(15); var _reselect = __webpack_require__(16); +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + var selectState = exports.selectState = function selectState(state) { return state.search || {}; }; @@ -3718,6 +3732,10 @@ var selectSearchValue = exports.selectSearchValue = (0, _reselect.createSelector return state.searchQuery; }); +var selectSuggestions = exports.selectSuggestions = (0, _reselect.createSelector)(selectState, function (state) { + return state.suggestions; +}); + var selectSearchQuery = exports.selectSearchQuery = (0, _reselect.createSelector)(_navigation.selectCurrentPage, _navigation.selectCurrentParams, function (page, params) { return page === 'search' ? params && params.query : null; }); @@ -3751,6 +3769,80 @@ var selectWunderBarAddress = exports.selectWunderBarAddress = (0, _reselect.crea var selectSearchBarFocused = exports.selectSearchBarFocused = (0, _reselect.createSelector)(selectState, function (state) { return state.focused; }); +// export const selectSear + +var selectSearchSuggestions = exports.selectSearchSuggestions = (0, _reselect.createSelector)(selectSearchValue, selectSuggestions, function (query, suggestions) { + if (!query) { + return []; + } + + var queryIsPrefix = query === 'lbry:' || query === 'lbry:/' || query === 'lbry://'; + + if (query.startsWith('lbry://') && query !== 'lbry://') { + // If it starts with a prefix, don't show any autocomplete results + // They are probably typing/pasting in a lbry uri + return [{ + value: query, + type: SEARCH_TYPES.FILE + }]; + } else if (queryIsPrefix) { + // If it is a prefix, wait until something else comes to figure out what to do + return []; + } + + var searchSuggestions = []; + try { + var uri = (0, _lbryURI.normalizeURI)(query); + + var _parseURI = (0, _lbryURI.parseURI)(uri), + claimName = _parseURI.claimName, + isChannel = _parseURI.isChannel; + + searchSuggestions.push({ + value: claimName, + type: SEARCH_TYPES.SEARCH + }, { + value: uri, + shorthand: isChannel ? claimName.slice(1) : claimName, + type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE + }); + } catch (e) { + searchSuggestions.push({ + value: query, + type: SEARCH_TYPES.SEARCH + }); + } + + var apiSuggestions = suggestions[query] || []; + if (apiSuggestions.length) { + searchSuggestions = searchSuggestions.concat(apiSuggestions.filter(function (suggestion) { + return suggestion !== query; + }).map(function (suggestion) { + // determine if it's a channel + try { + var _uri = (0, _lbryURI.normalizeURI)(suggestion); + + var _parseURI2 = (0, _lbryURI.parseURI)(_uri), + _claimName = _parseURI2.claimName, + _isChannel = _parseURI2.isChannel; + + return { + value: _uri, + shorthand: _isChannel ? _claimName.slice(1) : _claimName, + type: _isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE + }; + } catch (e) { + // search result includes some character that isn't valid in claim names + return { + value: suggestion, + type: SEARCH_TYPES.SEARCH + }; + } + })); + } + + return searchSuggestions; +}); /***/ }), /* 19 */ @@ -3759,6 +3851,20 @@ var selectSearchBarFocused = exports.selectSearchBarFocused = (0, _reselect.crea "use strict"; +Object.defineProperty(exports, "__esModule", { + value: true +}); +var FILE = exports.FILE = 'file'; +var CHANNEL = exports.CHANNEL = 'channel'; +var SEARCH = exports.SEARCH = 'search'; + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + Object.defineProperty(exports, "__esModule", { value: true }); @@ -3767,7 +3873,7 @@ var isClaimNsfw = exports.isClaimNsfw = function isClaimNsfw(claim) { }; /***/ }), -/* 20 */ +/* 21 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -3790,7 +3896,7 @@ function batchActions() { } /***/ }), -/* 21 */ +/* 22 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -3850,7 +3956,7 @@ function doFetchCostInfoForUri(uri) { } /***/ }), -/* 22 */ +/* 23 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -3875,7 +3981,7 @@ var _claims = __webpack_require__(5); var _claims2 = __webpack_require__(14); -var _file_info = __webpack_require__(23); +var _file_info = __webpack_require__(24); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -3943,7 +4049,7 @@ function doFetchFileInfosAndPublishedClaims() { } /***/ }), -/* 23 */ +/* 24 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4150,7 +4256,7 @@ var selectSearchDownloadUris = exports.selectSearchDownloadUris = function selec }; /***/ }), -/* 24 */ +/* 25 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -4165,17 +4271,13 @@ var _action_types = __webpack_require__(4); var ACTIONS = _interopRequireWildcard(_action_types); -var _search = __webpack_require__(25); - -var SEARCH_TYPES = _interopRequireWildcard(_search); - var _lbryURI = __webpack_require__(2); var _claims = __webpack_require__(5); -var _search2 = __webpack_require__(18); +var _search = __webpack_require__(18); -var _batchActions = __webpack_require__(20); +var _batchActions = __webpack_require__(21); var _handleFetch = __webpack_require__(26); @@ -4185,13 +4287,14 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } -var DEFAULTSEARCHRESULTSIZE = 10; // @flow - +// @flow +var DEFAULTSEARCHRESULTSIZE = 10; var DEFAULTSEARCHRESULTFROM = 0; var doSearch = exports.doSearch = function doSearch(rawQuery) { var size = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULTSEARCHRESULTSIZE; var from = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULTSEARCHRESULTFROM; + var isBackgroundSearch = arguments[3]; return function (dispatch, getState) { var state = getState(); var query = rawQuery.replace(/^lbry:\/\//i, ''); @@ -4204,7 +4307,7 @@ var doSearch = exports.doSearch = function doSearch(rawQuery) { } // If we have already searched for something, we don't need to do anything - var urisForQuery = (0, _search2.makeSelectSearchUris)(query)(state); + var urisForQuery = (0, _search.makeSelectSearchUris)(query)(state); if (urisForQuery && !!urisForQuery.length) { return; } @@ -4216,7 +4319,8 @@ var doSearch = exports.doSearch = function doSearch(rawQuery) { // If the user is on the file page with a pre-populated uri and they select // the search option without typing anything, searchQuery will be empty // We need to populate it so the input is filled on the search page - if (!state.search.searchQuery) { + // isBackgroundSearch means the search is happening in the background, don't update the search query + if (!state.search.searchQuery && !isBackgroundSearch) { dispatch({ type: ACTIONS.UPDATE_SEARCH_QUERY, data: { searchQuery: query } @@ -4252,103 +4356,29 @@ var doSearch = exports.doSearch = function doSearch(rawQuery) { }; var getSearchSuggestions = exports.getSearchSuggestions = function getSearchSuggestions(value /*: string*/) { - return function (dispatch) { + return function (dispatch, getState) { var query = value.trim(); - var isPrefix = function isPrefix() { - return query === '@' || query === 'lbry:' || query === 'lbry:/' || query === 'lbry://'; - }; - - if (!query || isPrefix()) { - dispatch({ - type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS, - data: { suggestions: [] } - }); - return; - } - - var suggestions = []; - try { - // If the user is about to manually add the claim id ignore it until they - // actually add one. This would hardly ever happen, but then the search - // suggestions won't change just from adding a '#' after a uri - var uriQuery = query; - if (uriQuery.endsWith('#')) { - uriQuery = uriQuery.slice(0, -1); - } - - var uri = (0, _lbryURI.normalizeURI)(uriQuery); - - var _parseURI = (0, _lbryURI.parseURI)(uri), - claimName = _parseURI.claimName, - isChannel = _parseURI.isChannel; - - suggestions.push({ - value: uri, - shorthand: isChannel ? claimName.slice(1) : claimName, - type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE - }, { - value: claimName, - type: SEARCH_TYPES.SEARCH - }); - } catch (e) { - suggestions.push({ - value: query, - type: SEARCH_TYPES.SEARCH - }); - } - - // Populate the current search query suggestion before fetching results - dispatch({ - type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS, - data: { suggestions: suggestions } - }); - // strip out any basic stuff for more accurate search results - var searchValue = value.replace(/lbry:\/\//g, '').replace(/-/g, ' '); + var searchValue = query.replace(/lbry:\/\//g, '').replace(/-/g, ' '); if (searchValue.includes('#')) { // This should probably be more robust, but I think it's fine for now // Remove everything after # to get rid of the claim id searchValue = searchValue.substring(0, searchValue.indexOf('#')); } + var suggestions = (0, _search.selectSuggestions)(getState()); + if (suggestions[searchValue]) { + return; + } + fetch('https://lighthouse.lbry.io/autocomplete?s=' + searchValue).then(_handleFetch2.default).then(function (apiSuggestions) { - // Suggestion could be a channel, uri, or search term - var formattedSuggestions = apiSuggestions.slice(0, 6).filter(function (suggestion) { - return suggestion !== query; - }).map(function (suggestion) { - if (suggestion.includes(' ')) { - return { - value: suggestion, - type: SEARCH_TYPES.SEARCH - }; - } - - try { - var _uri = (0, _lbryURI.normalizeURI)(suggestion); - - var _parseURI2 = (0, _lbryURI.parseURI)(_uri), - _claimName = _parseURI2.claimName, - _isChannel = _parseURI2.isChannel; - - return { - value: _uri, - shorthand: _isChannel ? _claimName.slice(1) : _claimName, - type: _isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE - }; - } catch (e) { - // search result includes some character that isn't valid in claim names - return { - value: suggestion, - type: SEARCH_TYPES.SEARCH - }; - } - }); - - suggestions = suggestions.concat(formattedSuggestions); dispatch({ type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS, - data: { suggestions: suggestions } + data: { + query: searchValue, + suggestions: apiSuggestions + } }); }).catch(function () { // If the fetch fails, do nothing @@ -4387,20 +4417,6 @@ var doBlurSearchInput = exports.doBlurSearchInput = function doBlurSearchInput() }; }; -/***/ }), -/* 25 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var FILE = exports.FILE = 'file'; -var CHANNEL = exports.CHANNEL = 'channel'; -var SEARCH = exports.SEARCH = 'search'; - /***/ }), /* 26 */ /***/ (function(module, exports, __webpack_require__) { @@ -5903,6 +5919,7 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope /*:: type UpdateSearchSuggestions = { type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS, data: { + query: string, suggestions: Array, }, };*/ @@ -5926,7 +5943,7 @@ var defaultState = { isActive: false, // does the user have any typed text in the search input focused: false, // is the search input focused searchQuery: '', // needs to be an empty string for input focusing - suggestions: [], + suggestions: {}, urisByQuery: {} }; @@ -5955,15 +5972,15 @@ var searchReducer = exports.searchReducer = (0, _reduxUtils.handleActions)((_han }); }), _defineProperty(_handleActions, ACTIONS.UPDATE_SEARCH_SUGGESTIONS, function (state /*: SearchState*/, action /*: UpdateSearchSuggestions*/) /*: SearchState*/ { return _extends({}, state, { - suggestions: action.data.suggestions + suggestions: _extends({}, state.suggestions, _defineProperty({}, action.data.query, action.data.suggestions)) }); }), _defineProperty(_handleActions, ACTIONS.HISTORY_NAVIGATE, function (state /*: SearchState*/, action /*: HistoryNavigate*/) /*: SearchState*/ { var url = action.data.url; return _extends({}, state, { searchQuery: url.indexOf('/search') === 0 ? url.slice(14) : '', - suggestions: [], - isActive: url.indexOf('/search') === 0 + isActive: url.indexOf('/search') === 0, + suggestions: {} }); }), _defineProperty(_handleActions, ACTIONS.DISMISS_NOTIFICATION, function (state /*: SearchState*/) /*: SearchState*/ { return _extends({}, state, { diff --git a/src/index.js b/src/index.js index cdca8a5..000bb3b 100644 --- a/src/index.js +++ b/src/index.js @@ -189,6 +189,7 @@ export { selectSearchUrisByQuery, selectWunderBarAddress, selectSearchBarFocused, + selectSearchSuggestions, } from 'redux/selectors/search'; export { diff --git a/src/redux/actions/search.js b/src/redux/actions/search.js index cf87fd5..0f16f89 100644 --- a/src/redux/actions/search.js +++ b/src/redux/actions/search.js @@ -1,9 +1,8 @@ // @flow import * as ACTIONS from 'constants/action_types'; -import * as SEARCH_TYPES from 'constants/search'; -import { normalizeURI, buildURI, parseURI } from 'lbryURI'; +import { buildURI } from 'lbryURI'; import { doResolveUri } from 'redux/actions/claims'; -import { makeSelectSearchUris } from 'redux/selectors/search'; +import { makeSelectSearchUris, selectSuggestions } from 'redux/selectors/search'; import { batchActions } from 'util/batchActions'; import handleFetchResponse from 'util/handle-fetch'; @@ -77,102 +76,31 @@ export const doSearch = ( }); }; -export const getSearchSuggestions = (value: string) => dispatch => { +export const getSearchSuggestions = (value: string) => (dispatch, getState) => { const query = value.trim(); - const isPrefix = () => - query === '@' || query === 'lbry:' || query === 'lbry:/' || query === 'lbry://'; - - if (!query || isPrefix()) { - dispatch({ - type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS, - data: { suggestions: [] }, - }); - return; - } - - let suggestions = []; - try { - // If the user is about to manually add the claim id ignore it until they - // actually add one. This would hardly ever happen, but then the search - // suggestions won't change just from adding a '#' after a uri - let uriQuery = query; - if (uriQuery.endsWith('#')) { - uriQuery = uriQuery.slice(0, -1); - } - - const uri = normalizeURI(uriQuery); - const { claimName, isChannel } = parseURI(uri); - - suggestions.push( - { - value: uri, - shorthand: isChannel ? claimName.slice(1) : claimName, - type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE, - }, - { - value: claimName, - type: SEARCH_TYPES.SEARCH, - } - ); - } catch (e) { - suggestions.push({ - value: query, - type: SEARCH_TYPES.SEARCH, - }); - } - - // Populate the current search query suggestion before fetching results - dispatch({ - type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS, - data: { suggestions }, - }); - // strip out any basic stuff for more accurate search results - let searchValue = value.replace(/lbry:\/\//g, '').replace(/-/g, ' '); + let searchValue = query.replace(/lbry:\/\//g, '').replace(/-/g, ' '); if (searchValue.includes('#')) { // This should probably be more robust, but I think it's fine for now // Remove everything after # to get rid of the claim id searchValue = searchValue.substring(0, searchValue.indexOf('#')); } + const suggestions = selectSuggestions(getState()); + if (suggestions[searchValue]) { + return; + } + fetch(`https://lighthouse.lbry.io/autocomplete?s=${searchValue}`) .then(handleFetchResponse) .then(apiSuggestions => { - // Suggestion could be a channel, uri, or search term - const formattedSuggestions = apiSuggestions - .slice(0, 6) - .filter(suggestion => suggestion !== query) - .map(suggestion => { - if (suggestion.includes(' ')) { - return { - value: suggestion, - type: SEARCH_TYPES.SEARCH, - }; - } - - try { - const uri = normalizeURI(suggestion); - const { claimName, isChannel } = parseURI(uri); - - return { - value: uri, - shorthand: isChannel ? claimName.slice(1) : claimName, - type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE, - }; - } catch (e) { - // search result includes some character that isn't valid in claim names - return { - value: suggestion, - type: SEARCH_TYPES.SEARCH, - }; - } - }); - - suggestions = suggestions.concat(formattedSuggestions); dispatch({ type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS, - data: { suggestions }, + data: { + query: searchValue, + suggestions: apiSuggestions, + }, }); }) .catch(() => { diff --git a/src/redux/reducers/search.js b/src/redux/reducers/search.js index 6f8a6cc..4372ee8 100644 --- a/src/redux/reducers/search.js +++ b/src/redux/reducers/search.js @@ -26,6 +26,7 @@ type SearchSuggestion = { type UpdateSearchSuggestions = { type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS, data: { + query: string, suggestions: Array, }, }; @@ -50,7 +51,7 @@ const defaultState = { isActive: false, // does the user have any typed text in the search input focused: false, // is the search input focused searchQuery: '', // needs to be an empty string for input focusing - suggestions: [], + suggestions: {}, urisByQuery: {}, }; @@ -89,7 +90,10 @@ export const searchReducer = handleActions( action: UpdateSearchSuggestions ): SearchState => ({ ...state, - suggestions: action.data.suggestions, + suggestions: { + ...state.suggestions, + [action.data.query]: action.data.suggestions, + }, }), // clear the searchQuery on back/forward unless to search page @@ -98,8 +102,8 @@ export const searchReducer = handleActions( return { ...state, searchQuery: url.indexOf('/search') === 0 ? url.slice(14) : '', - suggestions: [], isActive: url.indexOf('/search') === 0, + suggestions: {}, }; }, diff --git a/src/redux/selectors/search.js b/src/redux/selectors/search.js index 82fe741..caa5eaf 100644 --- a/src/redux/selectors/search.js +++ b/src/redux/selectors/search.js @@ -1,3 +1,5 @@ +import * as SEARCH_TYPES from 'constants/search'; +import { normalizeURI, parseURI } from 'lbryURI'; import { selectCurrentPage, selectCurrentParams } from 'redux/selectors/navigation'; import { createSelector } from 'reselect'; @@ -5,6 +7,8 @@ export const selectState = state => state.search || {}; export const selectSearchValue = createSelector(selectState, state => state.searchQuery); +export const selectSuggestions = createSelector(selectState, state => state.suggestions); + export const selectSearchQuery = createSelector( selectCurrentPage, selectCurrentParams, @@ -37,3 +41,79 @@ export const selectWunderBarAddress = createSelector( ); export const selectSearchBarFocused = createSelector(selectState, state => state.focused); +// export const selectSear + +export const selectSearchSuggestions = createSelector( + selectSearchValue, + selectSuggestions, + (query, suggestions) => { + if (!query) { + return []; + } + + const queryIsPrefix = query === 'lbry:' || query === 'lbry:/' || query === 'lbry://'; + + if (query.startsWith('lbry://') && query !== 'lbry://') { + // If it starts with a prefix, don't show any autocomplete results + // They are probably typing/pasting in a lbry uri + return [ + { + value: query, + type: SEARCH_TYPES.FILE, + }, + ]; + } else if (queryIsPrefix) { + // If it is a prefix, wait until something else comes to figure out what to do + return []; + } + + let searchSuggestions = []; + try { + const uri = normalizeURI(query); + const { claimName, isChannel } = parseURI(uri); + searchSuggestions.push( + { + value: claimName, + type: SEARCH_TYPES.SEARCH, + }, + { + value: uri, + shorthand: isChannel ? claimName.slice(1) : claimName, + type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE, + } + ); + } catch (e) { + searchSuggestions.push({ + value: query, + type: SEARCH_TYPES.SEARCH, + }); + } + + const apiSuggestions = suggestions[query] || []; + if (apiSuggestions.length) { + searchSuggestions = searchSuggestions.concat( + apiSuggestions.filter(suggestion => suggestion !== query).map(suggestion => { + // determine if it's a channel + try { + const uri = normalizeURI(suggestion); + const { claimName, isChannel } = parseURI(uri); + + return { + value: uri, + shorthand: isChannel ? claimName.slice(1) : claimName, + type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE, + }; + } catch (e) { + // search result includes some character that isn't valid in claim names + return { + value: suggestion, + type: SEARCH_TYPES.SEARCH, + }; + } + }) + ); + } + + return searchSuggestions; + } +);