add doResolvedSearch actions which returns resolved search results (#258)

* add doResolvedSearch actions which returns resolved search results
* add recommended content selector
* update ResolvedSearchResult type
* support for multiple pages of resolved search results
This commit is contained in:
Akinwale Ariwodola 2020-01-22 02:04:50 +01:00 committed by GitHub
parent f0891dd298
commit 17a5260c3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 388 additions and 15 deletions

154
dist/bundle.es.js vendored
View file

@ -15,7 +15,6 @@ const CHANNEL_NEW = 'new';
const PAGE_SIZE = 20; const PAGE_SIZE = 20;
var claim = /*#__PURE__*/Object.freeze({ var claim = /*#__PURE__*/Object.freeze({
__proto__: null,
MINIMUM_PUBLISH_BID: MINIMUM_PUBLISH_BID, MINIMUM_PUBLISH_BID: MINIMUM_PUBLISH_BID,
CHANNEL_ANONYMOUS: CHANNEL_ANONYMOUS, CHANNEL_ANONYMOUS: CHANNEL_ANONYMOUS,
CHANNEL_NEW: CHANNEL_NEW, CHANNEL_NEW: CHANNEL_NEW,
@ -163,6 +162,9 @@ const DELETE_PURCHASED_URI = 'DELETE_PURCHASED_URI';
const SEARCH_START = 'SEARCH_START'; const SEARCH_START = 'SEARCH_START';
const SEARCH_SUCCESS = 'SEARCH_SUCCESS'; const SEARCH_SUCCESS = 'SEARCH_SUCCESS';
const SEARCH_FAIL = 'SEARCH_FAIL'; const SEARCH_FAIL = 'SEARCH_FAIL';
const RESOLVED_SEARCH_START = 'RESOLVED_SEARCH_START';
const RESOLVED_SEARCH_SUCCESS = 'RESOLVED_SEARCH_SUCCESS';
const RESOLVED_SEARCH_FAIL = 'RESOLVED_SEARCH_FAIL';
const UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY'; const UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY';
const UPDATE_SEARCH_OPTIONS = 'UPDATE_SEARCH_OPTIONS'; const UPDATE_SEARCH_OPTIONS = 'UPDATE_SEARCH_OPTIONS';
const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS'; const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS';
@ -275,7 +277,6 @@ const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
const USER_STATE_POPULATE = 'USER_STATE_POPULATE'; const USER_STATE_POPULATE = 'USER_STATE_POPULATE';
var action_types = /*#__PURE__*/Object.freeze({ var action_types = /*#__PURE__*/Object.freeze({
__proto__: null,
WINDOW_FOCUSED: WINDOW_FOCUSED, WINDOW_FOCUSED: WINDOW_FOCUSED,
DAEMON_READY: DAEMON_READY, DAEMON_READY: DAEMON_READY,
DAEMON_VERSION_MATCH: DAEMON_VERSION_MATCH, DAEMON_VERSION_MATCH: DAEMON_VERSION_MATCH,
@ -403,6 +404,9 @@ var action_types = /*#__PURE__*/Object.freeze({
SEARCH_START: SEARCH_START, SEARCH_START: SEARCH_START,
SEARCH_SUCCESS: SEARCH_SUCCESS, SEARCH_SUCCESS: SEARCH_SUCCESS,
SEARCH_FAIL: SEARCH_FAIL, SEARCH_FAIL: SEARCH_FAIL,
RESOLVED_SEARCH_START: RESOLVED_SEARCH_START,
RESOLVED_SEARCH_SUCCESS: RESOLVED_SEARCH_SUCCESS,
RESOLVED_SEARCH_FAIL: RESOLVED_SEARCH_FAIL,
UPDATE_SEARCH_QUERY: UPDATE_SEARCH_QUERY, UPDATE_SEARCH_QUERY: UPDATE_SEARCH_QUERY,
UPDATE_SEARCH_OPTIONS: UPDATE_SEARCH_OPTIONS, UPDATE_SEARCH_OPTIONS: UPDATE_SEARCH_OPTIONS,
UPDATE_SEARCH_SUGGESTIONS: UPDATE_SEARCH_SUGGESTIONS, UPDATE_SEARCH_SUGGESTIONS: UPDATE_SEARCH_SUGGESTIONS,
@ -518,7 +522,6 @@ const OTHER = 'other';
const COPYRIGHT = 'copyright'; const COPYRIGHT = 'copyright';
var licenses = /*#__PURE__*/Object.freeze({ var licenses = /*#__PURE__*/Object.freeze({
__proto__: null,
CC_LICENSES: CC_LICENSES, CC_LICENSES: CC_LICENSES,
NONE: NONE, NONE: NONE,
PUBLIC_DOMAIN: PUBLIC_DOMAIN, PUBLIC_DOMAIN: PUBLIC_DOMAIN,
@ -549,7 +552,6 @@ const HISTORY = 'user_history';
const WALLET = 'wallet'; const WALLET = 'wallet';
var pages = /*#__PURE__*/Object.freeze({ var pages = /*#__PURE__*/Object.freeze({
__proto__: null,
AUTH: AUTH, AUTH: AUTH,
BACKUP: BACKUP, BACKUP: BACKUP,
CHANNEL: CHANNEL, CHANNEL: CHANNEL,
@ -599,7 +601,6 @@ const RECEIVE_INTERESTS_NOTIFICATIONS = 'receiveInterestsNotifications';
const RECEIVE_CREATOR_NOTIFICATIONS = 'receiveCreatorNotifications'; const RECEIVE_CREATOR_NOTIFICATIONS = 'receiveCreatorNotifications';
var settings = /*#__PURE__*/Object.freeze({ var settings = /*#__PURE__*/Object.freeze({
__proto__: null,
CREDIT_REQUIRED_ACKNOWLEDGED: CREDIT_REQUIRED_ACKNOWLEDGED, CREDIT_REQUIRED_ACKNOWLEDGED: CREDIT_REQUIRED_ACKNOWLEDGED,
NEW_USER_ACKNOWLEDGED: NEW_USER_ACKNOWLEDGED, NEW_USER_ACKNOWLEDGED: NEW_USER_ACKNOWLEDGED,
EMAIL_COLLECTION_ACKNOWLEDGED: EMAIL_COLLECTION_ACKNOWLEDGED, EMAIL_COLLECTION_ACKNOWLEDGED: EMAIL_COLLECTION_ACKNOWLEDGED,
@ -627,7 +628,6 @@ const TITLE = 'title';
const FILENAME = 'filename'; const FILENAME = 'filename';
var sort_options = /*#__PURE__*/Object.freeze({ var sort_options = /*#__PURE__*/Object.freeze({
__proto__: null,
DATE_NEW: DATE_NEW, DATE_NEW: DATE_NEW,
DATE_OLD: DATE_OLD, DATE_OLD: DATE_OLD,
TITLE: TITLE, TITLE: TITLE,
@ -641,7 +641,6 @@ const COMPLETE = 'complete';
const MANUAL = 'manual'; const MANUAL = 'manual';
var thumbnail_upload_statuses = /*#__PURE__*/Object.freeze({ var thumbnail_upload_statuses = /*#__PURE__*/Object.freeze({
__proto__: null,
API_DOWN: API_DOWN, API_DOWN: API_DOWN,
READY: READY, READY: READY,
IN_PROGRESS: IN_PROGRESS, IN_PROGRESS: IN_PROGRESS,
@ -661,7 +660,6 @@ const UPDATE = 'update';
const ABANDON = 'abandon'; const ABANDON = 'abandon';
var transaction_types = /*#__PURE__*/Object.freeze({ var transaction_types = /*#__PURE__*/Object.freeze({
__proto__: null,
ALL: ALL, ALL: ALL,
SPEND: SPEND, SPEND: SPEND,
RECEIVE: RECEIVE, RECEIVE: RECEIVE,
@ -678,7 +676,6 @@ const PAGE_SIZE$1 = 50;
const LATEST_PAGE_SIZE = 20; const LATEST_PAGE_SIZE = 20;
var transaction_list = /*#__PURE__*/Object.freeze({ var transaction_list = /*#__PURE__*/Object.freeze({
__proto__: null,
PAGE_SIZE: PAGE_SIZE$1, PAGE_SIZE: PAGE_SIZE$1,
LATEST_PAGE_SIZE: LATEST_PAGE_SIZE LATEST_PAGE_SIZE: LATEST_PAGE_SIZE
}); });
@ -687,7 +684,6 @@ const SPEECH_STATUS = 'https://spee.ch/api/config/site/publishing';
const SPEECH_PUBLISH = 'https://spee.ch/api/claim/publish'; const SPEECH_PUBLISH = 'https://spee.ch/api/claim/publish';
var speech_urls = /*#__PURE__*/Object.freeze({ var speech_urls = /*#__PURE__*/Object.freeze({
__proto__: null,
SPEECH_STATUS: SPEECH_STATUS, SPEECH_STATUS: SPEECH_STATUS,
SPEECH_PUBLISH: SPEECH_PUBLISH SPEECH_PUBLISH: SPEECH_PUBLISH
}); });
@ -733,7 +729,6 @@ const WALLET_DIR = 'wallet_dir';
const WALLETS = 'wallets'; const WALLETS = 'wallets';
var daemon_settings = /*#__PURE__*/Object.freeze({ var daemon_settings = /*#__PURE__*/Object.freeze({
__proto__: null,
ANNOUNCE_HEAD_AND_SD_ONLY: ANNOUNCE_HEAD_AND_SD_ONLY, ANNOUNCE_HEAD_AND_SD_ONLY: ANNOUNCE_HEAD_AND_SD_ONLY,
API: API, API: API,
BLOB_DOWNLOAD_TIMEOUT: BLOB_DOWNLOAD_TIMEOUT, BLOB_DOWNLOAD_TIMEOUT: BLOB_DOWNLOAD_TIMEOUT,
@ -787,7 +782,6 @@ var daemon_settings = /*#__PURE__*/Object.freeze({
const WALLET_SERVERS = LBRYUM_SERVERS; const WALLET_SERVERS = LBRYUM_SERVERS;
var shared_preferences = /*#__PURE__*/Object.freeze({ var shared_preferences = /*#__PURE__*/Object.freeze({
__proto__: null,
WALLET_SERVERS: WALLET_SERVERS WALLET_SERVERS: WALLET_SERVERS
}); });
@ -1342,6 +1336,18 @@ const makeSelectSearchUris = query =>
// replace statement below is kind of ugly, and repeated in doSearch action // replace statement below is kind of ugly, and repeated in doSearch action
reselect.createSelector(selectSearchUrisByQuery, byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]); reselect.createSelector(selectSearchUrisByQuery, byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]);
const selectResolvedSearchResultsByQuery = reselect.createSelector(selectState, state => state.resolvedResultsByQuery);
const selectResolvedSearchResultsByQueryLastPageReached = reselect.createSelector(selectState, state => state.resolvedResultsByQueryLastPageReached);
const makeSelectResolvedSearchResults = query =>
// replace statement below is kind of ugly, and repeated in doSearch action
reselect.createSelector(selectResolvedSearchResultsByQuery, byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]);
const makeSelectResolvedSearchResultsLastPageReached = query =>
// replace statement below is kind of ugly, and repeated in doSearch action
reselect.createSelector(selectResolvedSearchResultsByQueryLastPageReached, byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]);
const selectSearchBarFocused = reselect.createSelector(selectState, state => state.focused); const selectSearchBarFocused = reselect.createSelector(selectState, state => state.focused);
const selectSearchSuggestions = reselect.createSelector(selectSearchValue, selectSuggestions, (query, suggestions) => { const selectSearchSuggestions = reselect.createSelector(selectSearchValue, selectSuggestions, (query, suggestions) => {
@ -2277,6 +2283,34 @@ const makeSelectMyStreamUrlsForPage = (page = 1) => reselect.createSelector(sele
const selectMyStreamUrlsCount = reselect.createSelector(selectMyClaimUrisWithoutChannels, channels => channels.length); const selectMyStreamUrlsCount = reselect.createSelector(selectMyClaimUrisWithoutChannels, channels => channels.length);
const makeSelectResolvedRecommendedContentForUri = (uri, size) => reselect.createSelector(makeSelectClaimForUri(uri), selectResolvedSearchResultsByQuery, (claim, resolvedResultsByQuery) => {
const atVanityURI = !uri.includes('#');
let recommendedContent;
if (claim) {
// always grab full URL - this can change once search returns canonical
const currentUri = buildURI({ streamClaimId: claim.claim_id, streamName: claim.name });
const { title } = claim.value;
if (!title) {
return;
}
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), { size }, undefined, {
related_to: claim.claim_id
});
let results = resolvedResultsByQuery[searchQuery];
if (results) {
results = results.filter(result => buildURI({ streamClaimId: result.claimId, streamName: result.name }) !== currentUri);
recommendedContent = results;
}
}
return recommendedContent;
});
function numberWithCommas(x) { function numberWithCommas(x) {
var parts = x.toString().split('.'); var parts = x.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
@ -4020,6 +4054,63 @@ from, isBackgroundSearch = false, options = {}, resolveResults = true) => (dispa
}); });
}; };
const doResolvedSearch = (rawQuery, size, // only pass in if you don't want to use the users setting (ex: related content)
from, isBackgroundSearch = false, options = {}) => (dispatch, getState) => {
const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
if (!query) {
dispatch({
type: RESOLVED_SEARCH_FAIL
});
return;
}
const state = getState();
let queryWithOptions = makeSelectQueryWithOptions(query, size, from, isBackgroundSearch, options)(state);
// make from null so that we can maintain a reference to the same query for multiple pages and simply append the found results
let queryWithoutFrom = makeSelectQueryWithOptions(query, size, null, isBackgroundSearch, options)(state);
// If we have already searched for something, we don't need to do anything
// TODO: Tweak this check for multiple page results
/* const resultsForQuery = makeSelectResolvedSearchResults(queryWithOptions)(state);
if (resultsForQuery && resultsForQuery.length && resultsForQuery.length > (from * size)) {
return;
} */
dispatch({
type: RESOLVED_SEARCH_START
});
if (!state.search.searchQuery && !isBackgroundSearch) {
dispatch(doUpdateSearchQuery(query));
}
fetch(`${CONNECTION_STRING}search?resolve=true&${queryWithOptions}`).then(handleFetchResponse).then(data => {
const results = [];
data.forEach(result => {
if (result) {
results.push(result);
}
});
dispatch({
type: RESOLVED_SEARCH_SUCCESS,
data: {
query: queryWithoutFrom,
results,
pageSize: size,
append: parseInt(from, 10) > parseInt(size, 10) - 1
}
});
}).catch(e => {
dispatch({
type: RESOLVED_SEARCH_FAIL
});
});
};
const doFocusSearchInput = () => dispatch => dispatch({ const doFocusSearchInput = () => dispatch => dispatch({
type: SEARCH_FOCUS type: SEARCH_FOCUS
}); });
@ -5024,7 +5115,9 @@ const defaultState$7 = {
[SEARCH_OPTIONS.MEDIA_APPLICATION]: true [SEARCH_OPTIONS.MEDIA_APPLICATION]: true
}, },
suggestions: {}, suggestions: {},
urisByQuery: {} urisByQuery: {},
resolvedResultsByQuery: {},
resolvedResultsByQueryLastPageReached: {}
}; };
const searchReducer = handleActions({ const searchReducer = handleActions({
@ -5044,6 +5137,35 @@ const searchReducer = handleActions({
searching: false searching: false
}), }),
[RESOLVED_SEARCH_START]: state => _extends$d({}, state, {
searching: true
}),
[RESOLVED_SEARCH_SUCCESS]: (state, action) => {
const resolvedResultsByQuery = Object.assign({}, state.resolvedResultsByQuery);
const resolvedResultsByQueryLastPageReached = Object.assign({}, state.resolvedResultsByQueryLastPageReached);
const { append, query, results, pageSize } = action.data;
if (append) {
// todo: check for duplicates when concatenating?
resolvedResultsByQuery[query] = resolvedResultsByQuery[query] && resolvedResultsByQuery[query].length ? resolvedResultsByQuery[query].concat(results) : results;
} else {
resolvedResultsByQuery[query] = results;
}
// the returned number of urls is less than the page size, so we're on the last page
resolvedResultsByQueryLastPageReached[query] = results.length < pageSize;
return _extends$d({}, state, {
searching: false,
resolvedResultsByQuery,
resolvedResultsByQueryLastPageReached
});
},
[RESOLVED_SEARCH_FAIL]: state => _extends$d({}, state, {
searching: false
}),
[UPDATE_SEARCH_QUERY]: (state, action) => _extends$d({}, state, { [UPDATE_SEARCH_QUERY]: (state, action) => _extends$d({}, state, {
searchQuery: action.data.query, searchQuery: action.data.query,
isActive: true isActive: true
@ -5612,6 +5734,7 @@ exports.doPurchaseUri = doPurchaseUri;
exports.doResetThumbnailStatus = doResetThumbnailStatus; exports.doResetThumbnailStatus = doResetThumbnailStatus;
exports.doResolveUri = doResolveUri; exports.doResolveUri = doResolveUri;
exports.doResolveUris = doResolveUris; exports.doResolveUris = doResolveUris;
exports.doResolvedSearch = doResolvedSearch;
exports.doSearch = doSearch; exports.doSearch = doSearch;
exports.doSendDraftTransaction = doSendDraftTransaction; exports.doSendDraftTransaction = doSendDraftTransaction;
exports.doSendTip = doSendTip; exports.doSendTip = doSendTip;
@ -5680,6 +5803,9 @@ exports.makeSelectPermanentUrlForUri = makeSelectPermanentUrlForUri;
exports.makeSelectPublishFormValue = makeSelectPublishFormValue; exports.makeSelectPublishFormValue = makeSelectPublishFormValue;
exports.makeSelectQueryWithOptions = makeSelectQueryWithOptions; exports.makeSelectQueryWithOptions = makeSelectQueryWithOptions;
exports.makeSelectRecommendedContentForUri = makeSelectRecommendedContentForUri; exports.makeSelectRecommendedContentForUri = makeSelectRecommendedContentForUri;
exports.makeSelectResolvedRecommendedContentForUri = makeSelectResolvedRecommendedContentForUri;
exports.makeSelectResolvedSearchResults = makeSelectResolvedSearchResults;
exports.makeSelectResolvedSearchResultsLastPageReached = makeSelectResolvedSearchResultsLastPageReached;
exports.makeSelectSearchDownloadUrlsCount = makeSelectSearchDownloadUrlsCount; exports.makeSelectSearchDownloadUrlsCount = makeSelectSearchDownloadUrlsCount;
exports.makeSelectSearchDownloadUrlsForPage = makeSelectSearchDownloadUrlsForPage; exports.makeSelectSearchDownloadUrlsForPage = makeSelectSearchDownloadUrlsForPage;
exports.makeSelectSearchUris = makeSelectSearchUris; exports.makeSelectSearchUris = makeSelectSearchUris;
@ -5772,6 +5898,8 @@ exports.selectPurchasedUris = selectPurchasedUris;
exports.selectReceiveAddress = selectReceiveAddress; exports.selectReceiveAddress = selectReceiveAddress;
exports.selectRecentTransactions = selectRecentTransactions; exports.selectRecentTransactions = selectRecentTransactions;
exports.selectReservedBalance = selectReservedBalance; exports.selectReservedBalance = selectReservedBalance;
exports.selectResolvedSearchResultsByQuery = selectResolvedSearchResultsByQuery;
exports.selectResolvedSearchResultsByQueryLastPageReached = selectResolvedSearchResultsByQueryLastPageReached;
exports.selectResolvingUris = selectResolvingUris; exports.selectResolvingUris = selectResolvingUris;
exports.selectSearchBarFocused = selectSearchBarFocused; exports.selectSearchBarFocused = selectSearchBarFocused;
exports.selectSearchOptions = selectSearchOptions; exports.selectSearchOptions = selectSearchOptions;

View file

@ -28,6 +28,8 @@ declare type SearchState = {
options: SearchOptions, options: SearchOptions,
suggestions: { [string]: Array<SearchSuggestion> }, suggestions: { [string]: Array<SearchSuggestion> },
urisByQuery: {}, urisByQuery: {},
resolvedResultsByQuery: {},
resolvedResultsByQueryLastPageReached: {},
}; };
declare type SearchSuccess = { declare type SearchSuccess = {
@ -57,3 +59,26 @@ declare type UpdateSearchOptions = {
type: ACTIONS.UPDATE_SEARCH_OPTIONS, type: ACTIONS.UPDATE_SEARCH_OPTIONS,
data: SearchOptions, data: SearchOptions,
}; };
declare type ResolvedSearchResult = {
channel: string,
channel_claim_id: string,
claimId: string,
duration: number,
fee: number,
name: string,
nsfw: boolean,
release_time: string,
thumbnail_url: string,
title: string,
};
declare type ResolvedSearchSuccess = {
type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
data: {
append: boolean,
pageSize: number,
results: Array<ResolvedSearchResult>,
query: string,
},
};

25
flow-typed/Search.js vendored
View file

@ -28,6 +28,8 @@ declare type SearchState = {
options: SearchOptions, options: SearchOptions,
suggestions: { [string]: Array<SearchSuggestion> }, suggestions: { [string]: Array<SearchSuggestion> },
urisByQuery: {}, urisByQuery: {},
resolvedResultsByQuery: {},
resolvedResultsByQueryLastPageReached: {},
}; };
declare type SearchSuccess = { declare type SearchSuccess = {
@ -57,3 +59,26 @@ declare type UpdateSearchOptions = {
type: ACTIONS.UPDATE_SEARCH_OPTIONS, type: ACTIONS.UPDATE_SEARCH_OPTIONS,
data: SearchOptions, data: SearchOptions,
}; };
declare type ResolvedSearchResult = {
channel: string,
channel_claim_id: string,
claimId: string,
duration: number,
fee: number,
name: string,
nsfw: boolean,
release_time: string,
thumbnail_url: string,
title: string,
};
declare type ResolvedSearchSuccess = {
type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
data: {
append: boolean,
pageSize: number,
results: Array<ResolvedSearchResult>,
query: string,
},
};

View file

@ -139,6 +139,9 @@ export const DELETE_PURCHASED_URI = 'DELETE_PURCHASED_URI';
export const SEARCH_START = 'SEARCH_START'; export const SEARCH_START = 'SEARCH_START';
export const SEARCH_SUCCESS = 'SEARCH_SUCCESS'; export const SEARCH_SUCCESS = 'SEARCH_SUCCESS';
export const SEARCH_FAIL = 'SEARCH_FAIL'; export const SEARCH_FAIL = 'SEARCH_FAIL';
export const RESOLVED_SEARCH_START = 'RESOLVED_SEARCH_START';
export const RESOLVED_SEARCH_SUCCESS = 'RESOLVED_SEARCH_SUCCESS';
export const RESOLVED_SEARCH_FAIL = 'RESOLVED_SEARCH_FAIL';
export const UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY'; export const UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY';
export const UPDATE_SEARCH_OPTIONS = 'UPDATE_SEARCH_OPTIONS'; export const UPDATE_SEARCH_OPTIONS = 'UPDATE_SEARCH_OPTIONS';
export const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS'; export const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS';

View file

@ -510,5 +510,5 @@ export const DEFAULT_KNOWN_TAGS = [
'portugal', 'portugal',
'dantdm', 'dantdm',
'teaser', 'teaser',
'lbry' 'lbry',
]; ];

View file

@ -90,6 +90,7 @@ export {
export { export {
doSearch, doSearch,
doResolvedSearch,
doUpdateSearchQuery, doUpdateSearchQuery,
doFocusSearchInput, doFocusSearchInput,
doBlurSearchInput, doBlurSearchInput,
@ -183,6 +184,7 @@ export {
makeSelectOmittedCountForChannel, makeSelectOmittedCountForChannel,
makeSelectClaimIsNsfw, makeSelectClaimIsNsfw,
makeSelectRecommendedContentForUri, makeSelectRecommendedContentForUri,
makeSelectResolvedRecommendedContentForUri,
makeSelectFirstRecommendedFileForUri, makeSelectFirstRecommendedFileForUri,
makeSelectChannelForClaimUri, makeSelectChannelForClaimUri,
makeSelectClaimIsPending, makeSelectClaimIsPending,
@ -265,9 +267,13 @@ export {
export { selectSearchState }; export { selectSearchState };
export { export {
makeSelectSearchUris, makeSelectSearchUris,
makeSelectResolvedSearchResults,
makeSelectResolvedSearchResultsLastPageReached,
selectSearchValue, selectSearchValue,
selectSearchOptions, selectSearchOptions,
selectIsSearching, selectIsSearching,
selectResolvedSearchResultsByQuery,
selectResolvedSearchResultsByQueryLastPageReached,
selectSearchUrisByQuery, selectSearchUrisByQuery,
selectSearchBarFocused, selectSearchBarFocused,
selectSearchSuggestions, selectSearchSuggestions,

View file

@ -4,6 +4,7 @@ import { buildURI } from 'lbryURI';
import { doResolveUri } from 'redux/actions/claims'; import { doResolveUri } from 'redux/actions/claims';
import { import {
makeSelectSearchUris, makeSelectSearchUris,
makeSelectResolvedSearchResults,
selectSuggestions, selectSuggestions,
makeSelectQueryWithOptions, makeSelectQueryWithOptions,
selectSearchValue, selectSearchValue,
@ -159,6 +160,77 @@ export const doSearch = (
}); });
}; };
export const doResolvedSearch = (
rawQuery: string,
size: ?number, // only pass in if you don't want to use the users setting (ex: related content)
from: ?number,
isBackgroundSearch: boolean = false,
options: {
related_to?: string,
} = {}
) => (dispatch: Dispatch, getState: GetState) => {
const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
if (!query) {
dispatch({
type: ACTIONS.RESOLVED_SEARCH_FAIL,
});
return;
}
const state = getState();
let queryWithOptions = makeSelectQueryWithOptions(query, size, from, isBackgroundSearch, options)(
state
);
// make from null so that we can maintain a reference to the same query for multiple pages and simply append the found results
let queryWithoutFrom = makeSelectQueryWithOptions(query, size, null, isBackgroundSearch, options)(
state
);
// If we have already searched for something, we don't need to do anything
// TODO: Tweak this check for multiple page results
/* const resultsForQuery = makeSelectResolvedSearchResults(queryWithOptions)(state);
if (resultsForQuery && resultsForQuery.length && resultsForQuery.length > (from * size)) {
return;
} */
dispatch({
type: ACTIONS.RESOLVED_SEARCH_START,
});
if (!state.search.searchQuery && !isBackgroundSearch) {
dispatch(doUpdateSearchQuery(query));
}
fetch(`${CONNECTION_STRING}search?resolve=true&${queryWithOptions}`)
.then(handleFetchResponse)
.then((data: Array<ResolvedSearchResult>) => {
const results = [];
data.forEach(result => {
if (result) {
results.push(result);
}
});
dispatch({
type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
data: {
query: queryWithoutFrom,
results,
pageSize: size,
append: parseInt(from, 10) > parseInt(size, 10) - 1,
},
});
})
.catch(e => {
dispatch({
type: ACTIONS.RESOLVED_SEARCH_FAIL,
});
});
};
export const doFocusSearchInput = () => (dispatch: Dispatch) => export const doFocusSearchInput = () => (dispatch: Dispatch) =>
dispatch({ dispatch({
type: ACTIONS.SEARCH_FOCUS, type: ACTIONS.SEARCH_FOCUS,

View file

@ -18,6 +18,8 @@ const defaultState = {
}, },
suggestions: {}, suggestions: {},
urisByQuery: {}, urisByQuery: {},
resolvedResultsByQuery: {},
resolvedResultsByQueryLastPageReached: {},
}; };
export const searchReducer = handleActions( export const searchReducer = handleActions(
@ -41,6 +43,47 @@ export const searchReducer = handleActions(
searching: false, searching: false,
}), }),
[ACTIONS.RESOLVED_SEARCH_START]: (state: SearchState): SearchState => ({
...state,
searching: true,
}),
[ACTIONS.RESOLVED_SEARCH_SUCCESS]: (
state: SearchState,
action: ResolvedSearchSuccess
): SearchState => {
const resolvedResultsByQuery = Object.assign({}, state.resolvedResultsByQuery);
const resolvedResultsByQueryLastPageReached = Object.assign(
{},
state.resolvedResultsByQueryLastPageReached
);
const { append, query, results, pageSize } = action.data;
if (append) {
// todo: check for duplicates when concatenating?
resolvedResultsByQuery[query] =
resolvedResultsByQuery[query] && resolvedResultsByQuery[query].length
? resolvedResultsByQuery[query].concat(results)
: results;
} else {
resolvedResultsByQuery[query] = results;
}
// the returned number of urls is less than the page size, so we're on the last page
resolvedResultsByQueryLastPageReached[query] = results.length < pageSize;
return {
...state,
searching: false,
resolvedResultsByQuery,
resolvedResultsByQueryLastPageReached,
};
},
[ACTIONS.RESOLVED_SEARCH_FAIL]: (state: SearchState): SearchState => ({
...state,
searching: false,
}),
[ACTIONS.UPDATE_SEARCH_QUERY]: ( [ACTIONS.UPDATE_SEARCH_QUERY]: (
state: SearchState, state: SearchState,
action: UpdateSearchQuery action: UpdateSearchQuery

View file

@ -1,6 +1,9 @@
// @flow // @flow
import { normalizeURI, buildURI, parseURI } from 'lbryURI'; import { normalizeURI, buildURI, parseURI } from 'lbryURI';
import { selectSearchUrisByQuery } from 'redux/selectors/search'; import {
selectResolvedSearchResultsByQuery,
selectSearchUrisByQuery,
} from 'redux/selectors/search';
import { selectSupportsByOutpoint } from 'redux/selectors/wallet'; import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { isClaimNsfw, createNormalizedClaimSearchKey } from 'util/claim'; import { isClaimNsfw, createNormalizedClaimSearchKey } from 'util/claim';
@ -639,3 +642,39 @@ export const selectMyStreamUrlsCount = createSelector(
selectMyClaimUrisWithoutChannels, selectMyClaimUrisWithoutChannels,
channels => channels.length channels => channels.length
); );
export const makeSelectResolvedRecommendedContentForUri = (uri: string, size: number) =>
createSelector(
makeSelectClaimForUri(uri),
selectResolvedSearchResultsByQuery,
(claim, resolvedResultsByQuery) => {
const atVanityURI = !uri.includes('#');
let recommendedContent;
if (claim) {
// always grab full URL - this can change once search returns canonical
const currentUri = buildURI({ streamClaimId: claim.claim_id, streamName: claim.name });
const { title } = claim.value;
if (!title) {
return;
}
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), { size }, undefined, {
related_to: claim.claim_id,
});
let results = resolvedResultsByQuery[searchQuery];
if (results) {
results = results.filter(
result =>
buildURI({ streamClaimId: result.claimId, streamName: result.name }) !== currentUri
);
recommendedContent = results;
}
}
return recommendedContent;
}
);

View file

@ -44,6 +44,38 @@ export const makeSelectSearchUris = (query: string): ((state: State) => Array<st
byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query] byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]
); );
export const selectResolvedSearchResultsByQuery: (
state: State
) => { [string]: Array<ResolvedSearchResult> } = createSelector(
selectState,
state => state.resolvedResultsByQuery
);
export const selectResolvedSearchResultsByQueryLastPageReached: (
state: State
) => { [string]: Array<boolean> } = createSelector(
selectState,
state => state.resolvedResultsByQueryLastPageReached
);
export const makeSelectResolvedSearchResults = (
query: string
): ((state: State) => Array<ResolvedSearchResult>) =>
// replace statement below is kind of ugly, and repeated in doSearch action
createSelector(
selectResolvedSearchResultsByQuery,
byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]
);
export const makeSelectResolvedSearchResultsLastPageReached = (
query: string
): ((state: State) => boolean) =>
// replace statement below is kind of ugly, and repeated in doSearch action
createSelector(
selectResolvedSearchResultsByQueryLastPageReached,
byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]
);
export const selectSearchBarFocused: boolean = createSelector( export const selectSearchBarFocused: boolean = createSelector(
selectState, selectState,
state => state.focused state => state.focused