From 2b725cb31729234ba73117e2a74688b8bba26e7c Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Mon, 18 Feb 2019 11:24:18 -0500 Subject: [PATCH] feat: add support for search filters --- dist/bundle.js | 772 +++++++++++++++------------- src/constants/action_types.js | 1 + src/constants/search.js | 21 +- src/index.js | 16 +- src/redux/actions/search.js | 49 +- src/redux/reducers/notifications.js | 7 +- src/redux/reducers/search.js | 75 ++- src/redux/reducers/wallet.js | 4 +- src/redux/selectors/claims.js | 5 +- src/redux/selectors/search.js | 76 ++- src/types/Search.js | 68 +++ src/util/query_params.js | 46 +- 12 files changed, 708 insertions(+), 432 deletions(-) create mode 100644 src/types/Search.js diff --git a/dist/bundle.js b/dist/bundle.js index a1790a6..59b2c24 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -89,8 +89,8 @@ return /******/ (function(modules) { // webpackBootstrap Object.defineProperty(exports, "__esModule", { value: true }); -exports.selectTransactionListFilter = exports.selectWalletUnlockResult = exports.selectWalletUnlockSucceeded = exports.selectWalletUnlockPending = exports.selectWalletDecryptResult = exports.selectWalletDecryptSucceeded = exports.selectWalletDecryptPending = exports.selectWalletEncryptResult = exports.selectWalletEncryptSucceeded = exports.selectWalletEncryptPending = exports.selectWalletState = exports.selectWalletIsEncrypted = exports.selectBlocks = exports.selectDraftTransactionError = exports.selectDraftTransactionAddress = exports.selectDraftTransactionAmount = exports.selectDraftTransaction = exports.selectGettingNewAddress = exports.selectReceiveAddress = exports.selectIsSendingSupport = exports.selectIsFetchingTransactions = exports.selectHasTransactions = exports.selectRecentTransactions = exports.selectTransactionItems = exports.selectTransactionsById = exports.selectBalance = exports.makeSelectBlockDate = exports.selectSearchSuggestions = exports.selectSearchBarFocused = exports.selectWunderBarAddress = exports.selectSearchUrisByQuery = exports.selectIsSearching = exports.selectSearchValue = exports.selectSearchQuery = exports.makeSelectSearchUris = exports.selectSearchState = exports.selectActiveHistoryEntry = exports.selectHistoryStack = exports.selectHistoryIndex = exports.selectIsForwardDisabled = exports.selectIsBackDisabled = exports.selectPathAfterAuth = exports.selectPageTitle = exports.selectHeaderLinks = exports.selectCurrentParams = exports.selectCurrentPage = exports.selectCurrentPath = exports.makeSelectCurrentParam = exports.computePageFromPath = exports.selectFileListPublishedSort = exports.selectFileListDownloadedSort = exports.selectSearchDownloadUris = exports.selectTotalDownloadProgress = exports.selectDownloadingFileInfos = exports.selectFileInfosDownloaded = exports.selectUrisLoading = exports.selectDownloadingByOutpoint = exports.selectIsFetchingFileListDownloadedOrPublished = exports.selectIsFetchingFileList = exports.selectFileInfosByOutpoint = exports.makeSelectLoadingForUri = exports.makeSelectDownloadingForUri = exports.makeSelectFileInfoForUri = exports.selectFetchingCostInfo = exports.selectCostForCurrentPageUri = exports.selectAllCostInfoByUri = exports.makeSelectCostInfoForUri = exports.makeSelectFetchingCostInfoForUri = exports.selectChannelClaimCounts = exports.selectPlayingUri = exports.selectFetchingTrendingUris = exports.selectTrendingUris = exports.selectFetchingFeaturedUris = exports.selectFeaturedUris = exports.selectResolvingUris = exports.selectMyChannelClaims = exports.selectFetchingMyChannels = exports.selectMyClaimsOutpoints = exports.selectAllMyClaimsByOutpoint = exports.selectMyClaimsWithoutChannels = exports.selectMyClaims = exports.selectPendingClaims = exports.selectIsFetchingClaimListMine = undefined; -exports.selectAllFetchingChannelClaims = exports.selectMyActiveClaims = exports.selectAbandoningIds = exports.selectMyClaimsRaw = exports.selectAllClaimsByChannel = exports.selectClaimsByUri = exports.selectClaimsById = exports.selectPendingById = exports.makeSelectPendingByUri = exports.makeSelectClaimIsPending = exports.makeSelectChannelForClaimUri = exports.makeSelectFirstRecommendedFileForUri = exports.makeSelectRecommendedContentForUri = exports.makeSelectNsfwCountForChannel = exports.makeSelectNsfwCountFromUris = exports.makeSelectTotalPagesForChannel = exports.makeSelectTotalItemsForChannel = exports.makeSelectIsUriResolving = exports.makeSelectContentTypeForUri = exports.makeSelectTitleForUri = exports.makeSelectMetadataForUri = exports.makeSelectClaimsInChannelForPage = exports.makeSelectClaimsInChannelForCurrentPage = exports.makeSelectFetchingChannelClaims = exports.makeSelectClaimIsMine = exports.makeSelectClaimForUri = exports.selectError = exports.selectToast = exports.selectBlackListedOutpoints = exports.blacklistReducer = exports.walletReducer = exports.searchReducer = exports.notificationsReducer = exports.fileInfoReducer = exports.costInfoReducer = exports.claimsReducer = exports.creditsToString = exports.formatFullPrice = exports.formatCredits = exports.toQueryString = exports.parseQueryParams = exports.batchActions = exports.doUpdateBlockHeight = exports.doSetTransactionListFilter = exports.doWalletStatus = exports.doWalletUnlock = exports.doWalletDecrypt = exports.doWalletEncrypt = exports.doSendTip = exports.doSetDraftTransactionAddress = exports.doSetDraftTransactionAmount = exports.doSendDraftTransaction = exports.doCheckAddressIsMine = exports.doGetNewAddress = exports.doFetchBlock = exports.doFetchTransactions = exports.doBalanceSubscribe = exports.doUpdateBalance = exports.doBlackListedOutpointsSubscribe = exports.setSearchApi = exports.doBlurSearchInput = exports.doFocusSearchInput = exports.doUpdateSearchQuery = exports.doSearch = exports.doSetFileListSort = exports.doFetchFileInfosAndPublishedClaims = exports.doFileList = exports.doFetchFileInfo = exports.doFetchCostInfoForUri = exports.doFetchTrendingUris = exports.doFetchFeaturedUris = exports.doResolveUri = exports.doResolveUris = exports.doAbandonClaim = exports.doFetchClaimListMine = exports.doFetchClaimCountByChannel = exports.doFetchClaimsByChannel = exports.doDismissError = exports.doError = exports.doDismissToast = exports.doToast = exports.convertToShareLink = exports.isNameValid = exports.isURIClaimable = exports.isURIValid = exports.normalizeURI = exports.buildURI = exports.parseURI = exports.regexAddress = exports.regexInvalidURI = exports.Lbryapi = exports.Lbry = exports.PAGES = exports.SORT_OPTIONS = exports.TRANSACTIONS = exports.SETTINGS = exports.SEARCH_TYPES = exports.THUMBNAIL_STATUSES = exports.ACTIONS = exports.Toast = undefined; +exports.selectTransactionListFilter = exports.selectWalletUnlockResult = exports.selectWalletUnlockSucceeded = exports.selectWalletUnlockPending = exports.selectWalletDecryptResult = exports.selectWalletDecryptSucceeded = exports.selectWalletDecryptPending = exports.selectWalletEncryptResult = exports.selectWalletEncryptSucceeded = exports.selectWalletEncryptPending = exports.selectWalletState = exports.selectWalletIsEncrypted = exports.selectBlocks = exports.selectDraftTransactionError = exports.selectDraftTransactionAddress = exports.selectDraftTransactionAmount = exports.selectDraftTransaction = exports.selectGettingNewAddress = exports.selectReceiveAddress = exports.selectIsSendingSupport = exports.selectIsFetchingTransactions = exports.selectHasTransactions = exports.selectRecentTransactions = exports.selectTransactionItems = exports.selectTransactionsById = exports.selectBalance = exports.makeSelectBlockDate = exports.makeSelectQueryWithOptions = exports.selectSearchSuggestions = exports.selectSearchBarFocused = exports.selectWunderBarAddress = exports.selectSearchUrisByQuery = exports.selectIsSearching = exports.selectSearchOptions = exports.selectSearchValue = exports.selectSearchQuery = exports.makeSelectSearchUris = exports.selectSearchState = exports.selectActiveHistoryEntry = exports.selectHistoryStack = exports.selectHistoryIndex = exports.selectIsForwardDisabled = exports.selectIsBackDisabled = exports.selectPathAfterAuth = exports.selectPageTitle = exports.selectHeaderLinks = exports.selectCurrentParams = exports.selectCurrentPage = exports.selectCurrentPath = exports.makeSelectCurrentParam = exports.computePageFromPath = exports.selectFileListPublishedSort = exports.selectFileListDownloadedSort = exports.selectSearchDownloadUris = exports.selectTotalDownloadProgress = exports.selectDownloadingFileInfos = exports.selectFileInfosDownloaded = exports.selectUrisLoading = exports.selectDownloadingByOutpoint = exports.selectIsFetchingFileListDownloadedOrPublished = exports.selectIsFetchingFileList = exports.selectFileInfosByOutpoint = exports.makeSelectLoadingForUri = exports.makeSelectDownloadingForUri = exports.makeSelectFileInfoForUri = exports.selectFetchingCostInfo = exports.selectCostForCurrentPageUri = exports.selectAllCostInfoByUri = exports.makeSelectCostInfoForUri = exports.makeSelectFetchingCostInfoForUri = exports.selectChannelClaimCounts = exports.selectPlayingUri = exports.selectFetchingTrendingUris = exports.selectTrendingUris = exports.selectFetchingFeaturedUris = exports.selectFeaturedUris = exports.selectResolvingUris = exports.selectMyChannelClaims = exports.selectFetchingMyChannels = exports.selectMyClaimsOutpoints = exports.selectAllMyClaimsByOutpoint = exports.selectMyClaimsWithoutChannels = exports.selectMyClaims = exports.selectPendingClaims = exports.selectIsFetchingClaimListMine = exports.selectAllFetchingChannelClaims = exports.selectMyActiveClaims = undefined; +exports.selectAbandoningIds = exports.selectMyClaimsRaw = exports.selectAllClaimsByChannel = exports.selectClaimsByUri = exports.selectClaimsById = exports.selectPendingById = exports.makeSelectPendingByUri = exports.makeSelectClaimIsPending = exports.makeSelectChannelForClaimUri = exports.makeSelectFirstRecommendedFileForUri = exports.makeSelectRecommendedContentForUri = exports.makeSelectNsfwCountForChannel = exports.makeSelectNsfwCountFromUris = exports.makeSelectTotalPagesForChannel = exports.makeSelectTotalItemsForChannel = exports.makeSelectIsUriResolving = exports.makeSelectContentTypeForUri = exports.makeSelectTitleForUri = exports.makeSelectMetadataForUri = exports.makeSelectClaimsInChannelForPage = exports.makeSelectClaimsInChannelForCurrentPage = exports.makeSelectFetchingChannelClaims = exports.makeSelectClaimIsMine = exports.makeSelectClaimForUri = exports.selectError = exports.selectToast = exports.selectBlackListedOutpoints = exports.blacklistReducer = exports.walletReducer = exports.searchReducer = exports.notificationsReducer = exports.fileInfoReducer = exports.costInfoReducer = exports.claimsReducer = exports.creditsToString = exports.formatFullPrice = exports.formatCredits = exports.toQueryString = exports.parseQueryParams = exports.batchActions = exports.doUpdateBlockHeight = exports.doSetTransactionListFilter = exports.doWalletStatus = exports.doWalletUnlock = exports.doWalletDecrypt = exports.doWalletEncrypt = exports.doSendTip = exports.doSetDraftTransactionAddress = exports.doSetDraftTransactionAmount = exports.doSendDraftTransaction = exports.doCheckAddressIsMine = exports.doGetNewAddress = exports.doFetchBlock = exports.doFetchTransactions = exports.doBalanceSubscribe = exports.doUpdateBalance = exports.doBlackListedOutpointsSubscribe = exports.doUpdateSearchOptions = exports.setSearchApi = exports.doBlurSearchInput = exports.doFocusSearchInput = exports.doUpdateSearchQuery = exports.doSearch = exports.doSetFileListSort = exports.doFetchFileInfosAndPublishedClaims = exports.doFileList = exports.doFetchFileInfo = exports.doFetchCostInfoForUri = exports.doFetchTrendingUris = exports.doFetchFeaturedUris = exports.doResolveUri = exports.doResolveUris = exports.doAbandonClaim = exports.doFetchClaimListMine = exports.doFetchClaimCountByChannel = exports.doFetchClaimsByChannel = exports.doDismissError = exports.doError = exports.doDismissToast = exports.doToast = exports.convertToShareLink = exports.isNameValid = exports.isURIClaimable = exports.isURIValid = exports.normalizeURI = exports.buildURI = exports.parseURI = exports.regexAddress = exports.regexInvalidURI = exports.Lbryapi = exports.Lbry = exports.PAGES = exports.SORT_OPTIONS = exports.TRANSACTIONS = exports.SETTINGS = exports.SEARCH_OPTIONS = exports.SEARCH_TYPES = exports.THUMBNAIL_STATUSES = exports.ACTIONS = exports.Toast = undefined; var _Notification = __webpack_require__(1); @@ -304,6 +304,12 @@ Object.defineProperty(exports, 'setSearchApi', { return _search.setSearchApi; } }); +Object.defineProperty(exports, 'doUpdateSearchOptions', { + enumerable: true, + get: function get() { + return _search.doUpdateSearchOptions; + } +}); var _blacklist = __webpack_require__(35); @@ -995,7 +1001,7 @@ Object.defineProperty(exports, 'selectActiveHistoryEntry', { } }); -var _search3 = __webpack_require__(21); +var _search3 = __webpack_require__(22); Object.defineProperty(exports, 'makeSelectSearchUris', { enumerable: true, @@ -1015,6 +1021,12 @@ Object.defineProperty(exports, 'selectSearchValue', { return _search3.selectSearchValue; } }); +Object.defineProperty(exports, 'selectSearchOptions', { + enumerable: true, + get: function get() { + return _search3.selectSearchOptions; + } +}); Object.defineProperty(exports, 'selectIsSearching', { enumerable: true, get: function get() { @@ -1045,6 +1057,12 @@ Object.defineProperty(exports, 'selectSearchSuggestions', { return _search3.selectSearchSuggestions; } }); +Object.defineProperty(exports, 'makeSelectQueryWithOptions', { + enumerable: true, + get: function get() { + return _search3.makeSelectQueryWithOptions; + } +}); var _wallet3 = __webpack_require__(26); @@ -1219,10 +1237,6 @@ var _thumbnail_upload_statuses = __webpack_require__(49); var THUMBNAIL_STATUSES = _interopRequireWildcard(_thumbnail_upload_statuses); -var _search4 = __webpack_require__(22); - -var SEARCH_TYPES = _interopRequireWildcard(_search4); - var _settings = __webpack_require__(50); var SETTINGS = _interopRequireWildcard(_settings); @@ -1239,6 +1253,8 @@ var _pages = __webpack_require__(40); var PAGES = _interopRequireWildcard(_pages); +var _search4 = __webpack_require__(21); + var _lbry = __webpack_require__(9); var _lbry2 = _interopRequireDefault(_lbry); @@ -1254,7 +1270,8 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; // constants exports.ACTIONS = ACTIONS; exports.THUMBNAIL_STATUSES = THUMBNAIL_STATUSES; -exports.SEARCH_TYPES = SEARCH_TYPES; +exports.SEARCH_TYPES = _search4.SEARCH_TYPES; +exports.SEARCH_OPTIONS = _search4.SEARCH_OPTIONS; exports.SETTINGS = SETTINGS; exports.TRANSACTIONS = TRANSACTIONS; exports.SORT_OPTIONS = SORT_OPTIONS; @@ -1279,96 +1296,96 @@ var ACTIONS = _interopRequireWildcard(_action_types); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } -/* - Toasts: - - First-in, first-out queue - - Simple messages that are shown in response to user interactions - - Never saved - - If they are the result of errors, use the isError flag when creating - - For errors that should interrupt user behavior, use Error +/* + Toasts: + - First-in, first-out queue + - Simple messages that are shown in response to user interactions + - Never saved + - If they are the result of errors, use the isError flag when creating + - For errors that should interrupt user behavior, use Error */ -/*:: export type ToastParams = { - message: string, - title?: string, - linkText?: string, - linkTarget?: string, - isError?: boolean, +/*:: export type ToastParams = { + message: string, + title?: string, + linkText?: string, + linkTarget?: string, + isError?: boolean, };*/ // @flow -/*:: export type Toast = { - id: string, - params: ToastParams, +/*:: export type Toast = { + id: string, + params: ToastParams, };*/ -/* - Notifications: - - List of notifications based on user interactions/app notifications - - Always saved, but can be manually deleted - - Can happen in the background, or because of user interaction (ex: publish confirmed) +/* + Notifications: + - List of notifications based on user interactions/app notifications + - Always saved, but can be manually deleted + - Can happen in the background, or because of user interaction (ex: publish confirmed) */ -/*:: export type DoToast = { - type: ACTIONS.CREATE_TOAST, - data: Toast, +/*:: export type DoToast = { + type: ACTIONS.CREATE_TOAST, + data: Toast, };*/ -/*:: export type Notification = { - id: string, // Unique id - dateCreated: number, - isRead: boolean, // Used to display "new" notifications that a user hasn't seen yet - source?: string, // The type/area an notification is from. Used for sorting (ex: publishes, transactions) - // We may want to use priority/isDismissed in the future to specify how urgent a notification is - // and if the user should see it immediately - // isDissmied: boolean, - // priority?: number +/*:: export type Notification = { + id: string, // Unique id + dateCreated: number, + isRead: boolean, // Used to display "new" notifications that a user hasn't seen yet + source?: string, // The type/area an notification is from. Used for sorting (ex: publishes, transactions) + // We may want to use priority/isDismissed in the future to specify how urgent a notification is + // and if the user should see it immediately + // isDissmied: boolean, + // priority?: number };*/ -/*:: export type DoNotification = { - type: ACTIONS.CREATE_NOTIFICATION, - data: Notification, +/*:: export type DoNotification = { + type: ACTIONS.CREATE_NOTIFICATION, + data: Notification, };*/ -/*:: export type DoEditNotification = { - type: ACTIONS.EDIT_NOTIFICATION, - data: { - id: string, - isRead: boolean, - // In the future we can add `isDismissed` if we decide to show notifications as they come in - // Similar to Facebook's notifications in the corner of the screen - // isDismissed: boolean, - }, +/*:: export type DoEditNotification = { + type: ACTIONS.EDIT_NOTIFICATION, + data: { + id: string, + isRead: boolean, + // In the future we can add `isDismissed` if we decide to show notifications as they come in + // Similar to Facebook's notifications in the corner of the screen + // isDismissed: boolean, + }, };*/ -/* - Errors: - - First-in, first-out queue - - Errors that should interupt user behavior - - For errors that can be shown without interrupting a user, use Toast with the isError flag +/* + Errors: + - First-in, first-out queue + - Errors that should interupt user behavior + - For errors that can be shown without interrupting a user, use Toast with the isError flag */ -/*:: export type DoDeleteNotification = { - type: ACTIONS.DELETE_NOTIFICATION, - data: { - id: string, // The id to delete - }, +/*:: export type DoDeleteNotification = { + type: ACTIONS.DELETE_NOTIFICATION, + data: { + id: string, // The id to delete + }, };*/ -/*:: export type Error = { - title: string, - text: string, +/*:: export type Error = { + title: string, + text: string, };*/ -/*:: export type DoError = { - type: ACTIONS.CREATE_ERROR, - data: Error, +/*:: export type DoError = { + type: ACTIONS.CREATE_ERROR, + data: Error, };*/ -/* - NotificationState +/* + NotificationState */ -/*:: export type DoDismissError = { - type: ACTIONS.DISMISS_ERROR, +/*:: export type DoDismissError = { + type: ACTIONS.DISMISS_ERROR, };*/ -/*:: export type NotificationState = { - notifications: Array, - errors: Array, - toasts: Array, +/*:: export type NotificationState = { + notifications: Array, + errors: Array, + toasts: Array, };*/ /***/ }), @@ -1500,6 +1517,7 @@ var SEARCH_START = exports.SEARCH_START = 'SEARCH_START'; var SEARCH_SUCCESS = exports.SEARCH_SUCCESS = 'SEARCH_SUCCESS'; var SEARCH_FAIL = exports.SEARCH_FAIL = 'SEARCH_FAIL'; var UPDATE_SEARCH_QUERY = exports.UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY'; +var UPDATE_SEARCH_OPTIONS = exports.UPDATE_SEARCH_OPTIONS = 'UPDATE_SEARCH_OPTIONS'; var UPDATE_SEARCH_SUGGESTIONS = exports.UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS'; var SEARCH_FOCUS = exports.SEARCH_FOCUS = 'SEARCH_FOCUS'; var SEARCH_BLUR = exports.SEARCH_BLUR = 'SEARCH_BLUR'; @@ -1630,27 +1648,27 @@ var claimIdMaxLength = 40; var regexInvalidURI = exports.regexInvalidURI = /[^A-Za-z0-9-]/g; var regexAddress = exports.regexAddress = /^b(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/; -/** - * Parses a LBRY name into its component parts. Throws errors with user-friendly - * messages for invalid names. - * - * N.B. that "name" indicates the value in the name position of the URI. For - * claims for channel content, this will actually be the channel name, and - * the content name is in the path (e.g. lbry://@channel/content) - * - * In most situations, you'll want to use the contentName and channelName keys - * and ignore the name key. - * - * Returns a dictionary with keys: - * - name (string): The value in the "name" position in the URI. Note that this - * could be either content name or channel name; see above. - * - path (string, if persent) - * - claimSequence (int, if present) - * - bidPosition (int, if present) - * - claimId (string, if present) - * - isChannel (boolean) - * - contentName (string): For anon claims, the name; for channel claims, the path - * - channelName (string, if present): Channel name without @ +/** + * Parses a LBRY name into its component parts. Throws errors with user-friendly + * messages for invalid names. + * + * N.B. that "name" indicates the value in the name position of the URI. For + * claims for channel content, this will actually be the channel name, and + * the content name is in the path (e.g. lbry://@channel/content) + * + * In most situations, you'll want to use the contentName and channelName keys + * and ignore the name key. + * + * Returns a dictionary with keys: + * - name (string): The value in the "name" position in the URI. Note that this + * could be either content name or channel name; see above. + * - path (string, if persent) + * - claimSequence (int, if present) + * - bidPosition (int, if present) + * - claimId (string, if present) + * - isChannel (boolean) + * - contentName (string): For anon claims, the name; for channel claims, the path + * - channelName (string, if present): Channel name without @ */ function parseURI(URI) { var requireProto = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; @@ -1758,10 +1776,10 @@ function parseURI(URI) { }, contentName ? { contentName: contentName } : {}, channelName ? { channelName: channelName } : {}, claimSequence ? { claimSequence: parseInt(claimSequence, 10) } : {}, bidPosition ? { bidPosition: parseInt(bidPosition, 10) } : {}, claimId ? { claimId: claimId } : {}, path ? { path: path } : {}); } -/** - * Takes an object in the same format returned by parse() and builds a URI. - * - * The channelName key will accept names with or without the @ prefix. +/** + * Takes an object in the same format returned by parse() and builds a URI. + * + * The channelName key will accept names with or without the @ prefix. */ function buildURI(URIObj) { var includeProto = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; @@ -1956,11 +1974,9 @@ module.exports = v4; // and inconsistent support for the `crypto` API. We do the best we can via // feature-detection -// getRandomValues needs to be invoked in a context where "this" is a Crypto -// implementation. Also, find the complete implementation of crypto on IE11. -var getRandomValues = (typeof(crypto) != 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto)) || - (typeof(msCrypto) != 'undefined' && typeof window.msCrypto.getRandomValues == 'function' && msCrypto.getRandomValues.bind(msCrypto)); - +// getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. +var getRandomValues = (typeof(crypto) != 'undefined' && crypto.getRandomValues.bind(crypto)) || + (typeof(msCrypto) != 'undefined' && msCrypto.getRandomValues.bind(msCrypto)); if (getRandomValues) { // WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef @@ -2003,15 +2019,14 @@ for (var i = 0; i < 256; ++i) { function bytesToUuid(buf, offset) { var i = offset || 0; var bth = byteToHex; - // join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4 - return ([bth[buf[i++]], bth[buf[i++]], - bth[buf[i++]], bth[buf[i++]], '-', - bth[buf[i++]], bth[buf[i++]], '-', - bth[buf[i++]], bth[buf[i++]], '-', - bth[buf[i++]], bth[buf[i++]], '-', - bth[buf[i++]], bth[buf[i++]], - bth[buf[i++]], bth[buf[i++]], - bth[buf[i++]], bth[buf[i++]]]).join(''); + return bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]]; } module.exports = bytesToUuid; @@ -3289,12 +3304,14 @@ var _lbryURI = __webpack_require__(3); var _navigation = __webpack_require__(18); -var _search = __webpack_require__(21); +var _search = __webpack_require__(22); var _reselect = __webpack_require__(19); var _claim = __webpack_require__(23); +var _query_params = __webpack_require__(20); + function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } var selectState = function selectState(state) { @@ -3599,7 +3616,9 @@ var makeSelectRecommendedContentForUri = exports.makeSelectRecommendedContentFor var title = claim.value.stream.metadata.title; - var searchUris = searchUrisByQuery[title.replace(/\//, ' ')]; + var searchQuery = (0, _query_params.getSearchQueryString)(title.replace(/\//, ' ')); + + var searchUris = searchUrisByQuery[searchQuery]; if (searchUris) { searchUris = searchUris.filter(function (searchUri) { return searchUri !== currentUri; @@ -3857,12 +3876,20 @@ function createStructuredSelector(selectors) { Object.defineProperty(exports, "__esModule", { value: true }); +exports.getSearchQueryString = undefined; + +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); // @flow -var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); exports.parseQueryParams = parseQueryParams; exports.toQueryString = toQueryString; -function parseQueryParams(queryString) { + +var _search = __webpack_require__(21); + +var DEFAULT_SEARCH_RESULT_FROM = 0; +var DEFAULT_SEARCH_SIZE = 20; + +function parseQueryParams(queryString /*: string*/) { if (queryString === '') return {}; var parts = queryString.split('?').pop().split('&').map(function (p) { return p.split('='); @@ -3879,7 +3906,7 @@ function parseQueryParams(queryString) { return params; } -function toQueryString(params) { +function toQueryString(params /*: { [string]: string | number }*/) { if (!params) return ''; var parts = []; @@ -3892,6 +3919,27 @@ function toQueryString(params) { return parts.join('&'); } +var getSearchQueryString = exports.getSearchQueryString = function getSearchQueryString(query /*: string*/) { + var options /*: any*/ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var includeUserOptions /*: boolean*/ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + var encodedQuery = encodeURIComponent(query); + var queryParams = ['s=' + encodedQuery, 'size=' + (options.size || DEFAULT_SEARCH_SIZE), 'from=' + (options.from || DEFAULT_SEARCH_RESULT_FROM)]; + + if (includeUserOptions) { + queryParams.push('claimType=' + options[_search.SEARCH_OPTIONS.CLAIM_TYPE]); + + // If they are only searching for channels, strip out the media info + if (options[_search.SEARCH_OPTIONS.CLAIM_TYPE] !== _search.SEARCH_OPTIONS.INCLUDE_CHANNELS) { + queryParams.push('mediaType=' + [_search.SEARCH_OPTIONS.MEDIA_FILE, _search.SEARCH_OPTIONS.MEDIA_AUDIO, _search.SEARCH_OPTIONS.MEDIA_VIDEO, _search.SEARCH_OPTIONS.MEDIA_TEXT, _search.SEARCH_OPTIONS.MEDIA_IMAGE, _search.SEARCH_OPTIONS.MEDIA_APPLICATION].reduce(function (acc, currentOption) { + return options[currentOption] ? '' + acc + currentOption + ',' : acc; + }, '')); + } + } + + return queryParams.join('&'); +}; + /***/ }), /* 21 */ /***/ (function(module, exports, __webpack_require__) { @@ -3902,140 +3950,25 @@ function toQueryString(params) { Object.defineProperty(exports, "__esModule", { value: true }); -exports.selectSearchSuggestions = exports.selectSearchBarFocused = exports.selectWunderBarAddress = exports.makeSelectSearchUris = exports.selectSearchUrisByQuery = exports.selectIsSearching = exports.selectSearchQuery = exports.selectSuggestions = exports.selectSearchValue = exports.selectState = undefined; - -var _search = __webpack_require__(22); - -var SEARCH_TYPES = _interopRequireWildcard(_search); - -var _lbryURI = __webpack_require__(3); - -var _navigation = __webpack_require__(18); - -var _reselect = __webpack_require__(19); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -var selectState = exports.selectState = function selectState(state) { - return state.search || {}; +var SEARCH_TYPES = exports.SEARCH_TYPES = { + FILE: 'file', + CHANNEL: 'channel', + SEARCH: 'search' }; -var selectSearchValue = exports.selectSearchValue = (0, _reselect.createSelector)(selectState, function (state) { - return state.searchQuery; -}); - -var selectSuggestions = exports.selectSuggestions = (0, _reselect.createSelector)(selectState, function (state) { - return state.suggestions; -}); - -var selectSearchQuery = exports.selectSearchQuery = (0, _reselect.createSelector)(_navigation.selectCurrentPage, _navigation.selectCurrentParams, function (page, params) { - return page === 'search' ? params && params.query : null; -}); - -var selectIsSearching = exports.selectIsSearching = (0, _reselect.createSelector)(selectState, function (state) { - return state.searching; -}); - -var selectSearchUrisByQuery = exports.selectSearchUrisByQuery = (0, _reselect.createSelector)(selectState, function (state) { - return state.urisByQuery; -}); - -var makeSelectSearchUris = exports.makeSelectSearchUris = function makeSelectSearchUris(query) { - return ( - // replace statement below is kind of ugly, and repeated in doSearch action - (0, _reselect.createSelector)(selectSearchUrisByQuery, function (byQuery) { - return byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]; - }) - ); +var SEARCH_OPTIONS = exports.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' }; -var selectWunderBarAddress = exports.selectWunderBarAddress = (0, _reselect.createSelector)(_navigation.selectCurrentPage, selectSearchQuery, _navigation.selectCurrentParams, function (page, query, params) { - // only populate the wunderbar address if we are on the file/channel pages - // or show the search query - if (page === 'show') { - return params.uri; - } - return query; -}); - -var selectSearchBarFocused = exports.selectSearchBarFocused = (0, _reselect.createSelector)(selectState, function (state) { - return state.focused; -}); -// export const selectSear - -var selectSearchSuggestions = exports.selectSearchSuggestions = (0, _reselect.createSelector)(selectSearchValue, selectSuggestions, function (query, suggestions) { - if (!query) { - return []; - } - - var queryIsPrefix = query === 'lbry:' || query === 'lbry:/' || query === 'lbry://'; - - if (query.startsWith('lbry://') && query !== 'lbry://') { - // If it starts with a prefix, don't show any autocomplete results - // They are probably typing/pasting in a lbry uri - return [{ - value: query, - type: SEARCH_TYPES.FILE - }]; - } else if (queryIsPrefix) { - // If it is a prefix, wait until something else comes to figure out what to do - return []; - } - - var searchSuggestions = []; - try { - var uri = (0, _lbryURI.normalizeURI)(query); - - var _parseURI = (0, _lbryURI.parseURI)(uri), - claimName = _parseURI.claimName, - isChannel = _parseURI.isChannel; - - searchSuggestions.push({ - value: claimName, - type: SEARCH_TYPES.SEARCH - }, { - value: uri, - shorthand: isChannel ? claimName.slice(1) : claimName, - type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE - }); - } catch (e) { - searchSuggestions.push({ - value: query, - type: SEARCH_TYPES.SEARCH - }); - } - - var apiSuggestions = suggestions[query] || []; - if (apiSuggestions.length) { - searchSuggestions = searchSuggestions.concat(apiSuggestions.filter(function (suggestion) { - return suggestion !== query; - }).map(function (suggestion) { - // determine if it's a channel - try { - var _uri = (0, _lbryURI.normalizeURI)(suggestion); - - var _parseURI2 = (0, _lbryURI.parseURI)(_uri), - _claimName = _parseURI2.claimName, - _isChannel = _parseURI2.isChannel; - - return { - value: _uri, - shorthand: _isChannel ? _claimName.slice(1) : _claimName, - type: _isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE - }; - } catch (e) { - // search result includes some character that isn't valid in claim names - return { - value: suggestion, - type: SEARCH_TYPES.SEARCH - }; - } - })); - } - - return searchSuggestions; -}); - /***/ }), /* 22 */ /***/ (function(module, exports, __webpack_require__) { @@ -4046,9 +3979,162 @@ var selectSearchSuggestions = exports.selectSearchSuggestions = (0, _reselect.cr Object.defineProperty(exports, "__esModule", { value: true }); -var FILE = exports.FILE = 'file'; -var CHANNEL = exports.CHANNEL = 'channel'; -var SEARCH = exports.SEARCH = 'search'; +exports.makeSelectQueryWithOptions = exports.selectSearchSuggestions = exports.selectSearchBarFocused = exports.selectWunderBarAddress = exports.makeSelectSearchUris = exports.selectSearchUrisByQuery = exports.selectIsSearching = exports.selectSearchQuery = exports.selectSuggestions = exports.selectSearchOptions = exports.selectSearchValue = exports.selectState = undefined; + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var _search = __webpack_require__(21); + +var _query_params = __webpack_require__(20); + +var _lbryURI = __webpack_require__(3); + +var _navigation = __webpack_require__(18); + +var _reselect = __webpack_require__(19); + +// @flow +/*:: import type { SearchState, SearchOptions, SearchSuggestion } from 'types/Search';*/ +/*:: type State = { search: SearchState };*/ +var selectState = exports.selectState = function selectState(state /*: State*/) /*: SearchState*/ { + return state.search; +}; + +var selectSearchValue /*: (state: State) => string*/ = exports.selectSearchValue = (0, _reselect.createSelector)(selectState, function (state) { + return state.searchQuery; +}); + +var selectSearchOptions /*: (state: State) => SearchOptions*/ = exports.selectSearchOptions = (0, _reselect.createSelector)(selectState, function (state) { + return state.options; +}); + +var selectSuggestions /*: ( + state: State + ) => { [string]: Array }*/ = exports.selectSuggestions = (0, _reselect.createSelector)(selectState, function (state) { + return state.suggestions; +}); + +var selectSearchQuery /*: (state: State) => ?string*/ = exports.selectSearchQuery = (0, _reselect.createSelector)(_navigation.selectCurrentPage, _navigation.selectCurrentParams, function (page /*: string*/, params /*: ?{ query: string }*/) { + return page === 'search' ? params && params.query : null; +}); + +var selectIsSearching /*: (state: State) => boolean*/ = exports.selectIsSearching = (0, _reselect.createSelector)(selectState, function (state) { + return state.searching; +}); + +var selectSearchUrisByQuery /*: ( + state: State + ) => { [string]: Array }*/ = exports.selectSearchUrisByQuery = (0, _reselect.createSelector)(selectState, function (state) { + return state.urisByQuery; +}); + +var makeSelectSearchUris = exports.makeSelectSearchUris = function makeSelectSearchUris(query +// replace statement below is kind of ugly, and repeated in doSearch action +/*: string*/) /*: ((state: State) => Array)*/ { + return (0, _reselect.createSelector)(selectSearchUrisByQuery, function (byQuery) { + return byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]; + }); +}; + +var selectWunderBarAddress = exports.selectWunderBarAddress = (0, _reselect.createSelector)(_navigation.selectCurrentPage, selectSearchQuery, _navigation.selectCurrentParams, function (page /*: string*/, query /*: string*/, params /*: { uri: string }*/) { + // only populate the wunderbar address if we are on the file/channel pages + // or show the search query + if (page === 'show') { + return params.uri; + } + return query; +}); + +var selectSearchBarFocused /*: boolean*/ = exports.selectSearchBarFocused = (0, _reselect.createSelector)(selectState, function (state) { + return state.focused; +}); + +var selectSearchSuggestions /*: Array*/ = exports.selectSearchSuggestions = (0, _reselect.createSelector)(selectSearchValue, selectSuggestions, function (query /*: string*/, suggestions /*: { [string]: Array }*/) { + if (!query) { + return []; + } + + var queryIsPrefix = query === 'lbry:' || query === 'lbry:/' || query === 'lbry://'; + + if (query.startsWith('lbry://') && query !== 'lbry://') { + // If it starts with a prefix, don't show any autocomplete results + // They are probably typing/pasting in a lbry uri + return [{ + value: query, + type: _search.SEARCH_TYPES.FILE + }]; + } else if (queryIsPrefix) { + // If it is a prefix, wait until something else comes to figure out what to do + return []; + } + + var searchSuggestions = []; + try { + var _uri = (0, _lbryURI.normalizeURI)(query); + + var _parseURI = (0, _lbryURI.parseURI)(_uri), + claimName = _parseURI.claimName, + isChannel = _parseURI.isChannel; + + searchSuggestions.push({ + value: claimName, + type: _search.SEARCH_TYPES.SEARCH + }, { + value: _uri, + shorthand: isChannel ? claimName.slice(1) : claimName, + type: isChannel ? _search.SEARCH_TYPES.CHANNEL : _search.SEARCH_TYPES.FILE + }); + } catch (e) { + searchSuggestions.push({ + value: query, + type: _search.SEARCH_TYPES.SEARCH + }); + } + + var apiSuggestions = suggestions[query] || []; + if (apiSuggestions.length) { + searchSuggestions = searchSuggestions.concat(apiSuggestions.filter(function (suggestion) { + return suggestion !== query; + }).map(function (suggestion) { + // determine if it's a channel + try { + var _uri2 = (0, _lbryURI.normalizeURI)(suggestion); + + var _parseURI2 = (0, _lbryURI.parseURI)(_uri2), + _claimName = _parseURI2.claimName, + _isChannel = _parseURI2.isChannel; + + return { + value: _uri2, + shorthand: _isChannel ? _claimName.slice(1) : _claimName, + type: _isChannel ? _search.SEARCH_TYPES.CHANNEL : _search.SEARCH_TYPES.FILE + }; + } catch (e) { + // search result includes some character that isn't valid in claim names + return { + value: suggestion, + type: _search.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 +var makeSelectQueryWithOptions = exports.makeSelectQueryWithOptions = function makeSelectQueryWithOptions(customQuery /*: ?string*/, customSize /*: ?number*/, customFrom // If it's a background search, don't use the users settings +/*: ?number*/) { + var isBackgroundSearch /*: boolean*/ = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; + return (0, _reselect.createSelector)(selectSearchQuery, selectSearchOptions, function (query, options) { + var size = customSize || options[_search.SEARCH_OPTIONS.RESULT_COUNT]; + + var queryString = (0, _query_params.getSearchQueryString)(customQuery || query, _extends({}, options, { size: size, from: customFrom }), !isBackgroundSearch); + + return queryString; + }); +}; /***/ }), /* 23 */ @@ -5173,7 +5259,7 @@ var selectFileListDownloadedSort = exports.selectFileListDownloadedSort = (0, _r Object.defineProperty(exports, "__esModule", { value: true }); -exports.doBlurSearchInput = exports.doFocusSearchInput = exports.doUpdateSearchQuery = exports.getSearchSuggestions = exports.doSearch = exports.setSearchApi = undefined; +exports.doUpdateSearchOptions = exports.doBlurSearchInput = exports.doFocusSearchInput = exports.doUpdateSearchQuery = exports.getSearchSuggestions = exports.doSearch = exports.setSearchApi = undefined; var _action_types = __webpack_require__(2); @@ -5183,7 +5269,7 @@ var _lbryURI = __webpack_require__(3); var _claims = __webpack_require__(8); -var _search = __webpack_require__(21); +var _search = __webpack_require__(22); var _batchActions = __webpack_require__(24); @@ -5199,27 +5285,25 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } -var DEFAULTSEARCHRESULTSIZE = 10; // @flow +// @flow +/*:: import type { SearchState, SearchOptions } from 'types/Search';*/ + -var DEFAULTSEARCHRESULTFROM = 0; var DEBOUNCED_SEARCH_SUGGESTION_MS = 300; /*:: type Dispatch = (action: any) => any;*/ // We can't use env's because they aren't passed into node_modules -/*:: type GetState = () => {};*/ +/*:: type GetState = () => { search: SearchState };*/ var CONNECTION_STRING = 'https://lighthouse.lbry.io/'; -var setSearchApi = exports.setSearchApi = function setSearchApi(endpoint) { +var setSearchApi = exports.setSearchApi = function setSearchApi(endpoint /*: string*/) { CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end; }; -var doSearch = exports.doSearch = function doSearch(rawQuery /*: string*/) { - var size /*: number*/ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULTSEARCHRESULTSIZE; - var from /*: number*/ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULTSEARCHRESULTFROM; +var doSearch = exports.doSearch = function doSearch(rawQuery /*: string*/, size /*: ?number*/, from /*: ?number*/) { var isBackgroundSearch /*: boolean*/ = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; return function (dispatch /*: Dispatch*/, getState /*: GetState*/) { - var state = getState(); var query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' '); if (!query) { @@ -5229,8 +5313,11 @@ var doSearch = exports.doSearch = function doSearch(rawQuery /*: string*/) { return; } + var state = getState(); + var queryWithOptions = (0, _search.makeSelectQueryWithOptions)(query, size, from, isBackgroundSearch)(state); + // If we have already searched for something, we don't need to do anything - var urisForQuery = (0, _search.makeSelectSearchUris)(query)(state); + var urisForQuery = (0, _search.makeSelectSearchUris)(queryWithOptions)(state); if (urisForQuery && !!urisForQuery.length) { return; } @@ -5250,8 +5337,7 @@ var doSearch = exports.doSearch = function doSearch(rawQuery /*: string*/) { }); } - var encodedQuery = encodeURIComponent(query); - fetch(CONNECTION_STRING + 'search?s=' + encodedQuery + '&size=' + size + '&from=' + from).then(_handleFetch2.default).then(function (data) { + fetch(CONNECTION_STRING + 'search?' + queryWithOptions).then(_handleFetch2.default).then(function (data) { var uris = []; var actions = []; @@ -5267,7 +5353,7 @@ var doSearch = exports.doSearch = function doSearch(rawQuery /*: string*/) { actions.push({ type: ACTIONS.SEARCH_SUCCESS, data: { - query: query, + query: queryWithOptions, uris: uris } }); @@ -5346,6 +5432,23 @@ var doBlurSearchInput = exports.doBlurSearchInput = function doBlurSearchInput() }; }; +var doUpdateSearchOptions = exports.doUpdateSearchOptions = function doUpdateSearchOptions(newOptions /*: SearchOptions*/) { + return function (dispatch /*: Dispatch*/, getState /*: GetState*/) { + var state = getState(); + var searchQuery = (0, _search.selectSearchQuery)(state); + + dispatch({ + type: ACTIONS.UPDATE_SEARCH_OPTIONS, + data: newOptions + }); + + if (searchQuery) { + // After updating, perform a search with the new options + dispatch(doSearch(searchQuery)); + } + }; +}; + /***/ }), /* 33 */ /***/ (function(module, exports, __webpack_require__) { @@ -6176,12 +6279,13 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } // @flow -/*:: import type { - NotificationState, - DoToast, - DoNotification, - DoEditNotification, - DoDeleteNotification, +/*:: import type { + NotificationState, + DoToast, + DoError, + DoNotification, + DoEditNotification, + DoDeleteNotification, } from 'types/Notification';*/ @@ -6237,7 +6341,7 @@ var notificationsReducer = (0, _reduxUtils.handleActions)((_handleActions = {}, return _extends({}, state, { notifications: newNotifications }); -}), _defineProperty(_handleActions, ACTIONS.CREATE_ERROR, function (state /*: NotificationState*/, action /*: DoToast*/) { +}), _defineProperty(_handleActions, ACTIONS.CREATE_ERROR, function (state /*: NotificationState*/, action /*: DoError*/) { var error = action.data; var newErrors = state.errors.slice(); newErrors.push(error); @@ -6301,10 +6405,9 @@ Object.defineProperty(exports, "__esModule", { }); exports.searchReducer = undefined; -var _handleActions; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; // @flow +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _options, _handleActions; var _action_types = __webpack_require__(2); @@ -6312,55 +6415,28 @@ var ACTIONS = _interopRequireWildcard(_action_types); var _reduxUtils = __webpack_require__(42); +var _search = __webpack_require__(21); + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } // @flow -/*:: type SearchSuccess = { - type: ACTIONS.SEARCH_SUCCESS, - data: { - query: string, - uris: Array, - }, -};*/ -/*:: 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, - }, -};*/ -/*:: type SearchState = { - isActive: boolean, - searchQuery: string, - suggestions: Array, - urisByQuery: {}, -};*/ -/*:: type HistoryNavigate = { - type: ACTIONS.HISTORY_NAVIGATE, - data: { - url: string, - index?: number, - scrollY?: number, - }, -};*/ + +/*:: import type { + SearchState, + SearchSuccess, + UpdateSearchQuery, + UpdateSearchSuggestions, + HistoryNavigate, + UpdateSearchOptions, +} from 'types/Search';*/ var defaultState = { isActive: false, // does the user have any typed text in the search input focused: false, // is the search input focused searchQuery: '', // needs to be an empty string for input focusing + options: (_options = {}, _defineProperty(_options, _search.SEARCH_OPTIONS.RESULT_COUNT, 30), _defineProperty(_options, _search.SEARCH_OPTIONS.CLAIM_TYPE, _search.SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS), _defineProperty(_options, _search.SEARCH_OPTIONS.MEDIA_AUDIO, true), _defineProperty(_options, _search.SEARCH_OPTIONS.MEDIA_VIDEO, true), _defineProperty(_options, _search.SEARCH_OPTIONS.MEDIA_TEXT, true), _defineProperty(_options, _search.SEARCH_OPTIONS.MEDIA_IMAGE, true), _defineProperty(_options, _search.SEARCH_OPTIONS.MEDIA_APPLICATION, true), _options), suggestions: {}, urisByQuery: {} }; @@ -6412,6 +6488,14 @@ var searchReducer = exports.searchReducer = (0, _reduxUtils.handleActions)((_han return _extends({}, state, { focused: false }); +}), _defineProperty(_handleActions, ACTIONS.UPDATE_SEARCH_OPTIONS, function (state /*: SearchState*/, action /*: UpdateSearchOptions*/) /*: SearchState*/ { + var oldOptions = state.options; + + var newOptions = action.data; + var options = _extends({}, oldOptions, newOptions); + return _extends({}, state, { + options: options + }); }), _handleActions), defaultState); /***/ }), @@ -6443,32 +6527,32 @@ var buildDraftTransaction = function buildDraftTransaction() { // TODO: Split into common success and failure types // See details in https://github.com/lbryio/lbry/issues/1307 -/*:: type ActionResult = { - type: any, - result: any, +/*:: type ActionResult = { + type: any, + result: any, };*/ -/*:: type WalletState = { - balance: any, - blocks: any, - latestBlock: number, - transactions: any, - fetchingTransactions: boolean, - gettingNewAddress: boolean, - draftTransaction: any, - sendingSupport: boolean, - walletIsEncrypted: boolean, - walletEncryptPending: boolean, - walletEncryptSucceded: ?boolean, - walletEncryptResult: ?boolean, - walletDecryptPending: boolean, - walletDecryptSucceded: ?boolean, - walletDecryptResult: ?boolean, - walletUnlockPending: boolean, - walletUnlockSucceded: ?boolean, - walletUnlockResult: ?boolean, - walletLockPending: boolean, - walletLockSucceded: ?boolean, - walletLockResult: ?boolean, +/*:: type WalletState = { + balance: any, + blocks: any, + latestBlock: ?number, + transactions: any, + fetchingTransactions: boolean, + gettingNewAddress: boolean, + draftTransaction: any, + sendingSupport: boolean, + walletIsEncrypted: boolean, + walletEncryptPending: boolean, + walletEncryptSucceded: ?boolean, + walletEncryptResult: ?boolean, + walletDecryptPending: boolean, + walletDecryptSucceded: ?boolean, + walletDecryptResult: ?boolean, + walletUnlockPending: boolean, + walletUnlockSucceded: ?boolean, + walletUnlockResult: ?boolean, + walletLockPending: boolean, + walletLockSucceded: ?boolean, + walletLockResult: ?boolean, };*/ @@ -6955,7 +7039,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); /* hardcoded names still exist for these in reducers/settings.js - only discovered when debugging */ -/* Many SETTINGS are stored in the localStorage by their name - +/* Many SETTINGS are stored in the localStorage by their name - be careful about changing the value of a SETTINGS constant, as doing so can invalidate existing SETTINGS */ var CREDIT_REQUIRED_ACKNOWLEDGED = exports.CREDIT_REQUIRED_ACKNOWLEDGED = 'credit_required_acknowledged'; var NEW_USER_ACKNOWLEDGED = exports.NEW_USER_ACKNOWLEDGED = 'welcome_acknowledged'; diff --git a/src/constants/action_types.js b/src/constants/action_types.js index 766bcd8..eb94996 100644 --- a/src/constants/action_types.js +++ b/src/constants/action_types.js @@ -117,6 +117,7 @@ export const SEARCH_START = 'SEARCH_START'; export const SEARCH_SUCCESS = 'SEARCH_SUCCESS'; export const SEARCH_FAIL = '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'; diff --git a/src/constants/search.js b/src/constants/search.js index 5bdbcd1..80d291f 100644 --- a/src/constants/search.js +++ b/src/constants/search.js @@ -1,3 +1,18 @@ -export const FILE = 'file'; -export const CHANNEL = 'channel'; -export const SEARCH = 'search'; +export const SEARCH_TYPES = { + FILE: 'file', + 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', +}; diff --git a/src/index.js b/src/index.js index dafa1fd..3efe1b8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,10 @@ import * as ACTIONS from 'constants/action_types'; import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses'; -import * as SEARCH_TYPES from 'constants/search'; import * as SETTINGS from 'constants/settings'; import * as TRANSACTIONS from 'constants/transaction_types'; import * as SORT_OPTIONS from 'constants/sort_options'; import * as PAGES from 'constants/pages'; +import { SEARCH_TYPES, SEARCH_OPTIONS } from 'constants/search'; import Lbry from 'lbry'; import Lbryapi from 'lbryapi'; @@ -14,7 +14,16 @@ import { selectState as selectSearchState } from 'redux/selectors/search'; export { Toast } from 'types/Notification'; // 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 export { Lbry, Lbryapi }; @@ -59,6 +68,7 @@ export { doFocusSearchInput, doBlurSearchInput, setSearchApi, + doUpdateSearchOptions, } from 'redux/actions/search'; export { doBlackListedOutpointsSubscribe } from 'redux/actions/blacklist'; @@ -191,11 +201,13 @@ export { makeSelectSearchUris, selectSearchQuery, selectSearchValue, + selectSearchOptions, selectIsSearching, selectSearchUrisByQuery, selectWunderBarAddress, selectSearchBarFocused, selectSearchSuggestions, + makeSelectQueryWithOptions, } from 'redux/selectors/search'; export { diff --git a/src/redux/actions/search.js b/src/redux/actions/search.js index a21880f..ee1fc7f 100644 --- a/src/redux/actions/search.js +++ b/src/redux/actions/search.js @@ -1,32 +1,35 @@ // @flow +import type { SearchState, SearchOptions } from 'types/Search'; import * as ACTIONS from 'constants/action_types'; import { buildURI } from 'lbryURI'; 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 debounce from 'util/debounce'; import handleFetchResponse from 'util/handle-fetch'; -const DEFAULTSEARCHRESULTSIZE = 10; -const DEFAULTSEARCHRESULTFROM = 0; const DEBOUNCED_SEARCH_SUGGESTION_MS = 300; 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 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; }; export const doSearch = ( - rawQuery: string, - size: number = DEFAULTSEARCHRESULTSIZE, - from: number = DEFAULTSEARCHRESULTFROM, + rawQuery: string, // pass in a query if you don't want to search for what's in the search bar + size: ?number, // only pass in if you don't want to use the users setting (ex: related content) + from: ?number, isBackgroundSearch: boolean = false ) => (dispatch: Dispatch, getState: GetState) => { - const state = getState(); const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' '); if (!query) { @@ -36,8 +39,11 @@ export const doSearch = ( 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 - const urisForQuery = makeSelectSearchUris(query)(state); + const urisForQuery = makeSelectSearchUris(queryWithOptions)(state); if (urisForQuery && !!urisForQuery.length) { return; } @@ -57,8 +63,7 @@ export const doSearch = ( }); } - const encodedQuery = encodeURIComponent(query); - fetch(`${CONNECTION_STRING}search?s=${encodedQuery}&size=${size}&from=${from}`) + fetch(`${CONNECTION_STRING}search?${queryWithOptions}`) .then(handleFetchResponse) .then(data => { const uris = []; @@ -76,7 +81,7 @@ export const doSearch = ( actions.push({ type: ACTIONS.SEARCH_SUCCESS, data: { - query, + query: queryWithOptions, uris, }, }); @@ -149,3 +154,21 @@ export const doBlurSearchInput = () => (dispatch: Dispatch) => dispatch({ 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)); + } +}; diff --git a/src/redux/reducers/notifications.js b/src/redux/reducers/notifications.js index 5c1af1a..45b0429 100644 --- a/src/redux/reducers/notifications.js +++ b/src/redux/reducers/notifications.js @@ -2,6 +2,7 @@ import type { NotificationState, DoToast, + DoError, DoNotification, DoEditNotification, DoDeleteNotification, @@ -55,7 +56,7 @@ const notificationsReducer = handleActions( let notifications = state.notifications.slice(); notifications = notifications.map( - (pastNotification) => + pastNotification => pastNotification.id === notification.id ? notification : pastNotification ); @@ -67,7 +68,7 @@ const notificationsReducer = handleActions( [ACTIONS.DELETE_NOTIFICATION]: (state: NotificationState, action: DoDeleteNotification) => { const { id } = action.data; let newNotifications = state.notifications.slice(); - newNotifications = newNotifications.filter((notification) => notification.id !== id); + newNotifications = newNotifications.filter(notification => notification.id !== id); return { ...state, @@ -76,7 +77,7 @@ const notificationsReducer = handleActions( }, // Errors - [ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoToast) => { + [ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoError) => { const error = action.data; const newErrors = state.errors.slice(); newErrors.push(error); diff --git a/src/redux/reducers/search.js b/src/redux/reducers/search.js index 4372ee8..cb85149 100644 --- a/src/redux/reducers/search.js +++ b/src/redux/reducers/search.js @@ -1,56 +1,29 @@ // @flow import * as ACTIONS from 'constants/action_types'; import { handleActions } from 'util/redux-utils'; - -type SearchSuccess = { - type: ACTIONS.SEARCH_SUCCESS, - data: { - query: string, - uris: Array, - }, -}; - -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, - }, -}; - -type SearchState = { - isActive: boolean, - searchQuery: string, - suggestions: Array, - urisByQuery: {}, -}; - -type HistoryNavigate = { - type: ACTIONS.HISTORY_NAVIGATE, - data: { - url: string, - index?: number, - scrollY?: number, - }, -}; +import { SEARCH_OPTIONS } from 'constants/search'; +import type { + SearchState, + SearchSuccess, + UpdateSearchQuery, + UpdateSearchSuggestions, + HistoryNavigate, + UpdateSearchOptions, +} from 'types/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: {}, }; @@ -123,6 +96,18 @@ export const searchReducer = handleActions( ...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 ); diff --git a/src/redux/reducers/wallet.js b/src/redux/reducers/wallet.js index d3f0acb..0df481e 100644 --- a/src/redux/reducers/wallet.js +++ b/src/redux/reducers/wallet.js @@ -17,7 +17,7 @@ type ActionResult = { type WalletState = { balance: any, blocks: any, - latestBlock: number, + latestBlock: ?number, transactions: any, fetchingTransactions: boolean, gettingNewAddress: boolean, @@ -73,7 +73,7 @@ reducers[ACTIONS.FETCH_TRANSACTIONS_COMPLETED] = (state: WalletState, action) => const { transactions } = action.data; - transactions.forEach((transaction) => { + transactions.forEach(transaction => { byId[transaction.txid] = transaction; }); diff --git a/src/redux/selectors/claims.js b/src/redux/selectors/claims.js index a287151..d7e0448 100644 --- a/src/redux/selectors/claims.js +++ b/src/redux/selectors/claims.js @@ -3,6 +3,7 @@ import { makeSelectCurrentParam } from 'redux/selectors/navigation'; import { selectSearchUrisByQuery } from 'redux/selectors/search'; import { createSelector } from 'reselect'; import { isClaimNsfw } from 'util/claim'; +import { getSearchQueryString } from 'util/query_params'; const selectState = state => state.claims || {}; @@ -297,7 +298,9 @@ export const makeSelectRecommendedContentForUri = uri => const { title } = claim.value.stream.metadata; - let searchUris = searchUrisByQuery[title.replace(/\//, ' ')]; + const searchQuery = getSearchQueryString(title.replace(/\//, ' ')); + + let searchUris = searchUrisByQuery[searchQuery]; if (searchUris) { searchUris = searchUris.filter(searchUri => searchUri !== currentUri); recommendedContent = searchUris; diff --git a/src/redux/selectors/search.js b/src/redux/selectors/search.js index 16000d2..5a0b322 100644 --- a/src/redux/selectors/search.js +++ b/src/redux/selectors/search.js @@ -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 { selectCurrentPage, selectCurrentParams } from 'redux/selectors/navigation'; 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 selectSearchQuery = createSelector( - selectCurrentPage, - selectCurrentParams, - (page, params) => (page === 'search' ? params && params.query : null) +export const selectSearchValue: (state: State) => string = createSelector( + selectState, + state => state.searchQuery ); -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 } = 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 } = createSelector(selectState, state => state.urisByQuery); + +export const makeSelectSearchUris = (query: string): ((state: State) => Array) => // replace statement below is kind of ugly, and repeated in doSearch action createSelector( selectSearchUrisByQuery, @@ -30,7 +53,7 @@ export const selectWunderBarAddress = createSelector( selectCurrentPage, selectSearchQuery, selectCurrentParams, - (page, query, params) => { + (page: string, query: string, params: { uri: string }) => { // only populate the wunderbar address if we are on the file/channel pages // or show the search query if (page === 'show') { @@ -40,13 +63,12 @@ export const selectWunderBarAddress = createSelector( } ); -export const selectSearchBarFocused = createSelector(selectState, state => state.focused); -// export const selectSear +export const selectSearchBarFocused: boolean = createSelector(selectState, state => state.focused); -export const selectSearchSuggestions = createSelector( +export const selectSearchSuggestions: Array = createSelector( selectSearchValue, selectSuggestions, - (query, suggestions) => { + (query: string, suggestions: { [string]: Array }) => { if (!query) { return []; } @@ -117,3 +139,23 @@ export const selectSearchSuggestions = createSelector( 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; + }); diff --git a/src/types/Search.js b/src/types/Search.js new file mode 100644 index 0000000..1e01dcf --- /dev/null +++ b/src/types/Search.js @@ -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 }, + urisByQuery: {}, +}; + +export type SearchSuccess = { + type: ACTIONS.SEARCH_SUCCESS, + data: { + query: string, + uris: Array, + }, +}; + +export type UpdateSearchQuery = { + type: ACTIONS.UPDATE_SEARCH_QUERY, + data: { + query: string, + }, +}; + +export type UpdateSearchSuggestions = { + type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS, + data: { + query: string, + suggestions: Array, + }, +}; + +export type HistoryNavigate = { + type: ACTIONS.HISTORY_NAVIGATE, + data: { + url: string, + index?: number, + scrollY?: number, + }, +}; + +export type UpdateSearchOptions = { + type: ACTIONS.UPDATE_SEARCH_OPTIONS, + data: SearchOptions, +}; diff --git a/src/util/query_params.js b/src/util/query_params.js index e7f04d1..1e99cb2 100644 --- a/src/util/query_params.js +++ b/src/util/query_params.js @@ -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 {}; const parts = queryString .split('?') @@ -14,7 +20,7 @@ export function parseQueryParams(queryString) { return params; } -export function toQueryString(params) { +export function toQueryString(params: { [string]: string | number }) { if (!params) return ''; const parts = []; @@ -26,3 +32,39 @@ export function toQueryString(params) { 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('&'); +}; -- 2.43.4