From 9250e0c45118237a94884bb762790efcaec9d0f4 Mon Sep 17 00:00:00 2001
From: Akinwale Ariwodola <akinwale@gmail.com>
Date: Sun, 5 Jan 2020 12:58:44 +0100
Subject: [PATCH 1/5] add doResolvedSearch actions which returns resolved
 search results

---
 dist/bundle.es.js             | 85 ++++++++++++++++++++++++++++++++++-
 dist/flow-typed/Search.js     | 20 +++++++++
 flow-typed/Search.js          | 20 +++++++++
 src/constants/action_types.js |  3 ++
 src/index.js                  |  3 ++
 src/redux/actions/search.js   | 64 ++++++++++++++++++++++++++
 src/redux/reducers/search.js  | 25 +++++++++++
 src/redux/selectors/search.js | 16 +++++++
 8 files changed, 235 insertions(+), 1 deletion(-)

diff --git a/dist/bundle.es.js b/dist/bundle.es.js
index 035f91c..a1c23ad 100644
--- a/dist/bundle.es.js
+++ b/dist/bundle.es.js
@@ -162,6 +162,9 @@ const DELETE_PURCHASED_URI = 'DELETE_PURCHASED_URI';
 const SEARCH_START = 'SEARCH_START';
 const SEARCH_SUCCESS = 'SEARCH_SUCCESS';
 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_OPTIONS = 'UPDATE_SEARCH_OPTIONS';
 const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS';
@@ -401,6 +404,9 @@ var action_types = /*#__PURE__*/Object.freeze({
   SEARCH_START: SEARCH_START,
   SEARCH_SUCCESS: SEARCH_SUCCESS,
   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_OPTIONS: UPDATE_SEARCH_OPTIONS,
   UPDATE_SEARCH_SUGGESTIONS: UPDATE_SEARCH_SUGGESTIONS,
@@ -1330,6 +1336,12 @@ const makeSelectSearchUris = query =>
 // replace statement below is kind of ugly, and repeated in doSearch action
 reselect.createSelector(selectSearchUrisByQuery, byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]);
 
+const selectResolvedSearchResultsByQuery = reselect.createSelector(selectState, state => state.resolvedResultsByQuery);
+
+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 selectSearchBarFocused = reselect.createSelector(selectState, state => state.focused);
 
 const selectSearchSuggestions = reselect.createSelector(selectSearchValue, selectSuggestions, (query, suggestions) => {
@@ -3995,6 +4007,57 @@ 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);
+
+  // If we have already searched for something, we don't need to do anything
+  const resultsForQuery = makeSelectResolvedSearchResults(queryWithOptions)(state);
+  if (resultsForQuery && !!resultsForQuery.length) {
+    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: queryWithOptions,
+        results
+      }
+    });
+  }).catch(e => {
+    dispatch({
+      type: RESOLVED_SEARCH_FAIL
+    });
+  });
+};
+
 const doFocusSearchInput = () => dispatch => dispatch({
   type: SEARCH_FOCUS
 });
@@ -4999,7 +5062,8 @@ const defaultState$7 = {
     [SEARCH_OPTIONS.MEDIA_APPLICATION]: true
   },
   suggestions: {},
-  urisByQuery: {}
+  urisByQuery: {},
+  resolvedResultsByQuery: {}
 };
 
 const searchReducer = handleActions({
@@ -5019,6 +5083,22 @@ const searchReducer = handleActions({
     searching: false
   }),
 
+  [RESOLVED_SEARCH_START]: state => _extends$c({}, state, {
+    searching: true
+  }),
+  [RESOLVED_SEARCH_SUCCESS]: (state, action) => {
+    const { query, results } = action.data;
+
+    return _extends$c({}, state, {
+      searching: false,
+      resolvedResultsByQuery: Object.assign({}, state.resolvedResultsByQuery, { [query]: results })
+    });
+  },
+
+  [RESOLVED_SEARCH_FAIL]: state => _extends$c({}, state, {
+    searching: false
+  }),
+
   [UPDATE_SEARCH_QUERY]: (state, action) => _extends$c({}, state, {
     searchQuery: action.data.query,
     isActive: true
@@ -5587,6 +5667,7 @@ exports.doPurchaseUri = doPurchaseUri;
 exports.doResetThumbnailStatus = doResetThumbnailStatus;
 exports.doResolveUri = doResolveUri;
 exports.doResolveUris = doResolveUris;
+exports.doResolvedSearch = doResolvedSearch;
 exports.doSearch = doSearch;
 exports.doSendDraftTransaction = doSendDraftTransaction;
 exports.doSendTip = doSendTip;
@@ -5655,6 +5736,7 @@ exports.makeSelectPermanentUrlForUri = makeSelectPermanentUrlForUri;
 exports.makeSelectPublishFormValue = makeSelectPublishFormValue;
 exports.makeSelectQueryWithOptions = makeSelectQueryWithOptions;
 exports.makeSelectRecommendedContentForUri = makeSelectRecommendedContentForUri;
+exports.makeSelectResolvedSearchResults = makeSelectResolvedSearchResults;
 exports.makeSelectSearchDownloadUrlsCount = makeSelectSearchDownloadUrlsCount;
 exports.makeSelectSearchDownloadUrlsForPage = makeSelectSearchDownloadUrlsForPage;
 exports.makeSelectSearchUris = makeSelectSearchUris;
@@ -5747,6 +5829,7 @@ exports.selectPurchasedUris = selectPurchasedUris;
 exports.selectReceiveAddress = selectReceiveAddress;
 exports.selectRecentTransactions = selectRecentTransactions;
 exports.selectReservedBalance = selectReservedBalance;
+exports.selectResolvedSearchResultsByQuery = selectResolvedSearchResultsByQuery;
 exports.selectResolvingUris = selectResolvingUris;
 exports.selectSearchBarFocused = selectSearchBarFocused;
 exports.selectSearchOptions = selectSearchOptions;
diff --git a/dist/flow-typed/Search.js b/dist/flow-typed/Search.js
index 5fad710..9b5c909 100644
--- a/dist/flow-typed/Search.js
+++ b/dist/flow-typed/Search.js
@@ -28,6 +28,7 @@ declare type SearchState = {
   options: SearchOptions,
   suggestions: { [string]: Array<SearchSuggestion> },
   urisByQuery: {},
+  resolvedResultsByQuery: {},
 };
 
 declare type SearchSuccess = {
@@ -57,3 +58,22 @@ declare type UpdateSearchOptions = {
   type: ACTIONS.UPDATE_SEARCH_OPTIONS,
   data: SearchOptions,
 };
+
+declare type ResolvedSearchResult = {
+  channel: string,
+  channel_claim_id: string,
+  claimId: string,
+  fee: number,
+  name: string,
+  release_time: string,
+  thumbnail_url: string,
+  title: string,
+};
+
+declare type ResolvedSearchSuccess = {
+  type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
+  data: {
+    query: string,
+    results: Array<ResolvedSearchResult>,
+  },
+};
diff --git a/flow-typed/Search.js b/flow-typed/Search.js
index 5fad710..9b5c909 100644
--- a/flow-typed/Search.js
+++ b/flow-typed/Search.js
@@ -28,6 +28,7 @@ declare type SearchState = {
   options: SearchOptions,
   suggestions: { [string]: Array<SearchSuggestion> },
   urisByQuery: {},
+  resolvedResultsByQuery: {},
 };
 
 declare type SearchSuccess = {
@@ -57,3 +58,22 @@ declare type UpdateSearchOptions = {
   type: ACTIONS.UPDATE_SEARCH_OPTIONS,
   data: SearchOptions,
 };
+
+declare type ResolvedSearchResult = {
+  channel: string,
+  channel_claim_id: string,
+  claimId: string,
+  fee: number,
+  name: string,
+  release_time: string,
+  thumbnail_url: string,
+  title: string,
+};
+
+declare type ResolvedSearchSuccess = {
+  type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
+  data: {
+    query: string,
+    results: Array<ResolvedSearchResult>,
+  },
+};
diff --git a/src/constants/action_types.js b/src/constants/action_types.js
index ac23374..42cf987 100644
--- a/src/constants/action_types.js
+++ b/src/constants/action_types.js
@@ -139,6 +139,9 @@ export const DELETE_PURCHASED_URI = 'DELETE_PURCHASED_URI';
 export const SEARCH_START = 'SEARCH_START';
 export const SEARCH_SUCCESS = 'SEARCH_SUCCESS';
 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_OPTIONS = 'UPDATE_SEARCH_OPTIONS';
 export const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS';
diff --git a/src/index.js b/src/index.js
index 65f1db9..be0a3d0 100644
--- a/src/index.js
+++ b/src/index.js
@@ -90,6 +90,7 @@ export {
 
 export {
   doSearch,
+  doResolvedSearch,
   doUpdateSearchQuery,
   doFocusSearchInput,
   doBlurSearchInput,
@@ -265,9 +266,11 @@ export {
 export { selectSearchState };
 export {
   makeSelectSearchUris,
+  makeSelectResolvedSearchResults,
   selectSearchValue,
   selectSearchOptions,
   selectIsSearching,
+  selectResolvedSearchResultsByQuery,
   selectSearchUrisByQuery,
   selectSearchBarFocused,
   selectSearchSuggestions,
diff --git a/src/redux/actions/search.js b/src/redux/actions/search.js
index 3cd3a47..ccab1c3 100644
--- a/src/redux/actions/search.js
+++ b/src/redux/actions/search.js
@@ -4,6 +4,7 @@ import { buildURI } from 'lbryURI';
 import { doResolveUri } from 'redux/actions/claims';
 import {
   makeSelectSearchUris,
+  makeSelectResolvedSearchResults,
   selectSuggestions,
   makeSelectQueryWithOptions,
   selectSearchValue,
@@ -159,6 +160,69 @@ 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
+  );
+
+  // If we have already searched for something, we don't need to do anything
+  const resultsForQuery = makeSelectResolvedSearchResults(queryWithOptions)(state);
+  if (resultsForQuery && !!resultsForQuery.length) {
+    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: queryWithOptions,
+          results,
+        },
+      });
+    })
+    .catch(e => {
+      dispatch({
+        type: ACTIONS.RESOLVED_SEARCH_FAIL,
+      });
+    });
+};
+
 export const doFocusSearchInput = () => (dispatch: Dispatch) =>
   dispatch({
     type: ACTIONS.SEARCH_FOCUS,
diff --git a/src/redux/reducers/search.js b/src/redux/reducers/search.js
index ee0bd8b..fe5a443 100644
--- a/src/redux/reducers/search.js
+++ b/src/redux/reducers/search.js
@@ -18,6 +18,7 @@ const defaultState = {
   },
   suggestions: {},
   urisByQuery: {},
+  resolvedResultsByQuery: {},
 };
 
 export const searchReducer = handleActions(
@@ -41,6 +42,30 @@ export const searchReducer = handleActions(
       searching: false,
     }),
 
+    [ACTIONS.RESOLVED_SEARCH_START]: (state: SearchState): SearchState => ({
+      ...state,
+      searching: true,
+    }),
+    [ACTIONS.RESOLVED_SEARCH_SUCCESS]: (
+      state: SearchState,
+      action: ResolvedSearchSuccess
+    ): SearchState => {
+      const { query, results } = action.data;
+
+      return {
+        ...state,
+        searching: false,
+        resolvedResultsByQuery: Object.assign({}, state.resolvedResultsByQuery, {
+          [query]: results,
+        }),
+      };
+    },
+
+    [ACTIONS.RESOLVED_SEARCH_FAIL]: (state: SearchState): SearchState => ({
+      ...state,
+      searching: false,
+    }),
+
     [ACTIONS.UPDATE_SEARCH_QUERY]: (
       state: SearchState,
       action: UpdateSearchQuery
diff --git a/src/redux/selectors/search.js b/src/redux/selectors/search.js
index d556602..2dc006a 100644
--- a/src/redux/selectors/search.js
+++ b/src/redux/selectors/search.js
@@ -44,6 +44,22 @@ export const makeSelectSearchUris = (query: string): ((state: State) => Array<st
     byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]
   );
 
+export const selectResolvedSearchResultsByQuery: (
+  state: State
+) => { [string]: Array<ResolvedSearchResult> } = createSelector(
+  selectState,
+  state => state.resolvedResultsByQuery
+);
+
+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 selectSearchBarFocused: boolean = createSelector(
   selectState,
   state => state.focused

From 6ef2f9dde9167f3217fde2d7d3fed97282bdb0dc Mon Sep 17 00:00:00 2001
From: Akinwale Ariwodola <akinwale@gmail.com>
Date: Sun, 5 Jan 2020 13:40:48 +0100
Subject: [PATCH 2/5] add recommended content selector

---
 dist/bundle.es.js             | 33 +++++++++++++++++++++++++++-
 src/index.js                  |  1 +
 src/redux/selectors/claims.js | 41 ++++++++++++++++++++++++++++++++++-
 3 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/dist/bundle.es.js b/dist/bundle.es.js
index a1c23ad..1fb9212 100644
--- a/dist/bundle.es.js
+++ b/dist/bundle.es.js
@@ -2277,6 +2277,34 @@ const makeSelectMyStreamUrlsForPage = (page = 1) => reselect.createSelector(sele
 
 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) {
   var parts = x.toString().split('.');
   parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
@@ -5091,7 +5119,9 @@ const searchReducer = handleActions({
 
     return _extends$c({}, state, {
       searching: false,
-      resolvedResultsByQuery: Object.assign({}, state.resolvedResultsByQuery, { [query]: results })
+      resolvedResultsByQuery: Object.assign({}, state.resolvedResultsByQuery, {
+        [query]: results
+      })
     });
   },
 
@@ -5736,6 +5766,7 @@ exports.makeSelectPermanentUrlForUri = makeSelectPermanentUrlForUri;
 exports.makeSelectPublishFormValue = makeSelectPublishFormValue;
 exports.makeSelectQueryWithOptions = makeSelectQueryWithOptions;
 exports.makeSelectRecommendedContentForUri = makeSelectRecommendedContentForUri;
+exports.makeSelectResolvedRecommendedContentForUri = makeSelectResolvedRecommendedContentForUri;
 exports.makeSelectResolvedSearchResults = makeSelectResolvedSearchResults;
 exports.makeSelectSearchDownloadUrlsCount = makeSelectSearchDownloadUrlsCount;
 exports.makeSelectSearchDownloadUrlsForPage = makeSelectSearchDownloadUrlsForPage;
diff --git a/src/index.js b/src/index.js
index be0a3d0..fceec98 100644
--- a/src/index.js
+++ b/src/index.js
@@ -184,6 +184,7 @@ export {
   makeSelectOmittedCountForChannel,
   makeSelectClaimIsNsfw,
   makeSelectRecommendedContentForUri,
+  makeSelectResolvedRecommendedContentForUri,
   makeSelectFirstRecommendedFileForUri,
   makeSelectChannelForClaimUri,
   makeSelectClaimIsPending,
diff --git a/src/redux/selectors/claims.js b/src/redux/selectors/claims.js
index e825188..6adf368 100644
--- a/src/redux/selectors/claims.js
+++ b/src/redux/selectors/claims.js
@@ -1,6 +1,9 @@
 // @flow
 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 { createSelector } from 'reselect';
 import { isClaimNsfw, createNormalizedClaimSearchKey } from 'util/claim';
@@ -639,3 +642,39 @@ export const selectMyStreamUrlsCount = createSelector(
   selectMyClaimUrisWithoutChannels,
   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;
+    }
+  );

From e8f29f1c47b136669df265babb109d2488a37db0 Mon Sep 17 00:00:00 2001
From: Akinwale Ariwodola <akinwale@gmail.com>
Date: Sun, 5 Jan 2020 17:56:08 +0100
Subject: [PATCH 3/5] update ResolvedSearchResult type

---
 dist/flow-typed/Search.js | 2 ++
 flow-typed/Search.js      | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/dist/flow-typed/Search.js b/dist/flow-typed/Search.js
index 9b5c909..b099a47 100644
--- a/dist/flow-typed/Search.js
+++ b/dist/flow-typed/Search.js
@@ -63,8 +63,10 @@ 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,
diff --git a/flow-typed/Search.js b/flow-typed/Search.js
index 9b5c909..b099a47 100644
--- a/flow-typed/Search.js
+++ b/flow-typed/Search.js
@@ -63,8 +63,10 @@ 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,

From c910cd2b80b165843a81fdf6ce96094429b94ec8 Mon Sep 17 00:00:00 2001
From: Akinwale Ariwodola <akinwale@gmail.com>
Date: Wed, 8 Jan 2020 09:09:45 +0100
Subject: [PATCH 4/5] support for multiple pages of resolved search results

---
 dist/bundle.es.js             | 46 +++++++++++++++++++++++++++--------
 dist/flow-typed/Search.js     |  5 +++-
 flow-typed/Search.js          |  5 +++-
 src/index.js                  |  2 ++
 src/redux/actions/search.js   | 16 +++++++++---
 src/redux/reducers/search.js  | 26 +++++++++++++++++---
 src/redux/selectors/search.js | 16 ++++++++++++
 7 files changed, 96 insertions(+), 20 deletions(-)

diff --git a/dist/bundle.es.js b/dist/bundle.es.js
index 1fb9212..5e8dd3c 100644
--- a/dist/bundle.es.js
+++ b/dist/bundle.es.js
@@ -1338,10 +1338,16 @@ reselect.createSelector(selectSearchUrisByQuery, byQuery => byQuery[query ? quer
 
 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 selectSearchSuggestions = reselect.createSelector(selectSearchValue, selectSuggestions, (query, suggestions) => {
@@ -4049,11 +4055,15 @@ from, isBackgroundSearch = false, options = {}) => (dispatch, getState) => {
   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
-  const resultsForQuery = makeSelectResolvedSearchResults(queryWithOptions)(state);
-  if (resultsForQuery && !!resultsForQuery.length) {
+  // 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
@@ -4075,8 +4085,10 @@ from, isBackgroundSearch = false, options = {}) => (dispatch, getState) => {
     dispatch({
       type: RESOLVED_SEARCH_SUCCESS,
       data: {
-        query: queryWithOptions,
-        results
+        query: queryWithoutFrom,
+        results,
+        pageSize: size,
+        append: parseInt(from, 10) > parseInt(size, 10) - 1
       }
     });
   }).catch(e => {
@@ -5091,7 +5103,8 @@ const defaultState$7 = {
   },
   suggestions: {},
   urisByQuery: {},
-  resolvedResultsByQuery: {}
+  resolvedResultsByQuery: {},
+  resolvedResultsByQueryLastPageReached: {}
 };
 
 const searchReducer = handleActions({
@@ -5115,13 +5128,24 @@ const searchReducer = handleActions({
     searching: true
   }),
   [RESOLVED_SEARCH_SUCCESS]: (state, action) => {
-    const { query, results } = action.data;
+    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$c({}, state, {
       searching: false,
-      resolvedResultsByQuery: Object.assign({}, state.resolvedResultsByQuery, {
-        [query]: results
-      })
+      resolvedResultsByQuery,
+      resolvedResultsByQueryLastPageReached
     });
   },
 
@@ -5768,6 +5792,7 @@ exports.makeSelectQueryWithOptions = makeSelectQueryWithOptions;
 exports.makeSelectRecommendedContentForUri = makeSelectRecommendedContentForUri;
 exports.makeSelectResolvedRecommendedContentForUri = makeSelectResolvedRecommendedContentForUri;
 exports.makeSelectResolvedSearchResults = makeSelectResolvedSearchResults;
+exports.makeSelectResolvedSearchResultsLastPageReached = makeSelectResolvedSearchResultsLastPageReached;
 exports.makeSelectSearchDownloadUrlsCount = makeSelectSearchDownloadUrlsCount;
 exports.makeSelectSearchDownloadUrlsForPage = makeSelectSearchDownloadUrlsForPage;
 exports.makeSelectSearchUris = makeSelectSearchUris;
@@ -5861,6 +5886,7 @@ exports.selectReceiveAddress = selectReceiveAddress;
 exports.selectRecentTransactions = selectRecentTransactions;
 exports.selectReservedBalance = selectReservedBalance;
 exports.selectResolvedSearchResultsByQuery = selectResolvedSearchResultsByQuery;
+exports.selectResolvedSearchResultsByQueryLastPageReached = selectResolvedSearchResultsByQueryLastPageReached;
 exports.selectResolvingUris = selectResolvingUris;
 exports.selectSearchBarFocused = selectSearchBarFocused;
 exports.selectSearchOptions = selectSearchOptions;
diff --git a/dist/flow-typed/Search.js b/dist/flow-typed/Search.js
index b099a47..2a2152e 100644
--- a/dist/flow-typed/Search.js
+++ b/dist/flow-typed/Search.js
@@ -29,6 +29,7 @@ declare type SearchState = {
   suggestions: { [string]: Array<SearchSuggestion> },
   urisByQuery: {},
   resolvedResultsByQuery: {},
+  resolvedResultsByQueryLastPageReached: {},
 };
 
 declare type SearchSuccess = {
@@ -75,7 +76,9 @@ declare type ResolvedSearchResult = {
 declare type ResolvedSearchSuccess = {
   type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
   data: {
-    query: string,
+    append: boolean,
+    pageSize: number,
     results: Array<ResolvedSearchResult>,
+    query: string,
   },
 };
diff --git a/flow-typed/Search.js b/flow-typed/Search.js
index b099a47..2a2152e 100644
--- a/flow-typed/Search.js
+++ b/flow-typed/Search.js
@@ -29,6 +29,7 @@ declare type SearchState = {
   suggestions: { [string]: Array<SearchSuggestion> },
   urisByQuery: {},
   resolvedResultsByQuery: {},
+  resolvedResultsByQueryLastPageReached: {},
 };
 
 declare type SearchSuccess = {
@@ -75,7 +76,9 @@ declare type ResolvedSearchResult = {
 declare type ResolvedSearchSuccess = {
   type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
   data: {
-    query: string,
+    append: boolean,
+    pageSize: number,
     results: Array<ResolvedSearchResult>,
+    query: string,
   },
 };
diff --git a/src/index.js b/src/index.js
index fceec98..b9d554f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -268,10 +268,12 @@ export { selectSearchState };
 export {
   makeSelectSearchUris,
   makeSelectResolvedSearchResults,
+  makeSelectResolvedSearchResultsLastPageReached,
   selectSearchValue,
   selectSearchOptions,
   selectIsSearching,
   selectResolvedSearchResultsByQuery,
+  selectResolvedSearchResultsByQueryLastPageReached,
   selectSearchUrisByQuery,
   selectSearchBarFocused,
   selectSearchSuggestions,
diff --git a/src/redux/actions/search.js b/src/redux/actions/search.js
index ccab1c3..e00d7ba 100644
--- a/src/redux/actions/search.js
+++ b/src/redux/actions/search.js
@@ -183,11 +183,17 @@ export const doResolvedSearch = (
     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
-  const resultsForQuery = makeSelectResolvedSearchResults(queryWithOptions)(state);
-  if (resultsForQuery && !!resultsForQuery.length) {
+  // 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,
@@ -211,8 +217,10 @@ export const doResolvedSearch = (
       dispatch({
         type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
         data: {
-          query: queryWithOptions,
+          query: queryWithoutFrom,
           results,
+          pageSize: size,
+          append: parseInt(from, 10) > parseInt(size, 10) - 1,
         },
       });
     })
diff --git a/src/redux/reducers/search.js b/src/redux/reducers/search.js
index fe5a443..27e1013 100644
--- a/src/redux/reducers/search.js
+++ b/src/redux/reducers/search.js
@@ -19,6 +19,7 @@ const defaultState = {
   suggestions: {},
   urisByQuery: {},
   resolvedResultsByQuery: {},
+  resolvedResultsByQueryLastPageReached: {},
 };
 
 export const searchReducer = handleActions(
@@ -50,14 +51,31 @@ export const searchReducer = handleActions(
       state: SearchState,
       action: ResolvedSearchSuccess
     ): SearchState => {
-      const { query, results } = action.data;
+      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: Object.assign({}, state.resolvedResultsByQuery, {
-          [query]: results,
-        }),
+        resolvedResultsByQuery,
+        resolvedResultsByQueryLastPageReached,
       };
     },
 
diff --git a/src/redux/selectors/search.js b/src/redux/selectors/search.js
index 2dc006a..48dca04 100644
--- a/src/redux/selectors/search.js
+++ b/src/redux/selectors/search.js
@@ -51,6 +51,13 @@ export const selectResolvedSearchResultsByQuery: (
   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>) =>
@@ -60,6 +67,15 @@ export const makeSelectResolvedSearchResults = (
     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(
   selectState,
   state => state.focused

From acbbc66b78566da11faa0fa8c65b44a8d4302c5e Mon Sep 17 00:00:00 2001
From: Akinwale Ariwodola <akinwale@gmail.com>
Date: Mon, 3 Feb 2020 07:25:44 +0100
Subject: [PATCH 5/5] add nsfw flag

---
 dist/bundle.es.js           | 5 +++--
 src/redux/actions/search.js | 8 ++++++--
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/dist/bundle.es.js b/dist/bundle.es.js
index 0549d59..1c85e26 100644
--- a/dist/bundle.es.js
+++ b/dist/bundle.es.js
@@ -4055,7 +4055,7 @@ 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) => {
+from, isBackgroundSearch = false, options = {}, nsfw) => (dispatch, getState) => {
   const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
 
   if (!query) {
@@ -4086,7 +4086,8 @@ from, isBackgroundSearch = false, options = {}) => (dispatch, getState) => {
     dispatch(doUpdateSearchQuery(query));
   }
 
-  fetch(`${CONNECTION_STRING}search?resolve=true&${queryWithOptions}`).then(handleFetchResponse).then(data => {
+  const fetchUrl = nsfw ? `${CONNECTION_STRING}search?resolve=true&${queryWithOptions}` : `${CONNECTION_STRING}search?resolve=true&nsfw=false&${queryWithOptions}`;
+  fetch(fetchUrl).then(handleFetchResponse).then(data => {
     const results = [];
 
     data.forEach(result => {
diff --git a/src/redux/actions/search.js b/src/redux/actions/search.js
index e00d7ba..fc17b4b 100644
--- a/src/redux/actions/search.js
+++ b/src/redux/actions/search.js
@@ -167,7 +167,8 @@ export const doResolvedSearch = (
   isBackgroundSearch: boolean = false,
   options: {
     related_to?: string,
-  } = {}
+  } = {},
+  nsfw: boolean
 ) => (dispatch: Dispatch, getState: GetState) => {
   const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
 
@@ -203,7 +204,10 @@ export const doResolvedSearch = (
     dispatch(doUpdateSearchQuery(query));
   }
 
-  fetch(`${CONNECTION_STRING}search?resolve=true&${queryWithOptions}`)
+  const fetchUrl = nsfw
+    ? `${CONNECTION_STRING}search?resolve=true&${queryWithOptions}`
+    : `${CONNECTION_STRING}search?resolve=true&nsfw=false&${queryWithOptions}`;
+  fetch(fetchUrl)
     .then(handleFetchResponse)
     .then((data: Array<ResolvedSearchResult>) => {
       const results = [];