Relation search widget (#186)
* search action/reducer refactor * Relation widget skeleton * search clearing * query action + reducer * Autocomplete component for RelationControl
This commit is contained in:
parent
4f6f4bfae9
commit
05337ff232
@ -121,6 +121,7 @@
|
||||
"prosemirror-view": "^0.12.0",
|
||||
"react": "^15.1.0",
|
||||
"react-addons-css-transition-group": "^15.3.1",
|
||||
"react-autosuggest": "^7.0.1",
|
||||
"react-datetime": "^2.6.0",
|
||||
"react-dom": "^15.1.0",
|
||||
"react-hot-loader": "^3.0.0-beta.2",
|
||||
|
@ -27,10 +27,6 @@ export const ENTRY_PERSIST_REQUEST = 'ENTRY_PERSIST_REQUEST';
|
||||
export const ENTRY_PERSIST_SUCCESS = 'ENTRY_PERSIST_SUCCESS';
|
||||
export const ENTRY_PERSIST_FAILURE = 'ENTRY_PERSIST_FAILURE';
|
||||
|
||||
export const SEARCH_ENTRIES_REQUEST = 'SEARCH_ENTRIES_REQUEST';
|
||||
export const SEARCH_ENTRIES_SUCCESS = 'SEARCH_ENTRIES_SUCCESS';
|
||||
export const SEARCH_ENTRIES_FAILURE = 'SEARCH_ENTRIES_FAILURE';
|
||||
|
||||
/*
|
||||
* Simple Action Creators (Internal)
|
||||
* We still need to export them for tests
|
||||
@ -122,35 +118,6 @@ export function emmptyDraftCreated(entry) {
|
||||
payload: entry,
|
||||
};
|
||||
}
|
||||
|
||||
export function searchingEntries(searchTerm) {
|
||||
return {
|
||||
type: SEARCH_ENTRIES_REQUEST,
|
||||
payload: { searchTerm },
|
||||
};
|
||||
}
|
||||
|
||||
export function searchSuccess(searchTerm, entries, page) {
|
||||
return {
|
||||
type: SEARCH_ENTRIES_SUCCESS,
|
||||
payload: {
|
||||
searchTerm,
|
||||
entries,
|
||||
page,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function searchFailure(searchTerm, error) {
|
||||
return {
|
||||
type: SEARCH_ENTRIES_FAILURE,
|
||||
payload: {
|
||||
searchTerm,
|
||||
error,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Exported simple Action Creators
|
||||
*/
|
||||
@ -244,23 +211,3 @@ export function persistEntry(collection, entryDraft) {
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function searchEntries(searchTerm, page = 0) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
let collections = state.collections.keySeq().toArray();
|
||||
collections = collections.filter(collection => selectIntegration(state, collection, 'search'));
|
||||
const integration = selectIntegration(state, collections[0], 'search');
|
||||
if (!integration) {
|
||||
dispatch(searchFailure(searchTerm, 'Search integration is not configured.'));
|
||||
}
|
||||
const provider = integration ?
|
||||
getIntegrationProvider(state.integrations, integration)
|
||||
: currentBackend(state.config);
|
||||
dispatch(searchingEntries(searchTerm));
|
||||
provider.search(collections, searchTerm, page).then(
|
||||
response => dispatch(searchSuccess(searchTerm, response.entries, response.pagination)),
|
||||
error => dispatch(searchFailure(searchTerm, error))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
137
src/actions/search.js
Normal file
137
src/actions/search.js
Normal file
@ -0,0 +1,137 @@
|
||||
import { currentBackend } from '../backends/backend';
|
||||
import { getIntegrationProvider } from '../integrations';
|
||||
import { selectIntegration } from '../reducers';
|
||||
|
||||
/*
|
||||
* Contant Declarations
|
||||
*/
|
||||
export const SEARCH_ENTRIES_REQUEST = 'SEARCH_ENTRIES_REQUEST';
|
||||
export const SEARCH_ENTRIES_SUCCESS = 'SEARCH_ENTRIES_SUCCESS';
|
||||
export const SEARCH_ENTRIES_FAILURE = 'SEARCH_ENTRIES_FAILURE';
|
||||
|
||||
export const QUERY_REQUEST = 'QUERY_REQUEST';
|
||||
export const QUERY_SUCCESS = 'QUERY_SUCCESS';
|
||||
export const QUERY_FAILURE = 'QUERY_FAILURE';
|
||||
|
||||
export const SEARCH_CLEAR = 'SEARCH_CLEAR';
|
||||
|
||||
/*
|
||||
* Simple Action Creators (Internal)
|
||||
* We still need to export them for tests
|
||||
*/
|
||||
export function searchingEntries(searchTerm) {
|
||||
return {
|
||||
type: SEARCH_ENTRIES_REQUEST,
|
||||
payload: { searchTerm },
|
||||
};
|
||||
}
|
||||
|
||||
export function searchSuccess(searchTerm, entries, page) {
|
||||
return {
|
||||
type: SEARCH_ENTRIES_SUCCESS,
|
||||
payload: {
|
||||
searchTerm,
|
||||
entries,
|
||||
page,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function searchFailure(searchTerm, error) {
|
||||
return {
|
||||
type: SEARCH_ENTRIES_FAILURE,
|
||||
payload: {
|
||||
searchTerm,
|
||||
error,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function querying(collection, searchFields, searchTerm) {
|
||||
return {
|
||||
type: QUERY_REQUEST,
|
||||
payload: {
|
||||
collection,
|
||||
searchFields,
|
||||
searchTerm,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function querySuccess(collection, searchFields, searchTerm, response) {
|
||||
return {
|
||||
type: QUERY_SUCCESS,
|
||||
payload: {
|
||||
collection,
|
||||
searchFields,
|
||||
searchTerm,
|
||||
response,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function queryFailure(collection, searchFields, searchTerm, error) {
|
||||
return {
|
||||
type: QUERY_SUCCESS,
|
||||
payload: {
|
||||
collection,
|
||||
searchFields,
|
||||
searchTerm,
|
||||
error,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Exported simple Action Creators
|
||||
*/
|
||||
|
||||
export function clearSearch() {
|
||||
return { type: SEARCH_CLEAR };
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Exported Thunk Action Creators
|
||||
*/
|
||||
|
||||
// SearchEntries will search for complete entries in all collections.
|
||||
export function searchEntries(searchTerm, page = 0) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
let collections = state.collections.keySeq().toArray();
|
||||
collections = collections.filter(collection => selectIntegration(state, collection, 'search'));
|
||||
const integration = selectIntegration(state, collections[0], 'search');
|
||||
if (!integration) {
|
||||
dispatch(searchFailure(searchTerm, 'Search integration is not configured.'));
|
||||
}
|
||||
const provider = integration ?
|
||||
getIntegrationProvider(state.integrations, integration)
|
||||
: currentBackend(state.config);
|
||||
dispatch(searchingEntries(searchTerm));
|
||||
provider.search(collections, searchTerm, page).then(
|
||||
response => dispatch(searchSuccess(searchTerm, response.entries, response.pagination)),
|
||||
error => dispatch(searchFailure(searchTerm, error))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// Instead of searching for complete entries, query will search for specific fields
|
||||
// in specific collections and return raw data (no entries).
|
||||
export function query(collection, searchFields, searchTerm) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const integration = selectIntegration(state, collection, 'search');
|
||||
if (!integration) {
|
||||
dispatch(searchFailure(searchTerm, 'Search integration is not configured.'));
|
||||
}
|
||||
const provider = integration ?
|
||||
getIntegrationProvider(state.integrations, integration)
|
||||
: currentBackend(state.config);
|
||||
dispatch(querying(collection, searchFields, searchTerm));
|
||||
provider.searchBy(searchFields, collection, searchTerm).then(
|
||||
response => dispatch(querySuccess(collection, searchFields, searchTerm, response)),
|
||||
error => dispatch(queryFailure(collection, searchFields, searchTerm, error))
|
||||
);
|
||||
};
|
||||
}
|
@ -21,6 +21,9 @@ import SelectControl from './Widgets/SelectControl';
|
||||
import SelectPreview from './Widgets/SelectPreview';
|
||||
import ObjectControl from './Widgets/ObjectControl';
|
||||
import ObjectPreview from './Widgets/ObjectPreview';
|
||||
import RelationControl from './Widgets/RelationControl';
|
||||
import RelationPreview from './Widgets/RelationPreview';
|
||||
|
||||
|
||||
registry.registerWidget('string', StringControl, StringPreview);
|
||||
registry.registerWidget('text', TextControl, TextPreview);
|
||||
@ -32,6 +35,7 @@ registry.registerWidget('date', DateControl, DatePreview);
|
||||
registry.registerWidget('datetime', DateTimeControl, DateTimePreview);
|
||||
registry.registerWidget('select', SelectControl, SelectPreview);
|
||||
registry.registerWidget('object', ObjectControl, ObjectPreview);
|
||||
registry.registerWidget('relation', RelationControl, RelationPreview);
|
||||
registry.registerWidget('unknown', UnknownControl, UnknownPreview);
|
||||
|
||||
export function resolveWidget(name) { // eslint-disable-line
|
||||
|
108
src/components/Widgets/RelationControl.js
Normal file
108
src/components/Widgets/RelationControl.js
Normal file
@ -0,0 +1,108 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import Autosuggest from 'react-autosuggest';
|
||||
import { connect } from 'react-redux';
|
||||
import { debounce } from 'lodash';
|
||||
import { Loader } from '../../components/UI/index';
|
||||
import { query, clearSearch } from '../../actions/search';
|
||||
|
||||
|
||||
function escapeRegexCharacters(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
class RelationControl extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.node,
|
||||
field: PropTypes.node,
|
||||
isFetching: PropTypes.bool,
|
||||
query: PropTypes.func.isRequired,
|
||||
clearSearch: PropTypes.func.isRequired,
|
||||
queryHits: PropTypes.array, // eslint-disable-line
|
||||
};
|
||||
|
||||
onChange = (event, { newValue }) => {
|
||||
this.props.onChange(newValue);
|
||||
};
|
||||
|
||||
onSuggestionsFetchRequested = debounce(({ value }) => {
|
||||
if (value.length < 3) return;
|
||||
const { field } = this.props;
|
||||
const collection = field.get('collection');
|
||||
const searchFields = field.get('searchFields').map(f => `data.${ f }`).toJS();
|
||||
this.props.query(collection, searchFields, value);
|
||||
}, 80);
|
||||
|
||||
onSuggestionsClearRequested = () => {
|
||||
this.props.clearSearch();
|
||||
};
|
||||
|
||||
getMatchingHits = (value) => {
|
||||
const { field, queryHits } = this.props;
|
||||
const searchFields = field.get('searchFields').toJS();
|
||||
const escapedValue = escapeRegexCharacters(value.trim());
|
||||
const regex = new RegExp(`^ ${ escapedValue }`, 'i');
|
||||
|
||||
if (escapedValue === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return queryHits.filter((hit) => {
|
||||
let testResult = false;
|
||||
searchFields.forEach((f) => {
|
||||
testResult = testResult || regex.test(hit.data[f]);
|
||||
});
|
||||
return testResult;
|
||||
});
|
||||
};
|
||||
|
||||
getSuggestionValue = (suggestion) => {
|
||||
const { field } = this.props;
|
||||
const valueField = field.get('valueField');
|
||||
return suggestion.data[valueField];
|
||||
};
|
||||
|
||||
renderSuggestion = (suggestion) => {
|
||||
const { field } = this.props;
|
||||
const valueField = field.get('valueField');
|
||||
return <span>{suggestion.data[valueField]}</span>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { value, isFetching, queryHits } = this.props;
|
||||
|
||||
const inputProps = {
|
||||
placeholder: '',
|
||||
value: value || '',
|
||||
onChange: this.onChange,
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Autosuggest
|
||||
suggestions={queryHits}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} // eslint-disable-line
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested} // eslint-disable-line
|
||||
getSuggestionValue={this.getSuggestionValue}
|
||||
renderSuggestion={this.renderSuggestion}
|
||||
inputProps={inputProps}
|
||||
/>
|
||||
<Loader active={isFetching} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const isFetching = state.search.get('isFetching');
|
||||
const queryHits = state.search.get('queryHits');
|
||||
return { isFetching, queryHits };
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
query,
|
||||
clearSearch,
|
||||
}
|
||||
)(RelationControl);
|
10
src/components/Widgets/RelationPreview.js
Normal file
10
src/components/Widgets/RelationPreview.js
Normal file
@ -0,0 +1,10 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import previewStyle from './defaultPreviewStyle';
|
||||
|
||||
export default function RelationPreview({ value }) {
|
||||
return <div style={previewStyle}>{ value }</div>;
|
||||
}
|
||||
|
||||
RelationPreview.propTypes = {
|
||||
value: PropTypes.node,
|
||||
};
|
@ -2,7 +2,7 @@ import React, { PropTypes } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectSearchedEntries } from '../reducers';
|
||||
import { searchEntries } from '../actions/entries';
|
||||
import { searchEntries, clearSearch } from '../actions/search';
|
||||
import { Loader } from '../components/UI';
|
||||
import EntryListing from '../components/EntryListing/EntryListing';
|
||||
import styles from './breakpoints.css';
|
||||
@ -12,6 +12,7 @@ class SearchPage extends React.Component {
|
||||
static propTypes = {
|
||||
isFetching: PropTypes.bool,
|
||||
searchEntries: PropTypes.func.isRequired,
|
||||
clearSearch: PropTypes.func.isRequired,
|
||||
searchTerm: PropTypes.string.isRequired,
|
||||
collections: ImmutablePropTypes.seq,
|
||||
entries: ImmutablePropTypes.list,
|
||||
@ -30,9 +31,13 @@ class SearchPage extends React.Component {
|
||||
searchEntries(nextProps.searchTerm);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearSearch();
|
||||
}
|
||||
|
||||
handleLoadMore = (page) => {
|
||||
const { searchTerm, searchEntries } = this.props;
|
||||
searchEntries(searchTerm, page);
|
||||
if (!isNaN(page)) searchEntries(searchTerm, page);
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -70,5 +75,8 @@ function mapStateToProps(state, ownProps) {
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ searchEntries }
|
||||
{
|
||||
searchEntries,
|
||||
clearSearch,
|
||||
}
|
||||
)(SearchPage);
|
||||
|
@ -33,6 +33,66 @@ h1 {
|
||||
}
|
||||
|
||||
:global {
|
||||
|
||||
& .react-autosuggest__container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
& .react-autosuggest__input {
|
||||
width: 240px;
|
||||
height: 30px;
|
||||
padding: 10px 20px;
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
& .react-autosuggest__input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
& .react-autosuggest__container--open .react-autosuggest__input {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
& .react-autosuggest__suggestions-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .react-autosuggest__container--open .react-autosuggest__suggestions-container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 51px;
|
||||
width: 100%;
|
||||
border: 1px solid #aaa;
|
||||
background-color: #fff;
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
& .react-autosuggest__suggestions-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
& .react-autosuggest__suggestion {
|
||||
cursor: pointer;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
& .react-autosuggest__suggestion--focused {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
|
||||
& .rdt {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -4,14 +4,13 @@ import {
|
||||
ENTRY_SUCCESS,
|
||||
ENTRIES_REQUEST,
|
||||
ENTRIES_SUCCESS,
|
||||
SEARCH_ENTRIES_REQUEST,
|
||||
SEARCH_ENTRIES_SUCCESS,
|
||||
} from '../actions/entries';
|
||||
|
||||
import { SEARCH_ENTRIES_SUCCESS } from '../actions/search';
|
||||
|
||||
let collection;
|
||||
let loadedEntries;
|
||||
let page;
|
||||
let searchTerm;
|
||||
|
||||
const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
|
||||
switch (action.type) {
|
||||
@ -43,29 +42,12 @@ const entries = (state = Map({ entities: Map(), pages: Map() }), action) => {
|
||||
}));
|
||||
});
|
||||
|
||||
case SEARCH_ENTRIES_REQUEST:
|
||||
if (action.payload.searchTerm !== state.getIn(['search', 'term'])) {
|
||||
return state.withMutations((map) => {
|
||||
map.setIn(['search', 'isFetching'], true);
|
||||
map.setIn(['search', 'term'], action.payload.searchTerm);
|
||||
});
|
||||
}
|
||||
return state;
|
||||
|
||||
case SEARCH_ENTRIES_SUCCESS:
|
||||
loadedEntries = action.payload.entries;
|
||||
page = action.payload.page;
|
||||
searchTerm = action.payload.searchTerm;
|
||||
return state.withMutations((map) => {
|
||||
loadedEntries.forEach(entry => (
|
||||
map.setIn(['entities', `${ entry.collection }.${ entry.slug }`], fromJS(entry).set('isFetching', false))
|
||||
));
|
||||
const ids = List(loadedEntries.map(entry => ({ collection: entry.collection, slug: entry.slug })));
|
||||
map.set('search', Map({
|
||||
page,
|
||||
term: searchTerm,
|
||||
ids: page === 0 ? ids : map.getIn(['search', 'ids'], List()).concat(ids),
|
||||
}));
|
||||
});
|
||||
|
||||
default:
|
||||
@ -82,9 +64,4 @@ export const selectEntries = (state, collection) => {
|
||||
return slugs && slugs.map(slug => selectEntry(state, collection, slug));
|
||||
};
|
||||
|
||||
export const selectSearchedEntries = (state) => {
|
||||
const searchItems = state.getIn(['search', 'ids']);
|
||||
return searchItems && searchItems.map(({ collection, slug }) => selectEntry(state, collection, slug));
|
||||
};
|
||||
|
||||
export default entries;
|
||||
|
@ -6,6 +6,7 @@ import entries, * as fromEntries from './entries';
|
||||
import editorialWorkflow, * as fromEditorialWorkflow from './editorialWorkflow';
|
||||
import entryDraft from './entryDraft';
|
||||
import collections from './collections';
|
||||
import search from './search';
|
||||
import medias, * as fromMedias from './medias';
|
||||
import globalUI from './globalUI';
|
||||
|
||||
@ -13,6 +14,7 @@ const reducers = {
|
||||
auth,
|
||||
config,
|
||||
collections,
|
||||
search,
|
||||
integrations,
|
||||
editor,
|
||||
entries,
|
||||
@ -33,8 +35,10 @@ export const selectEntry = (state, collection, slug) =>
|
||||
export const selectEntries = (state, collection) =>
|
||||
fromEntries.selectEntries(state.entries, collection);
|
||||
|
||||
export const selectSearchedEntries = state =>
|
||||
fromEntries.selectSearchedEntries(state.entries);
|
||||
export const selectSearchedEntries = (state) => {
|
||||
const searchItems = state.search.get('entryIds');
|
||||
return searchItems && searchItems.map(({ collection, slug }) => fromEntries.selectEntry(state.entries, collection, slug));
|
||||
};
|
||||
|
||||
export const selectUnpublishedEntry = (state, status, slug) =>
|
||||
fromEditorialWorkflow.selectUnpublishedEntry(state.editorialWorkflow, status, slug);
|
||||
|
67
src/reducers/search.js
Normal file
67
src/reducers/search.js
Normal file
@ -0,0 +1,67 @@
|
||||
import { Map, List } from 'immutable';
|
||||
|
||||
import {
|
||||
SEARCH_ENTRIES_REQUEST,
|
||||
SEARCH_ENTRIES_SUCCESS,
|
||||
QUERY_REQUEST,
|
||||
QUERY_SUCCESS,
|
||||
SEARCH_CLEAR,
|
||||
} from '../actions/search';
|
||||
|
||||
let loadedEntries;
|
||||
let response;
|
||||
let page;
|
||||
let searchTerm;
|
||||
|
||||
const defaultState = Map({ isFetching: false, term: null, page: 0, entryIds: [], queryHits: [] });
|
||||
|
||||
const entries = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case SEARCH_CLEAR:
|
||||
return defaultState;
|
||||
|
||||
case SEARCH_ENTRIES_REQUEST:
|
||||
if (action.payload.searchTerm !== state.get('term')) {
|
||||
return state.withMutations((map) => {
|
||||
map.set('isFetching', true);
|
||||
map.set('term', action.payload.searchTerm);
|
||||
});
|
||||
}
|
||||
return state;
|
||||
|
||||
case SEARCH_ENTRIES_SUCCESS:
|
||||
loadedEntries = action.payload.entries;
|
||||
page = action.payload.page;
|
||||
searchTerm = action.payload.searchTerm;
|
||||
return state.withMutations((map) => {
|
||||
const entryIds = List(loadedEntries.map(entry => ({ collection: entry.collection, slug: entry.slug })));
|
||||
map.set('isFetching', false);
|
||||
map.set('page', page);
|
||||
map.set('term', searchTerm);
|
||||
map.set('entryIds', page === 0 ? entryIds : map.get('entryIds', List()).concat(entryIds));
|
||||
});
|
||||
|
||||
case QUERY_REQUEST:
|
||||
if (action.payload.searchTerm !== state.get('term')) {
|
||||
return state.withMutations((map) => {
|
||||
map.set('isFetching', true);
|
||||
map.set('term', action.payload.searchTerm);
|
||||
});
|
||||
}
|
||||
return state;
|
||||
|
||||
case QUERY_SUCCESS:
|
||||
searchTerm = action.payload.searchTerm;
|
||||
response = action.payload.response;
|
||||
return state.withMutations((map) => {
|
||||
map.set('isFetching', false);
|
||||
map.set('term', searchTerm);
|
||||
map.set('queryHits', response.hits);
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default entries;
|
51
yarn.lock
51
yarn.lock
@ -1201,12 +1201,6 @@ braces@^1.8.2:
|
||||
preserve "^0.2.0"
|
||||
repeat-element "^1.1.2"
|
||||
|
||||
bricks.js@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/bricks.js/-/bricks.js-1.7.0.tgz#2863bde2f03cd48d41dcca88bea1a198c839f608"
|
||||
dependencies:
|
||||
knot.js "^1.1.1"
|
||||
|
||||
brorand@^1.0.1:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.0.6.tgz#4028706b915f91f7b349a2e0bf3c376039d216e5"
|
||||
@ -4676,10 +4670,6 @@ kind-of@^3.0.2:
|
||||
dependencies:
|
||||
is-buffer "^1.0.2"
|
||||
|
||||
knot.js@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/knot.js/-/knot.js-1.1.1.tgz#35dc900d3c62813f0ca119c3d6a0a598e5cb6896"
|
||||
|
||||
known-css-properties@^0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.0.5.tgz#33de5b8279010a72db917d33119e4c27c078490a"
|
||||
@ -6934,6 +6924,22 @@ react-addons-test-utils@^15.3.2:
|
||||
version "15.3.2"
|
||||
resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.3.2.tgz#c09a44f583425a4a9c1b38444d7a6c3e6f0f41f6"
|
||||
|
||||
react-autosuggest:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-7.0.1.tgz#e751d2c2e516a344f6cdc150672e85f134f5f2f1"
|
||||
dependencies:
|
||||
react-autowhatever "^7.0.0"
|
||||
react-redux "^4.4.5"
|
||||
redux "^3.6.0"
|
||||
shallow-equal "^1.0.0"
|
||||
|
||||
react-autowhatever@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-autowhatever/-/react-autowhatever-7.0.0.tgz#7ea19f8024183acf1568fc8e4b76c0d0cc250d00"
|
||||
dependencies:
|
||||
react-themeable "^1.1.0"
|
||||
section-iterator "^2.0.0"
|
||||
|
||||
react-css-themr@~1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/react-css-themr/-/react-css-themr-1.4.1.tgz#26fa63fe0a8f7343b019f088f88475ca89da2d5a"
|
||||
@ -7054,6 +7060,15 @@ react-redux@^4.4.0:
|
||||
lodash "^4.2.0"
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
react-redux@^4.4.5:
|
||||
version "4.4.6"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-4.4.6.tgz#4b9d32985307a11096a2dd61561980044fcc6209"
|
||||
dependencies:
|
||||
hoist-non-react-statics "^1.0.3"
|
||||
invariant "^2.0.0"
|
||||
lodash "^4.2.0"
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
react-router-redux@^4.0.5:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/react-router-redux/-/react-router-redux-4.0.6.tgz#10cf98dce911d7dd912a05bdb07fee4d3c563dee"
|
||||
@ -7090,6 +7105,12 @@ react-sortable@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-sortable/-/react-sortable-1.2.0.tgz#5acd7e1910df665408957035acb5f2354519d849"
|
||||
|
||||
react-themeable@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-themeable/-/react-themeable-1.1.0.tgz#7d4466dd9b2b5fa75058727825e9f152ba379a0e"
|
||||
dependencies:
|
||||
object-assign "^3.0.0"
|
||||
|
||||
react-toolbox@^1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-toolbox/-/react-toolbox-1.2.2.tgz#ae8f3290da9e053625df97a63df7224943b79679"
|
||||
@ -7279,7 +7300,7 @@ redux-thunk@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-1.0.3.tgz#778aa0099eea0595031ab6b39165f6670d8d26bd"
|
||||
|
||||
redux@^3.2.0, redux@^3.3.1, redux@^3.5.2:
|
||||
redux@^3.2.0, redux@^3.3.1, redux@^3.5.2, redux@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"
|
||||
dependencies:
|
||||
@ -7557,6 +7578,10 @@ sax@^1.1.4, sax@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
|
||||
|
||||
section-iterator@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
|
||||
|
||||
selection-position@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/selection-position/-/selection-position-1.0.0.tgz#e43f87151d94957efa170e10e02c901b47f703c7"
|
||||
@ -7640,6 +7665,10 @@ sha.js@2.2.6:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba"
|
||||
|
||||
shallow-equal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.0.0.tgz#508d1838b3de590ab8757b011b25e430900945f7"
|
||||
|
||||
shallowequal@0.2.x:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-0.2.2.tgz#1e32fd5bcab6ad688a4812cb0cc04efc75c7014e"
|
||||
|
Loading…
x
Reference in New Issue
Block a user