Files
2022-12-09 08:36:26 -06:00

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)