remove search code
This commit is contained in:
parent
a1d5ce7e7e
commit
04e3ca8250
11 changed files with 247 additions and 1873 deletions
1126
dist/bundle.es.js
vendored
1126
dist/bundle.es.js
vendored
File diff suppressed because it is too large
Load diff
84
dist/flow-typed/Search.js
vendored
84
dist/flow-typed/Search.js
vendored
|
@ -1,84 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
declare type SearchSuggestion = {
|
||||
value: string,
|
||||
shorthand: string,
|
||||
type: string,
|
||||
};
|
||||
|
||||
declare type SearchOptions = {
|
||||
// :(
|
||||
// https://github.com/facebook/flow/issues/6492
|
||||
RESULT_COUNT: number,
|
||||
CLAIM_TYPE: string,
|
||||
INCLUDE_FILES: string,
|
||||
INCLUDE_CHANNELS: string,
|
||||
INCLUDE_FILES_AND_CHANNELS: string,
|
||||
MEDIA_AUDIO: string,
|
||||
MEDIA_VIDEO: string,
|
||||
MEDIA_TEXT: string,
|
||||
MEDIA_IMAGE: string,
|
||||
MEDIA_APPLICATION: string,
|
||||
};
|
||||
|
||||
declare type SearchState = {
|
||||
isActive: boolean,
|
||||
searchQuery: string,
|
||||
options: SearchOptions,
|
||||
suggestions: { [string]: Array<SearchSuggestion> },
|
||||
urisByQuery: {},
|
||||
resolvedResultsByQuery: {},
|
||||
resolvedResultsByQueryLastPageReached: {},
|
||||
};
|
||||
|
||||
declare type SearchSuccess = {
|
||||
type: ACTIONS.SEARCH_SUCCESS,
|
||||
data: {
|
||||
query: string,
|
||||
uris: Array<string>,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchQuery = {
|
||||
type: ACTIONS.UPDATE_SEARCH_QUERY,
|
||||
data: {
|
||||
query: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchSuggestions = {
|
||||
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
|
||||
data: {
|
||||
query: string,
|
||||
suggestions: Array<SearchSuggestion>,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchOptions = {
|
||||
type: ACTIONS.UPDATE_SEARCH_OPTIONS,
|
||||
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,
|
||||
},
|
||||
};
|
84
flow-typed/Search.js
vendored
84
flow-typed/Search.js
vendored
|
@ -1,84 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
declare type SearchSuggestion = {
|
||||
value: string,
|
||||
shorthand: string,
|
||||
type: string,
|
||||
};
|
||||
|
||||
declare type SearchOptions = {
|
||||
// :(
|
||||
// https://github.com/facebook/flow/issues/6492
|
||||
RESULT_COUNT: number,
|
||||
CLAIM_TYPE: string,
|
||||
INCLUDE_FILES: string,
|
||||
INCLUDE_CHANNELS: string,
|
||||
INCLUDE_FILES_AND_CHANNELS: string,
|
||||
MEDIA_AUDIO: string,
|
||||
MEDIA_VIDEO: string,
|
||||
MEDIA_TEXT: string,
|
||||
MEDIA_IMAGE: string,
|
||||
MEDIA_APPLICATION: string,
|
||||
};
|
||||
|
||||
declare type SearchState = {
|
||||
isActive: boolean,
|
||||
searchQuery: string,
|
||||
options: SearchOptions,
|
||||
suggestions: { [string]: Array<SearchSuggestion> },
|
||||
urisByQuery: {},
|
||||
resolvedResultsByQuery: {},
|
||||
resolvedResultsByQueryLastPageReached: {},
|
||||
};
|
||||
|
||||
declare type SearchSuccess = {
|
||||
type: ACTIONS.SEARCH_SUCCESS,
|
||||
data: {
|
||||
query: string,
|
||||
uris: Array<string>,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchQuery = {
|
||||
type: ACTIONS.UPDATE_SEARCH_QUERY,
|
||||
data: {
|
||||
query: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchSuggestions = {
|
||||
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
|
||||
data: {
|
||||
query: string,
|
||||
suggestions: Array<SearchSuggestion>,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchOptions = {
|
||||
type: ACTIONS.UPDATE_SEARCH_OPTIONS,
|
||||
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,
|
||||
},
|
||||
};
|
|
@ -172,19 +172,6 @@ export const PURCHASE_URI_COMPLETED = 'PURCHASE_URI_COMPLETED';
|
|||
export const PURCHASE_URI_FAILED = 'PURCHASE_URI_FAILED';
|
||||
export const CLEAR_PURCHASED_URI_SUCCESS = 'CLEAR_PURCHASED_URI_SUCCESS';
|
||||
|
||||
// Search
|
||||
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';
|
||||
export const SEARCH_FOCUS = 'SEARCH_FOCUS';
|
||||
export const SEARCH_BLUR = 'SEARCH_BLUR';
|
||||
|
||||
// Settings
|
||||
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED';
|
||||
export const DAEMON_STATUS_RECEIVED = 'DAEMON_STATUS_RECEIVED';
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
export const SEARCH_TYPES = {
|
||||
FILE: 'file',
|
||||
CHANNEL: 'channel',
|
||||
SEARCH: 'search',
|
||||
TAG: 'tag',
|
||||
};
|
||||
|
||||
export const SEARCH_OPTIONS = {
|
||||
RESULT_COUNT: 'size',
|
||||
CLAIM_TYPE: 'claimType',
|
||||
INCLUDE_FILES: 'file',
|
||||
INCLUDE_CHANNELS: 'channel',
|
||||
INCLUDE_FILES_AND_CHANNELS: 'file,channel',
|
||||
MEDIA_AUDIO: 'audio',
|
||||
MEDIA_VIDEO: 'video',
|
||||
MEDIA_TEXT: 'text',
|
||||
MEDIA_IMAGE: 'image',
|
||||
MEDIA_APPLICATION: 'application',
|
||||
};
|
34
src/index.js
34
src/index.js
|
@ -12,11 +12,9 @@ import * as TXO_LIST from 'constants/txo_list';
|
|||
import * as SPEECH_URLS from 'constants/speech_urls';
|
||||
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
|
||||
import * as SHARED_PREFERENCES from 'constants/shared_preferences';
|
||||
import { SEARCH_TYPES, SEARCH_OPTIONS } from 'constants/search';
|
||||
import { DEFAULT_KNOWN_TAGS, DEFAULT_FOLLOWED_TAGS, MATURE_TAGS } from 'constants/tags';
|
||||
import Lbry, { apiCall } from 'lbry';
|
||||
import LbryFirst from 'lbry-first';
|
||||
import { selectState as selectSearchState } from 'redux/selectors/search';
|
||||
|
||||
// constants
|
||||
export {
|
||||
|
@ -24,8 +22,6 @@ export {
|
|||
CLAIM_VALUES,
|
||||
LICENSES,
|
||||
THUMBNAIL_STATUSES,
|
||||
SEARCH_TYPES,
|
||||
SEARCH_OPTIONS,
|
||||
SETTINGS,
|
||||
DAEMON_SETTINGS,
|
||||
TRANSACTIONS,
|
||||
|
@ -101,16 +97,6 @@ export {
|
|||
doCheckReflectingFiles,
|
||||
} from 'redux/actions/publish';
|
||||
|
||||
export {
|
||||
doSearch,
|
||||
doResolvedSearch,
|
||||
doUpdateSearchQuery,
|
||||
doFocusSearchInput,
|
||||
doBlurSearchInput,
|
||||
setSearchApi,
|
||||
doUpdateSearchOptions,
|
||||
} from 'redux/actions/search';
|
||||
|
||||
export { savePosition } from 'redux/actions/content';
|
||||
|
||||
export {
|
||||
|
@ -152,7 +138,6 @@ export { contentReducer } from 'redux/reducers/content';
|
|||
export { fileInfoReducer } from 'redux/reducers/file_info';
|
||||
export { notificationsReducer } from 'redux/reducers/notifications';
|
||||
export { publishReducer } from 'redux/reducers/publish';
|
||||
export { searchReducer } from 'redux/reducers/search';
|
||||
export { tagsReducer } from 'redux/reducers/tags';
|
||||
export { walletReducer } from 'redux/reducers/wallet';
|
||||
|
||||
|
@ -184,9 +169,6 @@ export {
|
|||
makeSelectNsfwCountForChannel,
|
||||
makeSelectOmittedCountForChannel,
|
||||
makeSelectClaimIsNsfw,
|
||||
makeSelectRecommendedContentForUri,
|
||||
makeSelectResolvedRecommendedContentForUri,
|
||||
makeSelectFirstRecommendedFileForUri,
|
||||
makeSelectChannelForClaimUri,
|
||||
makeSelectClaimIsPending,
|
||||
makeSelectReflectingClaimForUri,
|
||||
|
@ -280,22 +262,6 @@ export {
|
|||
selectTakeOverAmount,
|
||||
} from 'redux/selectors/publish';
|
||||
|
||||
export { selectSearchState };
|
||||
export {
|
||||
makeSelectSearchUris,
|
||||
makeSelectResolvedSearchResults,
|
||||
makeSelectResolvedSearchResultsLastPageReached,
|
||||
selectSearchValue,
|
||||
selectSearchOptions,
|
||||
selectIsSearching,
|
||||
selectResolvedSearchResultsByQuery,
|
||||
selectResolvedSearchResultsByQueryLastPageReached,
|
||||
selectSearchUrisByQuery,
|
||||
selectSearchBarFocused,
|
||||
selectSearchSuggestions,
|
||||
makeSelectQueryWithOptions,
|
||||
} from 'redux/selectors/search';
|
||||
|
||||
export {
|
||||
selectBalance,
|
||||
selectTotalBalance,
|
||||
|
|
|
@ -1,278 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { buildURI } from 'lbryURI';
|
||||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import {
|
||||
makeSelectSearchUris,
|
||||
makeSelectResolvedSearchResults,
|
||||
selectSuggestions,
|
||||
makeSelectQueryWithOptions,
|
||||
selectSearchValue,
|
||||
} from 'redux/selectors/search';
|
||||
import { batchActions } from 'util/batch-actions';
|
||||
import debounce from 'util/debounce';
|
||||
import handleFetchResponse from 'util/handle-fetch';
|
||||
|
||||
const DEBOUNCED_SEARCH_SUGGESTION_MS = 300;
|
||||
type Dispatch = (action: any) => any;
|
||||
type GetState = () => { search: SearchState };
|
||||
|
||||
type SearchOptions = {
|
||||
size?: number,
|
||||
from?: number,
|
||||
related_to?: string,
|
||||
nsfw?: boolean,
|
||||
isBackgroundSearch?: boolean,
|
||||
resolveResults?: boolean,
|
||||
};
|
||||
|
||||
// We can't use env's because they aren't passed into node_modules
|
||||
let CONNECTION_STRING = 'https://lighthouse.lbry.com/';
|
||||
|
||||
export const setSearchApi = (endpoint: string) => {
|
||||
CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end;
|
||||
};
|
||||
|
||||
export const getSearchSuggestions = (value: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const query = value.trim();
|
||||
|
||||
// strip out any basic stuff for more accurate search results
|
||||
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(`${CONNECTION_STRING}autocomplete?s=${searchValue}`)
|
||||
.then(handleFetchResponse)
|
||||
.then(apiSuggestions => {
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
|
||||
data: {
|
||||
query: searchValue,
|
||||
suggestions: apiSuggestions,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// If the fetch fails, do nothing
|
||||
// Basic search suggestions are already populated at this point
|
||||
});
|
||||
};
|
||||
|
||||
const throttledSearchSuggestions = debounce((dispatch, query) => {
|
||||
dispatch(getSearchSuggestions(query));
|
||||
}, DEBOUNCED_SEARCH_SUGGESTION_MS);
|
||||
|
||||
export const doUpdateSearchQuery = (query: string, shouldSkipSuggestions: ?boolean) => (
|
||||
dispatch: Dispatch
|
||||
) => {
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_SEARCH_QUERY,
|
||||
data: { query },
|
||||
});
|
||||
|
||||
// Don't fetch new suggestions if the user just added a space
|
||||
if (!query.endsWith(' ') || !shouldSkipSuggestions) {
|
||||
throttledSearchSuggestions(dispatch, query);
|
||||
}
|
||||
};
|
||||
|
||||
export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
||||
const resolveResults = searchOptions && searchOptions.resolveResults;
|
||||
const isBackgroundSearch = (searchOptions && searchOptions.isBackgroundSearch) || false;
|
||||
|
||||
if (!query) {
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_FAIL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
|
||||
let queryWithOptions = makeSelectQueryWithOptions(query, searchOptions)(state);
|
||||
|
||||
// If we have already searched for something, we don't need to do anything
|
||||
const urisForQuery = makeSelectSearchUris(queryWithOptions)(state);
|
||||
if (urisForQuery && !!urisForQuery.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_START,
|
||||
});
|
||||
|
||||
// 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
|
||||
// isBackgroundSearch means the search is happening in the background, don't update the search query
|
||||
if (!state.search.searchQuery && !isBackgroundSearch) {
|
||||
dispatch(doUpdateSearchQuery(query));
|
||||
}
|
||||
|
||||
fetch(`${CONNECTION_STRING}search?${queryWithOptions}`)
|
||||
.then(handleFetchResponse)
|
||||
.then((data: Array<{ name: string, claimId: string }>) => {
|
||||
const uris = [];
|
||||
const actions = [];
|
||||
|
||||
data.forEach(result => {
|
||||
if (result) {
|
||||
const { name, claimId } = result;
|
||||
const urlObj: LbryUrlObj = {};
|
||||
|
||||
if (name.startsWith('@')) {
|
||||
urlObj.channelName = name;
|
||||
urlObj.channelClaimId = claimId;
|
||||
} else {
|
||||
urlObj.streamName = name;
|
||||
urlObj.streamClaimId = claimId;
|
||||
}
|
||||
|
||||
const url = buildURI(urlObj);
|
||||
if (resolveResults) {
|
||||
actions.push(doResolveUri(url));
|
||||
}
|
||||
uris.push(url);
|
||||
}
|
||||
});
|
||||
|
||||
actions.push({
|
||||
type: ACTIONS.SEARCH_SUCCESS,
|
||||
data: {
|
||||
query: queryWithOptions,
|
||||
uris,
|
||||
},
|
||||
});
|
||||
dispatch(batchActions(...actions));
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_FAIL,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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,
|
||||
} = {},
|
||||
nsfw: boolean
|
||||
) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
||||
|
||||
if (!query) {
|
||||
dispatch({
|
||||
type: ACTIONS.RESOLVED_SEARCH_FAIL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const optionsWithFrom: SearchOptions = {
|
||||
...(size ? { size } : {}),
|
||||
...(from ? { from } : {}),
|
||||
isBackgroundSearch,
|
||||
...options,
|
||||
};
|
||||
|
||||
const optionsWithoutFrom: SearchOptions = {
|
||||
...(size ? { size } : {}),
|
||||
isBackgroundSearch,
|
||||
...options,
|
||||
};
|
||||
|
||||
const state = getState();
|
||||
|
||||
let queryWithOptions = makeSelectQueryWithOptions(query, optionsWithFrom)(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, optionsWithoutFrom)(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));
|
||||
}
|
||||
|
||||
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 = [];
|
||||
|
||||
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) =>
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_FOCUS,
|
||||
});
|
||||
|
||||
export const doBlurSearchInput = () => (dispatch: Dispatch) =>
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_BLUR,
|
||||
});
|
||||
|
||||
export const doUpdateSearchOptions = (
|
||||
newOptions: SearchOptions,
|
||||
additionalOptions: SearchOptions
|
||||
) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const searchValue = selectSearchValue(state);
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_SEARCH_OPTIONS,
|
||||
data: newOptions,
|
||||
});
|
||||
|
||||
if (searchValue) {
|
||||
// After updating, perform a search with the new options
|
||||
dispatch(doSearch(searchValue, additionalOptions));
|
||||
}
|
||||
};
|
|
@ -1,137 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
import { SEARCH_OPTIONS } from 'constants/search';
|
||||
|
||||
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
|
||||
options: {
|
||||
[SEARCH_OPTIONS.RESULT_COUNT]: 30,
|
||||
[SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
|
||||
[SEARCH_OPTIONS.MEDIA_AUDIO]: true,
|
||||
[SEARCH_OPTIONS.MEDIA_VIDEO]: true,
|
||||
[SEARCH_OPTIONS.MEDIA_TEXT]: true,
|
||||
[SEARCH_OPTIONS.MEDIA_IMAGE]: true,
|
||||
[SEARCH_OPTIONS.MEDIA_APPLICATION]: true,
|
||||
},
|
||||
suggestions: {},
|
||||
urisByQuery: {},
|
||||
resolvedResultsByQuery: {},
|
||||
resolvedResultsByQueryLastPageReached: {},
|
||||
};
|
||||
|
||||
export const searchReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.SEARCH_START]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
searching: true,
|
||||
}),
|
||||
[ACTIONS.SEARCH_SUCCESS]: (state: SearchState, action: SearchSuccess): SearchState => {
|
||||
const { query, uris } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
searching: false,
|
||||
urisByQuery: Object.assign({}, state.urisByQuery, { [query]: uris }),
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.SEARCH_FAIL]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
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]: (
|
||||
state: SearchState,
|
||||
action: UpdateSearchQuery
|
||||
): SearchState => ({
|
||||
...state,
|
||||
searchQuery: action.data.query,
|
||||
isActive: true,
|
||||
}),
|
||||
|
||||
[ACTIONS.UPDATE_SEARCH_SUGGESTIONS]: (
|
||||
state: SearchState,
|
||||
action: UpdateSearchSuggestions
|
||||
): SearchState => ({
|
||||
...state,
|
||||
suggestions: {
|
||||
...state.suggestions,
|
||||
[action.data.query]: action.data.suggestions,
|
||||
},
|
||||
}),
|
||||
|
||||
// sets isActive to false so the uri will be populated correctly if the
|
||||
// user is on a file page. The search query will still be present on any
|
||||
// other page
|
||||
[ACTIONS.DISMISS_NOTIFICATION]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
isActive: false,
|
||||
}),
|
||||
|
||||
[ACTIONS.SEARCH_FOCUS]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
focused: true,
|
||||
}),
|
||||
[ACTIONS.SEARCH_BLUR]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
focused: false,
|
||||
}),
|
||||
[ACTIONS.UPDATE_SEARCH_OPTIONS]: (
|
||||
state: SearchState,
|
||||
action: UpdateSearchOptions
|
||||
): SearchState => {
|
||||
const { options: oldOptions } = state;
|
||||
const newOptions = action.data;
|
||||
const options = { ...oldOptions, ...newOptions };
|
||||
return {
|
||||
...state,
|
||||
options,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
|
@ -1,13 +1,8 @@
|
|||
// @flow
|
||||
import { normalizeURI, buildURI, parseURI } from 'lbryURI';
|
||||
import {
|
||||
selectResolvedSearchResultsByQuery,
|
||||
selectSearchUrisByQuery,
|
||||
} from 'redux/selectors/search';
|
||||
import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
|
||||
import { createSelector } from 'reselect';
|
||||
import { isClaimNsfw, filterClaims } from 'util/claim';
|
||||
import { getSearchQueryString } from 'util/query-params';
|
||||
import { PAGE_SIZE } from 'constants/claim';
|
||||
|
||||
const selectState = state => state.claims || {};
|
||||
|
@ -632,53 +627,6 @@ export const makeSelectClaimIsNsfw = (uri: string): boolean =>
|
|||
}
|
||||
);
|
||||
|
||||
export const makeSelectRecommendedContentForUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
selectSearchUrisByQuery,
|
||||
makeSelectClaimIsNsfw(uri),
|
||||
(claim, searchUrisByQuery, isMature) => {
|
||||
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 options: {
|
||||
related_to?: string,
|
||||
nsfw?: boolean,
|
||||
isBackgroundSearch?: boolean,
|
||||
} = { related_to: claim.claim_id, isBackgroundSearch: true };
|
||||
|
||||
if (!isMature) {
|
||||
options['nsfw'] = false;
|
||||
}
|
||||
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
||||
|
||||
let searchUris = searchUrisByQuery[searchQuery];
|
||||
if (searchUris) {
|
||||
searchUris = searchUris.filter(searchUri => searchUri !== currentUri);
|
||||
recommendedContent = searchUris;
|
||||
}
|
||||
}
|
||||
|
||||
return recommendedContent;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectFirstRecommendedFileForUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectRecommendedContentForUri(uri),
|
||||
recommendedContent => (recommendedContent ? recommendedContent[0] : null)
|
||||
);
|
||||
|
||||
// Returns the associated channel uri for a given claim uri
|
||||
// accepts a regular claim uri lbry://something
|
||||
// returns the channel uri that created this claim lbry://@channel
|
||||
|
@ -803,54 +751,3 @@ export const selectMyStreamUrlsCount = createSelector(
|
|||
selectMyClaimUrisWithoutChannels,
|
||||
channels => channels.length
|
||||
);
|
||||
|
||||
export const makeSelectResolvedRecommendedContentForUri = (
|
||||
uri: string,
|
||||
size: number,
|
||||
claimId: string,
|
||||
claimName: string,
|
||||
claimTitle: string
|
||||
) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
selectResolvedSearchResultsByQuery,
|
||||
makeSelectClaimIsNsfw(uri),
|
||||
(claim, resolvedResultsByQuery, isMature) => {
|
||||
const atVanityURI = !uri.includes('#');
|
||||
|
||||
let currentUri;
|
||||
let recommendedContent;
|
||||
let title;
|
||||
if (claim) {
|
||||
// always grab full URL - this can change once search returns canonical
|
||||
currentUri = buildURI({ streamClaimId: claim.claim_id, streamName: claim.name });
|
||||
title = claim.value ? claim.value.title : null;
|
||||
} else {
|
||||
// for cases on mobile where the claim may not have been resolved ()
|
||||
currentUri = buildURI({ streamClaimId: claimId, streamName: claimName });
|
||||
title = claimTitle;
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options: {
|
||||
related_to?: string,
|
||||
nsfw?: boolean,
|
||||
isBackgroundSearch?: boolean,
|
||||
} = { related_to: claim ? claim.claim_id : claimId, size, isBackgroundSearch: false };
|
||||
|
||||
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
||||
let results = resolvedResultsByQuery[searchQuery];
|
||||
if (results) {
|
||||
results = results.filter(
|
||||
result =>
|
||||
buildURI({ streamClaimId: result.claimId, streamName: result.name }) !== currentUri
|
||||
);
|
||||
recommendedContent = results;
|
||||
}
|
||||
|
||||
return recommendedContent;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,187 +0,0 @@
|
|||
// @flow
|
||||
import { SEARCH_TYPES, SEARCH_OPTIONS } from 'constants/search';
|
||||
import { getSearchQueryString } from 'util/query-params';
|
||||
import { normalizeURI, parseURI } from 'lbryURI';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
type State = { search: SearchState };
|
||||
|
||||
export const selectState = (state: State): SearchState => state.search;
|
||||
|
||||
export const selectSearchValue: (state: State) => string = createSelector(
|
||||
selectState,
|
||||
state => state.searchQuery
|
||||
);
|
||||
|
||||
export const selectSearchOptions: (state: State) => SearchOptions = createSelector(
|
||||
selectState,
|
||||
state => state.options
|
||||
);
|
||||
|
||||
export const selectSuggestions: (
|
||||
state: State
|
||||
) => { [string]: Array<SearchSuggestion> } = createSelector(
|
||||
selectState,
|
||||
state => state.suggestions
|
||||
);
|
||||
|
||||
export const selectIsSearching: (state: State) => boolean = createSelector(
|
||||
selectState,
|
||||
state => state.searching
|
||||
);
|
||||
|
||||
export const selectSearchUrisByQuery: (
|
||||
state: State
|
||||
) => { [string]: Array<string> } = createSelector(
|
||||
selectState,
|
||||
state => state.urisByQuery
|
||||
);
|
||||
|
||||
export const makeSelectSearchUris = (query: string): ((state: State) => Array<string>) =>
|
||||
// replace statement below is kind of ugly, and repeated in doSearch action
|
||||
createSelector(
|
||||
selectSearchUrisByQuery,
|
||||
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(
|
||||
selectState,
|
||||
state => state.focused
|
||||
);
|
||||
|
||||
export const selectSearchSuggestions: Array<SearchSuggestion> = createSelector(
|
||||
selectSearchValue,
|
||||
selectSuggestions,
|
||||
(query: string, suggestions: { [string]: Array<string> }) => {
|
||||
if (!query) {
|
||||
return [];
|
||||
}
|
||||
const queryIsPrefix =
|
||||
query === 'lbry:' || query === 'lbry:/' || query === 'lbry://' || query === 'lbry://@';
|
||||
|
||||
if (queryIsPrefix) {
|
||||
// If it is a prefix, wait until something else comes to figure out what to do
|
||||
return [];
|
||||
} else if (query.startsWith('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: query[7] === '@' ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
let searchSuggestions = [];
|
||||
try {
|
||||
const uri = normalizeURI(query);
|
||||
const { channelName, streamName, isChannel } = parseURI(uri);
|
||||
searchSuggestions.push(
|
||||
{
|
||||
value: query,
|
||||
type: SEARCH_TYPES.SEARCH,
|
||||
},
|
||||
{
|
||||
value: uri,
|
||||
shorthand: isChannel ? channelName : streamName,
|
||||
type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE,
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
searchSuggestions.push({
|
||||
value: query,
|
||||
type: SEARCH_TYPES.SEARCH,
|
||||
});
|
||||
}
|
||||
|
||||
searchSuggestions.push({
|
||||
value: query,
|
||||
type: SEARCH_TYPES.TAG,
|
||||
});
|
||||
|
||||
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 { channelName, streamName, isChannel } = parseURI(uri);
|
||||
|
||||
return {
|
||||
value: uri,
|
||||
shorthand: isChannel ? channelName : streamName,
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
// Creates a query string based on the state in the search reducer
|
||||
// Can be overrided by passing in custom sizes/from values for other areas pagination
|
||||
|
||||
type CustomOptions = {
|
||||
isBackgroundSearch?: boolean,
|
||||
size?: number,
|
||||
from?: number,
|
||||
related_to?: string,
|
||||
nsfw?: boolean,
|
||||
};
|
||||
|
||||
export const makeSelectQueryWithOptions = (customQuery: ?string, options: CustomOptions) =>
|
||||
createSelector(
|
||||
selectSearchValue,
|
||||
selectSearchOptions,
|
||||
(query, defaultOptions) => {
|
||||
const searchOptions = { ...defaultOptions, ...options };
|
||||
const queryString = getSearchQueryString(customQuery || query, searchOptions);
|
||||
|
||||
return queryString;
|
||||
}
|
||||
);
|
|
@ -1,8 +1,4 @@
|
|||
// @flow
|
||||
import { SEARCH_OPTIONS } from 'constants/search';
|
||||
|
||||
const DEFAULT_SEARCH_RESULT_FROM = 0;
|
||||
const DEFAULT_SEARCH_SIZE = 20;
|
||||
|
||||
export function parseQueryParams(queryString: string) {
|
||||
if (queryString === '') return {};
|
||||
|
@ -32,54 +28,3 @@ export function toQueryString(params: { [string]: string | number }) {
|
|||
|
||||
return parts.join('&');
|
||||
}
|
||||
|
||||
export const getSearchQueryString = (query: string, options: any = {}) => {
|
||||
const encodedQuery = encodeURIComponent(query);
|
||||
const queryParams = [
|
||||
`s=${encodedQuery}`,
|
||||
`size=${options.size || DEFAULT_SEARCH_SIZE}`,
|
||||
`from=${options.from || DEFAULT_SEARCH_RESULT_FROM}`,
|
||||
];
|
||||
const { isBackgroundSearch } = options;
|
||||
const includeUserOptions =
|
||||
typeof isBackgroundSearch === 'undefined' ? false : !isBackgroundSearch;
|
||||
|
||||
if (includeUserOptions) {
|
||||
const claimType = options[SEARCH_OPTIONS.CLAIM_TYPE];
|
||||
if (claimType) {
|
||||
queryParams.push(`claimType=${claimType}`);
|
||||
|
||||
// If they are only searching for channels, strip out the media info
|
||||
if (!claimType.includes(SEARCH_OPTIONS.INCLUDE_CHANNELS)) {
|
||||
queryParams.push(
|
||||
`mediaType=${[
|
||||
SEARCH_OPTIONS.MEDIA_FILE,
|
||||
SEARCH_OPTIONS.MEDIA_AUDIO,
|
||||
SEARCH_OPTIONS.MEDIA_VIDEO,
|
||||
SEARCH_OPTIONS.MEDIA_TEXT,
|
||||
SEARCH_OPTIONS.MEDIA_IMAGE,
|
||||
SEARCH_OPTIONS.MEDIA_APPLICATION,
|
||||
].reduce(
|
||||
(acc, currentOption) => (options[currentOption] ? `${acc}${currentOption},` : acc),
|
||||
''
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const additionalOptions = {};
|
||||
const { related_to } = options;
|
||||
const { nsfw } = options;
|
||||
if (related_to) additionalOptions['related_to'] = related_to;
|
||||
if (typeof nsfw !== 'undefined') additionalOptions['nsfw'] = nsfw;
|
||||
|
||||
if (additionalOptions) {
|
||||
Object.keys(additionalOptions).forEach(key => {
|
||||
const option = additionalOptions[key];
|
||||
queryParams.push(`${key}=${option}`);
|
||||
});
|
||||
}
|
||||
|
||||
return queryParams.join('&');
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue