feat: add support for search filters
This commit is contained in:
parent
ec1d5bd41a
commit
2b725cb317
12 changed files with 708 additions and 432 deletions
772
dist/bundle.js
vendored
772
dist/bundle.js
vendored
File diff suppressed because it is too large
Load diff
|
@ -117,6 +117,7 @@ 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 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_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS';
|
export const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS';
|
||||||
export const SEARCH_FOCUS = 'SEARCH_FOCUS';
|
export const SEARCH_FOCUS = 'SEARCH_FOCUS';
|
||||||
export const SEARCH_BLUR = 'SEARCH_BLUR';
|
export const SEARCH_BLUR = 'SEARCH_BLUR';
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
export const FILE = 'file';
|
export const SEARCH_TYPES = {
|
||||||
export const CHANNEL = 'channel';
|
FILE: 'file',
|
||||||
export const SEARCH = 'search';
|
CHANNEL: 'channel',
|
||||||
|
SEARCH: 'search',
|
||||||
|
};
|
||||||
|
|
||||||
|
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',
|
||||||
|
};
|
||||||
|
|
16
src/index.js
16
src/index.js
|
@ -1,10 +1,10 @@
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
||||||
import * as SEARCH_TYPES from 'constants/search';
|
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import * as TRANSACTIONS from 'constants/transaction_types';
|
import * as TRANSACTIONS from 'constants/transaction_types';
|
||||||
import * as SORT_OPTIONS from 'constants/sort_options';
|
import * as SORT_OPTIONS from 'constants/sort_options';
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
|
import { SEARCH_TYPES, SEARCH_OPTIONS } from 'constants/search';
|
||||||
|
|
||||||
import Lbry from 'lbry';
|
import Lbry from 'lbry';
|
||||||
import Lbryapi from 'lbryapi';
|
import Lbryapi from 'lbryapi';
|
||||||
|
@ -14,7 +14,16 @@ import { selectState as selectSearchState } from 'redux/selectors/search';
|
||||||
export { Toast } from 'types/Notification';
|
export { Toast } from 'types/Notification';
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
export { ACTIONS, THUMBNAIL_STATUSES, SEARCH_TYPES, SETTINGS, TRANSACTIONS, SORT_OPTIONS, PAGES };
|
export {
|
||||||
|
ACTIONS,
|
||||||
|
THUMBNAIL_STATUSES,
|
||||||
|
SEARCH_TYPES,
|
||||||
|
SEARCH_OPTIONS,
|
||||||
|
SETTINGS,
|
||||||
|
TRANSACTIONS,
|
||||||
|
SORT_OPTIONS,
|
||||||
|
PAGES,
|
||||||
|
};
|
||||||
|
|
||||||
// common
|
// common
|
||||||
export { Lbry, Lbryapi };
|
export { Lbry, Lbryapi };
|
||||||
|
@ -59,6 +68,7 @@ export {
|
||||||
doFocusSearchInput,
|
doFocusSearchInput,
|
||||||
doBlurSearchInput,
|
doBlurSearchInput,
|
||||||
setSearchApi,
|
setSearchApi,
|
||||||
|
doUpdateSearchOptions,
|
||||||
} from 'redux/actions/search';
|
} from 'redux/actions/search';
|
||||||
|
|
||||||
export { doBlackListedOutpointsSubscribe } from 'redux/actions/blacklist';
|
export { doBlackListedOutpointsSubscribe } from 'redux/actions/blacklist';
|
||||||
|
@ -191,11 +201,13 @@ export {
|
||||||
makeSelectSearchUris,
|
makeSelectSearchUris,
|
||||||
selectSearchQuery,
|
selectSearchQuery,
|
||||||
selectSearchValue,
|
selectSearchValue,
|
||||||
|
selectSearchOptions,
|
||||||
selectIsSearching,
|
selectIsSearching,
|
||||||
selectSearchUrisByQuery,
|
selectSearchUrisByQuery,
|
||||||
selectWunderBarAddress,
|
selectWunderBarAddress,
|
||||||
selectSearchBarFocused,
|
selectSearchBarFocused,
|
||||||
selectSearchSuggestions,
|
selectSearchSuggestions,
|
||||||
|
makeSelectQueryWithOptions,
|
||||||
} from 'redux/selectors/search';
|
} from 'redux/selectors/search';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -1,32 +1,35 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import type { SearchState, SearchOptions } from 'types/Search';
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import { buildURI } from 'lbryURI';
|
import { buildURI } from 'lbryURI';
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { makeSelectSearchUris, selectSuggestions } from 'redux/selectors/search';
|
import {
|
||||||
|
makeSelectSearchUris,
|
||||||
|
selectSuggestions,
|
||||||
|
makeSelectQueryWithOptions,
|
||||||
|
selectSearchQuery,
|
||||||
|
} from 'redux/selectors/search';
|
||||||
import { batchActions } from 'util/batchActions';
|
import { batchActions } from 'util/batchActions';
|
||||||
import debounce from 'util/debounce';
|
import debounce from 'util/debounce';
|
||||||
import handleFetchResponse from 'util/handle-fetch';
|
import handleFetchResponse from 'util/handle-fetch';
|
||||||
|
|
||||||
const DEFAULTSEARCHRESULTSIZE = 10;
|
|
||||||
const DEFAULTSEARCHRESULTFROM = 0;
|
|
||||||
const DEBOUNCED_SEARCH_SUGGESTION_MS = 300;
|
const DEBOUNCED_SEARCH_SUGGESTION_MS = 300;
|
||||||
type Dispatch = (action: any) => any;
|
type Dispatch = (action: any) => any;
|
||||||
type GetState = () => {};
|
type GetState = () => { search: SearchState };
|
||||||
|
|
||||||
// We can't use env's because they aren't passed into node_modules
|
// We can't use env's because they aren't passed into node_modules
|
||||||
let CONNECTION_STRING = 'https://lighthouse.lbry.io/';
|
let CONNECTION_STRING = 'https://lighthouse.lbry.io/';
|
||||||
|
|
||||||
export const setSearchApi = endpoint => {
|
export const setSearchApi = (endpoint: string) => {
|
||||||
CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end;
|
CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doSearch = (
|
export const doSearch = (
|
||||||
rawQuery: string,
|
rawQuery: string, // pass in a query if you don't want to search for what's in the search bar
|
||||||
size: number = DEFAULTSEARCHRESULTSIZE,
|
size: ?number, // only pass in if you don't want to use the users setting (ex: related content)
|
||||||
from: number = DEFAULTSEARCHRESULTFROM,
|
from: ?number,
|
||||||
isBackgroundSearch: boolean = false
|
isBackgroundSearch: boolean = false
|
||||||
) => (dispatch: Dispatch, getState: GetState) => {
|
) => (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = getState();
|
|
||||||
const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
|
@ -36,8 +39,11 @@ export const doSearch = (
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const state = getState();
|
||||||
|
const queryWithOptions = makeSelectQueryWithOptions(query, size, from, isBackgroundSearch)(state);
|
||||||
|
|
||||||
// If we have already searched for something, we don't need to do anything
|
// If we have already searched for something, we don't need to do anything
|
||||||
const urisForQuery = makeSelectSearchUris(query)(state);
|
const urisForQuery = makeSelectSearchUris(queryWithOptions)(state);
|
||||||
if (urisForQuery && !!urisForQuery.length) {
|
if (urisForQuery && !!urisForQuery.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -57,8 +63,7 @@ export const doSearch = (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const encodedQuery = encodeURIComponent(query);
|
fetch(`${CONNECTION_STRING}search?${queryWithOptions}`)
|
||||||
fetch(`${CONNECTION_STRING}search?s=${encodedQuery}&size=${size}&from=${from}`)
|
|
||||||
.then(handleFetchResponse)
|
.then(handleFetchResponse)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const uris = [];
|
const uris = [];
|
||||||
|
@ -76,7 +81,7 @@ export const doSearch = (
|
||||||
actions.push({
|
actions.push({
|
||||||
type: ACTIONS.SEARCH_SUCCESS,
|
type: ACTIONS.SEARCH_SUCCESS,
|
||||||
data: {
|
data: {
|
||||||
query,
|
query: queryWithOptions,
|
||||||
uris,
|
uris,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -149,3 +154,21 @@ export const doBlurSearchInput = () => (dispatch: Dispatch) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.SEARCH_BLUR,
|
type: ACTIONS.SEARCH_BLUR,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const doUpdateSearchOptions = (newOptions: SearchOptions) => (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: GetState
|
||||||
|
) => {
|
||||||
|
const state = getState();
|
||||||
|
const searchQuery = selectSearchQuery(state);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.UPDATE_SEARCH_OPTIONS,
|
||||||
|
data: newOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (searchQuery) {
|
||||||
|
// After updating, perform a search with the new options
|
||||||
|
dispatch(doSearch(searchQuery));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import type {
|
import type {
|
||||||
NotificationState,
|
NotificationState,
|
||||||
DoToast,
|
DoToast,
|
||||||
|
DoError,
|
||||||
DoNotification,
|
DoNotification,
|
||||||
DoEditNotification,
|
DoEditNotification,
|
||||||
DoDeleteNotification,
|
DoDeleteNotification,
|
||||||
|
@ -55,7 +56,7 @@ const notificationsReducer = handleActions(
|
||||||
let notifications = state.notifications.slice();
|
let notifications = state.notifications.slice();
|
||||||
|
|
||||||
notifications = notifications.map(
|
notifications = notifications.map(
|
||||||
(pastNotification) =>
|
pastNotification =>
|
||||||
pastNotification.id === notification.id ? notification : pastNotification
|
pastNotification.id === notification.id ? notification : pastNotification
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -67,7 +68,7 @@ const notificationsReducer = handleActions(
|
||||||
[ACTIONS.DELETE_NOTIFICATION]: (state: NotificationState, action: DoDeleteNotification) => {
|
[ACTIONS.DELETE_NOTIFICATION]: (state: NotificationState, action: DoDeleteNotification) => {
|
||||||
const { id } = action.data;
|
const { id } = action.data;
|
||||||
let newNotifications = state.notifications.slice();
|
let newNotifications = state.notifications.slice();
|
||||||
newNotifications = newNotifications.filter((notification) => notification.id !== id);
|
newNotifications = newNotifications.filter(notification => notification.id !== id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -76,7 +77,7 @@ const notificationsReducer = handleActions(
|
||||||
},
|
},
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
[ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoToast) => {
|
[ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoError) => {
|
||||||
const error = action.data;
|
const error = action.data;
|
||||||
const newErrors = state.errors.slice();
|
const newErrors = state.errors.slice();
|
||||||
newErrors.push(error);
|
newErrors.push(error);
|
||||||
|
|
|
@ -1,56 +1,29 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import { handleActions } from 'util/redux-utils';
|
import { handleActions } from 'util/redux-utils';
|
||||||
|
import { SEARCH_OPTIONS } from 'constants/search';
|
||||||
type SearchSuccess = {
|
import type {
|
||||||
type: ACTIONS.SEARCH_SUCCESS,
|
SearchState,
|
||||||
data: {
|
SearchSuccess,
|
||||||
query: string,
|
UpdateSearchQuery,
|
||||||
uris: Array<string>,
|
UpdateSearchSuggestions,
|
||||||
},
|
HistoryNavigate,
|
||||||
};
|
UpdateSearchOptions,
|
||||||
|
} from 'types/Search';
|
||||||
type UpdateSearchQuery = {
|
|
||||||
type: ACTIONS.UPDATE_SEARCH_QUERY,
|
|
||||||
data: {
|
|
||||||
query: string,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type SearchSuggestion = {
|
|
||||||
value: string,
|
|
||||||
shorthand: string,
|
|
||||||
type: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
type UpdateSearchSuggestions = {
|
|
||||||
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
|
|
||||||
data: {
|
|
||||||
query: string,
|
|
||||||
suggestions: Array<SearchSuggestion>,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type SearchState = {
|
|
||||||
isActive: boolean,
|
|
||||||
searchQuery: string,
|
|
||||||
suggestions: Array<SearchSuggestion>,
|
|
||||||
urisByQuery: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
type HistoryNavigate = {
|
|
||||||
type: ACTIONS.HISTORY_NAVIGATE,
|
|
||||||
data: {
|
|
||||||
url: string,
|
|
||||||
index?: number,
|
|
||||||
scrollY?: number,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
isActive: false, // does the user have any typed text in the search input
|
isActive: false, // does the user have any typed text in the search input
|
||||||
focused: false, // is the search input focused
|
focused: false, // is the search input focused
|
||||||
searchQuery: '', // needs to be an empty string for input focusing
|
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: {},
|
suggestions: {},
|
||||||
urisByQuery: {},
|
urisByQuery: {},
|
||||||
};
|
};
|
||||||
|
@ -123,6 +96,18 @@ export const searchReducer = handleActions(
|
||||||
...state,
|
...state,
|
||||||
focused: false,
|
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
|
defaultState
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,7 +17,7 @@ type ActionResult = {
|
||||||
type WalletState = {
|
type WalletState = {
|
||||||
balance: any,
|
balance: any,
|
||||||
blocks: any,
|
blocks: any,
|
||||||
latestBlock: number,
|
latestBlock: ?number,
|
||||||
transactions: any,
|
transactions: any,
|
||||||
fetchingTransactions: boolean,
|
fetchingTransactions: boolean,
|
||||||
gettingNewAddress: boolean,
|
gettingNewAddress: boolean,
|
||||||
|
@ -73,7 +73,7 @@ reducers[ACTIONS.FETCH_TRANSACTIONS_COMPLETED] = (state: WalletState, action) =>
|
||||||
|
|
||||||
const { transactions } = action.data;
|
const { transactions } = action.data;
|
||||||
|
|
||||||
transactions.forEach((transaction) => {
|
transactions.forEach(transaction => {
|
||||||
byId[transaction.txid] = transaction;
|
byId[transaction.txid] = transaction;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { makeSelectCurrentParam } from 'redux/selectors/navigation';
|
||||||
import { selectSearchUrisByQuery } from 'redux/selectors/search';
|
import { selectSearchUrisByQuery } from 'redux/selectors/search';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { isClaimNsfw } from 'util/claim';
|
import { isClaimNsfw } from 'util/claim';
|
||||||
|
import { getSearchQueryString } from 'util/query_params';
|
||||||
|
|
||||||
const selectState = state => state.claims || {};
|
const selectState = state => state.claims || {};
|
||||||
|
|
||||||
|
@ -297,7 +298,9 @@ export const makeSelectRecommendedContentForUri = uri =>
|
||||||
|
|
||||||
const { title } = claim.value.stream.metadata;
|
const { title } = claim.value.stream.metadata;
|
||||||
|
|
||||||
let searchUris = searchUrisByQuery[title.replace(/\//, ' ')];
|
const searchQuery = getSearchQueryString(title.replace(/\//, ' '));
|
||||||
|
|
||||||
|
let searchUris = searchUrisByQuery[searchQuery];
|
||||||
if (searchUris) {
|
if (searchUris) {
|
||||||
searchUris = searchUris.filter(searchUri => searchUri !== currentUri);
|
searchUris = searchUris.filter(searchUri => searchUri !== currentUri);
|
||||||
recommendedContent = searchUris;
|
recommendedContent = searchUris;
|
||||||
|
|
|
@ -1,25 +1,48 @@
|
||||||
import * as SEARCH_TYPES from 'constants/search';
|
// @flow
|
||||||
|
import type { SearchState, SearchOptions, SearchSuggestion } from 'types/Search';
|
||||||
|
import { SEARCH_TYPES, SEARCH_OPTIONS } from 'constants/search';
|
||||||
|
import { getSearchQueryString } from 'util/query_params';
|
||||||
import { normalizeURI, parseURI } from 'lbryURI';
|
import { normalizeURI, parseURI } from 'lbryURI';
|
||||||
import { selectCurrentPage, selectCurrentParams } from 'redux/selectors/navigation';
|
import { selectCurrentPage, selectCurrentParams } from 'redux/selectors/navigation';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
export const selectState = state => state.search || {};
|
type State = { search: SearchState };
|
||||||
|
|
||||||
export const selectSearchValue = createSelector(selectState, state => state.searchQuery);
|
export const selectState = (state: State): SearchState => state.search;
|
||||||
|
|
||||||
export const selectSuggestions = createSelector(selectState, state => state.suggestions);
|
export const selectSearchValue: (state: State) => string = createSelector(
|
||||||
|
selectState,
|
||||||
export const selectSearchQuery = createSelector(
|
state => state.searchQuery
|
||||||
selectCurrentPage,
|
|
||||||
selectCurrentParams,
|
|
||||||
(page, params) => (page === 'search' ? params && params.query : null)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectIsSearching = createSelector(selectState, state => state.searching);
|
export const selectSearchOptions: (state: State) => SearchOptions = createSelector(
|
||||||
|
selectState,
|
||||||
|
state => state.options
|
||||||
|
);
|
||||||
|
|
||||||
export const selectSearchUrisByQuery = createSelector(selectState, state => state.urisByQuery);
|
export const selectSuggestions: (
|
||||||
|
state: State
|
||||||
|
) => { [string]: Array<SearchSuggestion> } = createSelector(
|
||||||
|
selectState,
|
||||||
|
state => state.suggestions
|
||||||
|
);
|
||||||
|
|
||||||
export const makeSelectSearchUris = query =>
|
export const selectSearchQuery: (state: State) => ?string = createSelector(
|
||||||
|
selectCurrentPage,
|
||||||
|
selectCurrentParams,
|
||||||
|
(page: string, params: ?{ query: string }) => (page === 'search' ? params && params.query : null)
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
// replace statement below is kind of ugly, and repeated in doSearch action
|
||||||
createSelector(
|
createSelector(
|
||||||
selectSearchUrisByQuery,
|
selectSearchUrisByQuery,
|
||||||
|
@ -30,7 +53,7 @@ export const selectWunderBarAddress = createSelector(
|
||||||
selectCurrentPage,
|
selectCurrentPage,
|
||||||
selectSearchQuery,
|
selectSearchQuery,
|
||||||
selectCurrentParams,
|
selectCurrentParams,
|
||||||
(page, query, params) => {
|
(page: string, query: string, params: { uri: string }) => {
|
||||||
// only populate the wunderbar address if we are on the file/channel pages
|
// only populate the wunderbar address if we are on the file/channel pages
|
||||||
// or show the search query
|
// or show the search query
|
||||||
if (page === 'show') {
|
if (page === 'show') {
|
||||||
|
@ -40,13 +63,12 @@ export const selectWunderBarAddress = createSelector(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectSearchBarFocused = createSelector(selectState, state => state.focused);
|
export const selectSearchBarFocused: boolean = createSelector(selectState, state => state.focused);
|
||||||
// export const selectSear
|
|
||||||
|
|
||||||
export const selectSearchSuggestions = createSelector(
|
export const selectSearchSuggestions: Array<SearchSuggestion> = createSelector(
|
||||||
selectSearchValue,
|
selectSearchValue,
|
||||||
selectSuggestions,
|
selectSuggestions,
|
||||||
(query, suggestions) => {
|
(query: string, suggestions: { [string]: Array<string> }) => {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -117,3 +139,23 @@ export const selectSearchSuggestions = createSelector(
|
||||||
return searchSuggestions;
|
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
|
||||||
|
export const makeSelectQueryWithOptions = (
|
||||||
|
customQuery: ?string,
|
||||||
|
customSize: ?number,
|
||||||
|
customFrom: ?number,
|
||||||
|
isBackgroundSearch: boolean = false // If it's a background search, don't use the users settings
|
||||||
|
) =>
|
||||||
|
createSelector(selectSearchQuery, selectSearchOptions, (query, options) => {
|
||||||
|
const size = customSize || options[SEARCH_OPTIONS.RESULT_COUNT];
|
||||||
|
|
||||||
|
const queryString = getSearchQueryString(
|
||||||
|
customQuery || query,
|
||||||
|
{ ...options, size, from: customFrom },
|
||||||
|
!isBackgroundSearch
|
||||||
|
);
|
||||||
|
|
||||||
|
return queryString;
|
||||||
|
});
|
||||||
|
|
68
src/types/Search.js
Normal file
68
src/types/Search.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// @flow
|
||||||
|
import * as ACTIONS from 'constants/action_types';
|
||||||
|
|
||||||
|
export type SearchSuggestion = {
|
||||||
|
value: string,
|
||||||
|
shorthand: string,
|
||||||
|
type: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SearchState = {
|
||||||
|
isActive: boolean,
|
||||||
|
searchQuery: string,
|
||||||
|
options: SearchOptions,
|
||||||
|
suggestions: { [string]: Array<SearchSuggestion> },
|
||||||
|
urisByQuery: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SearchSuccess = {
|
||||||
|
type: ACTIONS.SEARCH_SUCCESS,
|
||||||
|
data: {
|
||||||
|
query: string,
|
||||||
|
uris: Array<string>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateSearchQuery = {
|
||||||
|
type: ACTIONS.UPDATE_SEARCH_QUERY,
|
||||||
|
data: {
|
||||||
|
query: string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateSearchSuggestions = {
|
||||||
|
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
|
||||||
|
data: {
|
||||||
|
query: string,
|
||||||
|
suggestions: Array<SearchSuggestion>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HistoryNavigate = {
|
||||||
|
type: ACTIONS.HISTORY_NAVIGATE,
|
||||||
|
data: {
|
||||||
|
url: string,
|
||||||
|
index?: number,
|
||||||
|
scrollY?: number,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateSearchOptions = {
|
||||||
|
type: ACTIONS.UPDATE_SEARCH_OPTIONS,
|
||||||
|
data: SearchOptions,
|
||||||
|
};
|
|
@ -1,4 +1,10 @@
|
||||||
export function parseQueryParams(queryString) {
|
// @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 {};
|
if (queryString === '') return {};
|
||||||
const parts = queryString
|
const parts = queryString
|
||||||
.split('?')
|
.split('?')
|
||||||
|
@ -14,7 +20,7 @@ export function parseQueryParams(queryString) {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toQueryString(params) {
|
export function toQueryString(params: { [string]: string | number }) {
|
||||||
if (!params) return '';
|
if (!params) return '';
|
||||||
|
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
@ -26,3 +32,39 @@ export function toQueryString(params) {
|
||||||
|
|
||||||
return parts.join('&');
|
return parts.join('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getSearchQueryString = (
|
||||||
|
query: string,
|
||||||
|
options: any = {},
|
||||||
|
includeUserOptions: boolean = false
|
||||||
|
) => {
|
||||||
|
const encodedQuery = encodeURIComponent(query);
|
||||||
|
const queryParams = [
|
||||||
|
`s=${encodedQuery}`,
|
||||||
|
`size=${options.size || DEFAULT_SEARCH_SIZE}`,
|
||||||
|
`from=${options.from || DEFAULT_SEARCH_RESULT_FROM}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (includeUserOptions) {
|
||||||
|
queryParams.push(`claimType=${options[SEARCH_OPTIONS.CLAIM_TYPE]}`);
|
||||||
|
|
||||||
|
// If they are only searching for channels, strip out the media info
|
||||||
|
if (options[SEARCH_OPTIONS.CLAIM_TYPE] !== 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),
|
||||||
|
''
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryParams.join('&');
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue