408 lines
13 KiB
JavaScript
408 lines
13 KiB
JavaScript
import {createAction, handleActions} from 'redux-actions'
|
|
import {fromJS} from 'immutable'
|
|
import * as searchApi from '../../../api/searchApi'
|
|
import * as feedsApi from '../../../api/feedsApi'
|
|
import {addAlert} from '../common/alerts'
|
|
import {getRestrictions} from '../common/auth'
|
|
import {getSidebarCategories} from './sidebar'
|
|
import {renewSearchBy, setCommonFilters} from './searchByFilters'
|
|
import {thunkAction, tokenInject} from '../../utils/common'
|
|
import * as helpers from '../../utils/helpers/search'
|
|
import { findFeedById } from '../../utils/helpers/sidebar'
|
|
import {filtersFromServerFormat, ADV_FILTERS_LIMIT} from '../../utils/helpers/advancedFilters'
|
|
|
|
import {
|
|
COMMENT_ARTICLE,
|
|
DELETE_ARTICLES,
|
|
DELETE_ARTICLES_FROM_FEED,
|
|
DELETE_COMMENT,
|
|
LOAD_MORE_COMMENTS,
|
|
UPDATE_COMMENT
|
|
} from './articles'
|
|
|
|
/*
|
|
* Constants
|
|
* */
|
|
const GET_SEARCH_RESULTS = 'GET_SEARCH_RESULTS'
|
|
|
|
const TOGGLE_REFINE_PANEL = 'TOGGLE_REFINE_PANEL'
|
|
const SELECT_REFINE_FILTER = 'SELECT_REFINE_FILTER'
|
|
const CLEAR_REFINE_FILTERS = 'CLEAR_REFINE_FILTERS'
|
|
const CLEAR_ALL_REFINE_FILTERS = 'CLEAR_ALL_REFINE_FILTERS'
|
|
const LOAD_MORE_REFINE_FILTERS = 'LOAD_MORE_REFINE_FILTERS'
|
|
const LOAD_LESS_REFINE_FILTERS = 'LOAD_LESS_REFINE_FILTERS'
|
|
|
|
const TOGGLE_SAVE_FEED_POPUP = 'TOGGLE_SAVE_FEED_POPUP'
|
|
|
|
const SET_FEED_RESULTS = 'SET_FEED_RESULTS'
|
|
|
|
const EDIT_FEED = 'EDIT_FEED'
|
|
|
|
const SET_NEW_SEARCH = 'SET_NEW_SEARCH'
|
|
const CHANGE_FEED_QUERY = 'CHANGE_FEED_QUERY'
|
|
const SET_ACTIVE_FEED = 'SET_ACTIVE_FEED'
|
|
const CHANGE_ACTIVE_FEED_NAME = 'CHANGE_ACTIVE_FEED_NAME'
|
|
|
|
const SEARCH_SET_VALUE = 'SEARCH_SET_VALUE'
|
|
|
|
const SELECT_ARTICLE = 'SELECT_ARTICLE'
|
|
const SELECT_ALL_ARTICLES = 'SELECT_ALL_ARTICLES'
|
|
const SAVE_FEED = 'SAVE_FEED'
|
|
const SAVE_AS_FEED = 'SAVE_AS_FEED'
|
|
|
|
/*
|
|
* Actions
|
|
* */
|
|
const getSearchResultsPending = createAction(GET_SEARCH_RESULTS + '_PENDING')
|
|
const getSearchResultsRejected = createAction(GET_SEARCH_RESULTS + '_REJECTED', (errors) => errors)
|
|
const setFeedResults = createAction(SET_FEED_RESULTS, (response) => response)
|
|
|
|
const _getSearchResults = (dispatch, getState, apiPromise, initialSearch = false) => {
|
|
dispatch(getSearchResultsPending())
|
|
return apiPromise
|
|
.then((response) => {
|
|
dispatch(setFeedResults(response))
|
|
if (response.meta.search.filters) {
|
|
dispatch(setCommonFilters(response.meta.search.filters, response.meta.sourceLists, response.meta.sources))
|
|
} else {
|
|
dispatch(renewSearchBy())
|
|
}
|
|
if (response.feed) {
|
|
const categories = getState().getIn(['appState', 'sidebar', 'categories']).toJS()
|
|
const feed = findFeedById(categories, parseInt(response.feed))
|
|
dispatch(setActiveFeed(fromJS(feed)))
|
|
}
|
|
const isFreeUser = getState().getIn(['common', 'auth', 'user', 'restrictions', 'plans', 'price']) === 0;
|
|
if (initialSearch && isFreeUser) {
|
|
const email = getState().getIn(['common', 'auth', 'user', 'email'])
|
|
searchApi.submitSearchHubspot({
|
|
email: email,
|
|
searchquery: response.meta.search.query
|
|
});
|
|
}
|
|
dispatch(getRestrictions())
|
|
})
|
|
.catch((errors) => {
|
|
dispatch(getSearchResultsRejected(errors))
|
|
dispatch(addAlert(errors))
|
|
})
|
|
}
|
|
|
|
const getSearchResults = (data, initialSearch) => {
|
|
return tokenInject((dispatch, getState, token) => {
|
|
const apiPromise = searchApi.searchQuery(token, data)
|
|
_getSearchResults(dispatch, getState, apiPromise, initialSearch)
|
|
})
|
|
}
|
|
|
|
const getFeedResults = (data, feedId) => {
|
|
return tokenInject((dispatch, getState, token) => {
|
|
const apiPromise = feedsApi.getFeedSearchResults(token, data, feedId)
|
|
_getSearchResults(dispatch, getState, apiPromise)
|
|
})
|
|
}
|
|
|
|
const saveAsFeed = thunkAction(SAVE_AS_FEED, (dataToSend, {token, dispatch, fulfilled}) => {
|
|
return feedsApi
|
|
.createFeed(token, dataToSend)
|
|
.then(() => {
|
|
fulfilled()
|
|
dispatch(getSidebarCategories())
|
|
dispatch(getRestrictions())
|
|
dispatch(addAlert({
|
|
type: 'notice',
|
|
transKey: 'saveFeed',
|
|
id: 'saveFeed'
|
|
}))
|
|
})
|
|
}, true)
|
|
|
|
const saveFeed = thunkAction(SAVE_FEED, (dataToSend, feedId, {token, dispatch, fulfilled}) => {
|
|
return feedsApi
|
|
.saveFeed(token, dataToSend, feedId)
|
|
.then(() => {
|
|
fulfilled()
|
|
dispatch(getSidebarCategories())
|
|
dispatch(getRestrictions())
|
|
dispatch(addAlert({
|
|
type: 'notice',
|
|
transKey: 'saveFeed',
|
|
id: 'saveFeed'
|
|
}))
|
|
})
|
|
}, true)
|
|
|
|
const toggleRefinePanel = createAction(TOGGLE_REFINE_PANEL)
|
|
const selectRefineFilter = createAction(SELECT_REFINE_FILTER, (groupName, filterValue) => {
|
|
return {groupName, filterValue}
|
|
})
|
|
const clearRefineFilters = createAction(CLEAR_REFINE_FILTERS)
|
|
const clearAllRefineFilters = createAction(CLEAR_ALL_REFINE_FILTERS)
|
|
const loadMoreRefineFilters = createAction(LOAD_MORE_REFINE_FILTERS, groupName => groupName)
|
|
const loadLessRefineFilters = createAction(LOAD_LESS_REFINE_FILTERS, groupName => groupName)
|
|
|
|
const toggleSaveFeedPopup = createAction(TOGGLE_SAVE_FEED_POPUP)
|
|
|
|
const editFeed = createAction(EDIT_FEED)
|
|
const setNewSearch = createAction(SET_NEW_SEARCH)
|
|
const changeFeedQuery = createAction(CHANGE_FEED_QUERY, value => value)
|
|
|
|
const setActiveFeed = createAction(SET_ACTIVE_FEED, feed => feed)
|
|
|
|
const changeActiveFeedName = createAction(CHANGE_ACTIVE_FEED_NAME, feedName => feedName)
|
|
|
|
const selectArticle = createAction(SELECT_ARTICLE, article => article)
|
|
const selectAllArticles = createAction(SELECT_ALL_ARTICLES, select => select)
|
|
|
|
export const actions = {
|
|
getSearchResults,
|
|
toggleSaveFeedPopup,
|
|
toggleRefinePanel,
|
|
loadMoreRefineFilters,
|
|
loadLessRefineFilters,
|
|
selectRefineFilter,
|
|
clearRefineFilters,
|
|
clearAllRefineFilters,
|
|
setFeedResults,
|
|
editFeed,
|
|
setNewSearch,
|
|
saveAsFeed,
|
|
saveFeed,
|
|
getFeedResults,
|
|
setActiveFeed,
|
|
changeActiveFeedName,
|
|
changeFeedQuery,
|
|
selectArticle,
|
|
selectAllArticles
|
|
}
|
|
|
|
/*
|
|
* State
|
|
* */
|
|
export const initialState = fromJS({
|
|
isEditingFeed: false,
|
|
isSavingFeed: false,
|
|
activeFeed: null,
|
|
loadedFeedName: null,
|
|
loadedFeedQuery: null,
|
|
searchResults: [],
|
|
searchResultsErrors: [],
|
|
searchResultsPending: false,
|
|
searchResultCount: 0,
|
|
searchResultTotalCount: 0,
|
|
searchResultPage: 1,
|
|
searchResultLimit: 100,
|
|
isSavedSearchVisible: false,
|
|
isSaveFeedPopupVisible: false,
|
|
isSynced: true,
|
|
isLoaded: false,
|
|
advancedFilters: {
|
|
all: {},
|
|
pages: {}, //{groupName1: {count: xx, totalCount: yy}, groupName2: ....}
|
|
selected: {}, // {keyword: {"fsdfdsf": 1}, groupName1: {value1: 0, value2: 1, ....}, groupName2: {}, ... } will be sent to the server
|
|
pending: {}, //which groups is not applied yet
|
|
isVisible: true
|
|
},
|
|
selectedArticles: []
|
|
})
|
|
|
|
const deselectArticlesReducer = (state, {payload: ids}) => {
|
|
const selectedArticles = state.get('selectedArticles').toJS()
|
|
const filtered = selectedArticles.filter((article) => !ids.includes(article.id))
|
|
return state.set('selectedArticles', fromJS(filtered))
|
|
}
|
|
/*
|
|
* Reducers
|
|
* */
|
|
export default handleActions({
|
|
|
|
[SEARCH_SET_VALUE]: (state, {payload}) => {
|
|
const {field, value} = payload
|
|
return state.set(field, value)
|
|
},
|
|
|
|
[`${GET_SEARCH_RESULTS}_PENDING`]: (state) => {
|
|
return state.merge({
|
|
searchResults: [],
|
|
searchResultCount: 0,
|
|
searchResultTotalCount: 0,
|
|
searchResultPage: 1,
|
|
searchResultsPending: true
|
|
})
|
|
},
|
|
|
|
[`${GET_SEARCH_RESULTS}_REJECTED`]: (state, {payload}) => {
|
|
return state.merge({
|
|
searchResults: [],
|
|
searchResultsErrors: payload,
|
|
searchResultCount: 0,
|
|
searchResultTotalCount: 0,
|
|
searchResultsPending: false
|
|
})
|
|
},
|
|
|
|
[SET_FEED_RESULTS]: (state, {payload: response}) => {
|
|
const {documents, advancedFilters, meta} = response
|
|
|
|
const selectedFilters = meta.search.advancedFilters
|
|
const {allFilters, pages} = filtersFromServerFormat(advancedFilters)
|
|
|
|
delete allFilters.reach // to hide reach in advanced filters
|
|
|
|
helpers.mergeAdvancedFilters(allFilters, selectedFilters, pages)
|
|
|
|
return state
|
|
.merge({
|
|
searchResults: documents.data,
|
|
searchResultsErrors: [],
|
|
searchResultCount: documents.count,
|
|
searchResultTotalCount: documents.totalCount,
|
|
searchResultPage: documents.page,
|
|
searchResultLimit: documents.limit,
|
|
searchResultsPending: false,
|
|
selectedArticles: [],
|
|
loadedFeedQuery: meta.search.query,
|
|
isSynced: meta.status === 'synced',
|
|
isLoaded: true,
|
|
isEditingFeed: true
|
|
})
|
|
.mergeIn(['advancedFilters'], {
|
|
all: allFilters,
|
|
selected: selectedFilters,
|
|
pages: pages,
|
|
pending: {}
|
|
})
|
|
},
|
|
|
|
[SET_ACTIVE_FEED]: (state, {payload: feed}) => {
|
|
return state.merge({
|
|
'activeFeed': feed,
|
|
'isEditingFeed': false
|
|
})
|
|
},
|
|
|
|
[CHANGE_ACTIVE_FEED_NAME]: (state, {payload: feedName}) => {
|
|
return state.setIn(['activeFeed', 'name'], feedName)
|
|
},
|
|
|
|
[TOGGLE_SAVE_FEED_POPUP]: (state, {payload}) => {
|
|
const isVisible = !state.get('isSaveFeedPopupVisible')
|
|
return state.set('isSaveFeedPopupVisible', isVisible)
|
|
},
|
|
|
|
[TOGGLE_REFINE_PANEL]: (state) => {
|
|
const path = ['advancedFilters', 'isVisible']
|
|
return state.setIn(path, !state.getIn(path))
|
|
},
|
|
|
|
[SELECT_REFINE_FILTER]: (state, {payload: {groupName, filterValue}}) => {
|
|
const path = ['advancedFilters', 'selected', groupName]
|
|
//two groups without multiple selection
|
|
if (groupName === 'articleDate' || groupName === 'keyword') {
|
|
state = state.deleteIn(path)
|
|
}
|
|
//tri-state switch
|
|
const currentState = state.getIn([...path, filterValue])
|
|
let newState
|
|
if (currentState === undefined) {
|
|
newState = 1
|
|
}
|
|
else if (currentState === 1) {
|
|
newState = -1
|
|
}
|
|
return state.deleteIn(['advancedFilters', 'pending', groupName]).setIn([...path, filterValue], newState)
|
|
},
|
|
|
|
[CLEAR_REFINE_FILTERS]: (state, {payload: groupName}) => {
|
|
return state.setIn(['advancedFilters', 'pending', groupName], true).deleteIn(['advancedFilters', 'selected', groupName])
|
|
},
|
|
|
|
[CLEAR_ALL_REFINE_FILTERS]: (state) => {
|
|
return state.mergeIn(['advancedFilters'], {
|
|
selected: {},
|
|
pending: {}
|
|
})
|
|
},
|
|
|
|
[LOAD_LESS_REFINE_FILTERS]: (state, {payload: groupName}) => {
|
|
const path = ['advancedFilters', 'pages', groupName, 'count']
|
|
const currentCount = state.getIn(path)
|
|
return state.setIn(path, Math.max(currentCount - ADV_FILTERS_LIMIT, ADV_FILTERS_LIMIT))
|
|
},
|
|
|
|
[LOAD_MORE_REFINE_FILTERS]: (state, {payload: groupName}) => {
|
|
const path = ['advancedFilters', 'pages', groupName]
|
|
const currentCount = state.getIn([...path, 'count'])
|
|
const totalCount = state.getIn([...path, 'totalCount'])
|
|
return state.setIn([...path, 'count'], Math.min(currentCount + ADV_FILTERS_LIMIT, totalCount))
|
|
},
|
|
|
|
[EDIT_FEED]: (state) => {
|
|
return state.set('isEditingFeed', true)
|
|
},
|
|
|
|
[SET_NEW_SEARCH]: (state) => {
|
|
return state.merge(initialState.toJS())
|
|
},
|
|
|
|
[CHANGE_FEED_QUERY]: (state, {payload: value}) => {
|
|
return state.merge({
|
|
loadedFeedQuery: value,
|
|
isEditingFeed: true
|
|
})
|
|
},
|
|
|
|
[`${SAVE_FEED} pending`]: (state, {payload: {isPending}}) => {
|
|
return state.set('isSavingFeed', isPending)
|
|
},
|
|
|
|
[`${SAVE_AS_FEED} pending`]: (state, {payload: {isPending}}) => {
|
|
return state.set('isSavingFeed', isPending)
|
|
},
|
|
|
|
[SELECT_ARTICLE]: (state, {payload: article}) => {
|
|
let selectedArticles = state.get('selectedArticles').toJS()
|
|
const articleIndex = helpers.indexById(selectedArticles, article.id)
|
|
if (articleIndex === -1) { //not selected yet
|
|
selectedArticles = selectedArticles.concat(article)
|
|
} else {
|
|
selectedArticles.splice(articleIndex, 1)
|
|
}
|
|
return state.set('selectedArticles', fromJS(selectedArticles))
|
|
},
|
|
|
|
[SELECT_ALL_ARTICLES]: (state, {payload: select}) => {
|
|
const selected = select ? state.get('searchResults').toJS() : fromJS([])
|
|
return state.set('selectedArticles', selected)
|
|
},
|
|
|
|
[`${LOAD_MORE_COMMENTS} fulfilled`]: (state, {payload}) => {
|
|
const {articleId, response} = payload
|
|
const articles = state.get('searchResults')
|
|
return state.set('searchResults', helpers.loadMoreComments(articles, articleId, response.data))
|
|
},
|
|
|
|
[`${COMMENT_ARTICLE} fulfilled`]: (state, {payload}) => {
|
|
const {comment, articleId} = payload
|
|
const articles = state.get('searchResults')
|
|
return state.set('searchResults', helpers.addComment(articles, articleId, comment))
|
|
},
|
|
|
|
[`${UPDATE_COMMENT} fulfilled`]: (state, {payload}) => {
|
|
const {comment, articleId} = payload
|
|
const articles = state.get('searchResults')
|
|
return state.set('searchResults', helpers.updateComment(articles, articleId, comment))
|
|
},
|
|
|
|
[`${DELETE_COMMENT} fulfilled`]: (state, {payload}) => {
|
|
const {commentId, articleId} = payload
|
|
const articles = state.get('searchResults')
|
|
return state.set('searchResults', helpers.deleteComment(articles, articleId, commentId))
|
|
},
|
|
|
|
//only remove deleted articles the from selection
|
|
[DELETE_ARTICLES]: deselectArticlesReducer,
|
|
[`${DELETE_ARTICLES_FROM_FEED} fulfilled`]: deselectArticlesReducer
|
|
|
|
}, initialState)
|