diff --git a/src/newsreader/js/pages/homepage/App.js b/src/newsreader/js/pages/homepage/App.js index 0b2aedb..01ca773 100644 --- a/src/newsreader/js/pages/homepage/App.js +++ b/src/newsreader/js/pages/homepage/App.js @@ -31,6 +31,7 @@ class App extends React.Component { post={this.props.post} rule={this.props.rule} category={this.props.category} + selectedType={this.props.selectedType} feedUrl={this.props.feedUrl} subredditUrl={this.props.subredditUrl} timelineUrl={this.props.timelineUrl} @@ -62,6 +63,7 @@ const mapStateToProps = state => { error, rule, post: state.selected.post, + selectedType: state.selected.item.type, }; } diff --git a/src/newsreader/js/pages/homepage/actions/posts.js b/src/newsreader/js/pages/homepage/actions/posts.js index 23bd3f6..49858fa 100644 --- a/src/newsreader/js/pages/homepage/actions/posts.js +++ b/src/newsreader/js/pages/homepage/actions/posts.js @@ -96,6 +96,23 @@ export const toggleSaved = (post, token) => { }; }; +// TODO add tests +export const fetchSavedPosts = (next = false) => { + return dispatch => { + dispatch(requestPosts()); + + const url = next ? next : '/api/posts/?saved=true'; + + return fetch(url) + .then(response => response.json()) + .then(posts => dispatch(receivePosts(posts.results, posts.next))) + .catch(error => { + dispatch(receivePosts([])); + dispatch(handleAPIError(error)); + }); + }; +}; + export const fetchPostsBySection = (section, next = false) => { return dispatch => { if (section.unread === 0) { diff --git a/src/newsreader/js/pages/homepage/actions/selected.js b/src/newsreader/js/pages/homepage/actions/selected.js index 189cad6..44fe79d 100644 --- a/src/newsreader/js/pages/homepage/actions/selected.js +++ b/src/newsreader/js/pages/homepage/actions/selected.js @@ -4,6 +4,9 @@ import { receiveRule, requestRule } from './rules.js'; import { CATEGORY_TYPE, RULE_TYPE } from '../constants.js'; export const MARK_SECTION_READ = 'MARK_SECTION_READ'; +export const SELECT_SAVED = 'SELECT_SAVED'; + +export const selectSaved = () => ({ type: SELECT_SAVED }); export const markSectionRead = section => ({ type: MARK_SECTION_READ, diff --git a/src/newsreader/js/pages/homepage/components/PostModal.js b/src/newsreader/js/pages/homepage/components/PostModal.js index 7ee5261..6da8044 100644 --- a/src/newsreader/js/pages/homepage/components/PostModal.js +++ b/src/newsreader/js/pages/homepage/components/PostModal.js @@ -6,6 +6,7 @@ import { unSelectPost, markPostRead, toggleSaved } from '../actions/posts.js'; import { CATEGORY_TYPE, RULE_TYPE, + SAVED_TYPE, FEED, SUBREDDIT, TWITTER_TIMELINE, @@ -21,7 +22,7 @@ class PostModal extends React.Component { const markPostRead = this.props.markPostRead; const token = Cookies.get('csrftoken'); - if (this.props.autoMarking && !post.read) { + if (this.props.autoMarking && this.props.selectedType != SAVED_TYPE && !post.read) { this.readTimer = setTimeout(markPostRead, 3000, post, token); } @@ -51,7 +52,8 @@ class PostModal extends React.Component { const token = Cookies.get('csrftoken'); const publicationDate = formatDatetime(post.publicationDate); const titleClassName = post.read ? 'post__title post__title--read' : 'post__title'; - const readButtonDisabled = post.read || this.props.isUpdating; + const readButtonDisabled = + post.read || this.props.isUpdating || this.props.selectedType === SAVED_TYPE; const savedIconClass = post.saved ? 'saved-icon saved-icon--saved' : 'saved-icon'; let ruleUrl = ''; diff --git a/src/newsreader/js/pages/homepage/components/postlist/PostItem.js b/src/newsreader/js/pages/homepage/components/postlist/PostItem.js index 58637bf..fe63a68 100644 --- a/src/newsreader/js/pages/homepage/components/postlist/PostItem.js +++ b/src/newsreader/js/pages/homepage/components/postlist/PostItem.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import { CATEGORY_TYPE, RULE_TYPE, + SAVED_TYPE, FEED, SUBREDDIT, TWITTER_TIMELINE, @@ -43,7 +44,7 @@ class PostItem extends React.Component { {publicationDate} {this.props.timezone} {post.author && `By ${post.author}`} - {this.props.selected.type == CATEGORY_TYPE && ( + {[CATEGORY_TYPE, SAVED_TYPE].includes(this.props.selected.type) && ( {rule.name} diff --git a/src/newsreader/js/pages/homepage/components/postlist/PostList.js b/src/newsreader/js/pages/homepage/components/postlist/PostList.js index 282300b..82617f8 100644 --- a/src/newsreader/js/pages/homepage/components/postlist/PostList.js +++ b/src/newsreader/js/pages/homepage/components/postlist/PostList.js @@ -2,7 +2,8 @@ import React from 'react'; import { connect } from 'react-redux'; import { isEqual } from 'lodash'; -import { fetchPostsBySection } from '../../actions/posts.js'; +import { fetchPostsBySection, fetchSavedPosts } from '../../actions/posts.js'; +import { SAVED_TYPE } from '../../constants.js'; import { filterPosts } from './filters.js'; import LoadingIndicator from '../../../../components/LoadingIndicator.js'; @@ -33,11 +34,15 @@ class PostList extends React.Component { } paginate() { - this.props.fetchPostsBySection(this.props.selected, this.props.next); + if (this.props.selected.type === SAVED_TYPE) { + this.props.fetchSavedPosts(this.props.next); + } else { + this.props.fetchPostsBySection(this.props.selected, this.props.next); + } } render() { - const postItems = this.props.postsBySection.map((item, index) => { + const postItems = this.props.postsByType.map((item, index) => { return (
- +

Select an item to show its unread posts

@@ -83,7 +88,7 @@ class PostList extends React.Component { const mapStateToProps = state => ({ isFetching: state.posts.isFetching, - postsBySection: filterPosts(state), + postsByType: filterPosts(state), next: state.selected.next, lastReached: state.selected.lastReached, selected: state.selected.item, @@ -91,6 +96,7 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ fetchPostsBySection: (rule, next = false) => dispatch(fetchPostsBySection(rule, next)), + fetchSavedPosts: (next = false) => dispatch(fetchSavedPosts(next)), }); export default connect(mapStateToProps, mapDispatchToProps)(PostList); diff --git a/src/newsreader/js/pages/homepage/components/postlist/filters.js b/src/newsreader/js/pages/homepage/components/postlist/filters.js index 3024aaf..8439fc9 100644 --- a/src/newsreader/js/pages/homepage/components/postlist/filters.js +++ b/src/newsreader/js/pages/homepage/components/postlist/filters.js @@ -1,4 +1,4 @@ -import { CATEGORY_TYPE, RULE_TYPE } from '../../constants.js'; +import { CATEGORY_TYPE, RULE_TYPE, SAVED_TYPE } from '../../constants.js'; const isEmpty = (object = {}) => { return Object.keys(object).length === 0; @@ -17,6 +17,10 @@ const sortOrdering = (firstPost, secondPost) => { return dateOrdering; }; +const savedOrdering = (firstPost, secondPost) => { + return new Date(firstPost.publicationDate) < new Date(secondPost.publicationDate); +}; + export const filterPostsByRule = (rule = {}, posts = []) => { const filteredPosts = posts.filter(post => { return post.rule === rule.id; @@ -45,15 +49,24 @@ export const filterPostsByCategory = (category = {}, rules = [], posts = []) => return sortedPosts; }; +export const filterPostsBySaved = (rules = [], posts = []) => { + const filteredPosts = posts.filter(post => post.saved); + return filteredPosts + .map(post => ({ ...post, rule: { ...rules.find(rule => rule.id === post.rule) } })) + .sort(savedOrdering); +}; + export const filterPosts = state => { const posts = Object.values({ ...state.posts.items }); + const rules = Object.values({ ...state.rules.items }); switch (state.selected.item.type) { case CATEGORY_TYPE: - const rules = Object.values({ ...state.rules.items }); return filterPostsByCategory({ ...state.selected.item }, rules, posts); case RULE_TYPE: return filterPostsByRule({ ...state.selected.item }, posts); + case SAVED_TYPE: + return filterPostsBySaved(rules, posts); } return []; diff --git a/src/newsreader/js/pages/homepage/components/sidebar/SavedItem.js b/src/newsreader/js/pages/homepage/components/sidebar/SavedItem.js index 96c4573..31b865a 100644 --- a/src/newsreader/js/pages/homepage/components/sidebar/SavedItem.js +++ b/src/newsreader/js/pages/homepage/components/sidebar/SavedItem.js @@ -1,13 +1,27 @@ import React from 'react'; import { connect } from 'react-redux'; +import { fetchSavedPosts } from '../../actions/posts.js'; +import { selectSaved } from '../../actions/selected.js'; +import { SAVED_TYPE } from '../../constants.js'; + class SavedItem extends React.Component { + handleSelect() { + this.props.selectSaved(); + this.props.fetchSavedPosts(); + } + render() { + const className = + this.props.selected.type === SAVED_TYPE + ? 'sidebar__container sidebar__container--selected' + : 'sidebar__container'; + return (
  • -
    +
    -
    +
    this.handleSelect()}> Saved posts
    @@ -16,4 +30,9 @@ class SavedItem extends React.Component { } } -export default connect()(SavedItem); +const mapDispatchToProps = dispatch => ({ + selectSaved: () => dispatch(selectSaved()), + fetchSavedPosts: () => dispatch(fetchSavedPosts()), +}); + +export default connect(null, mapDispatchToProps)(SavedItem); diff --git a/src/newsreader/js/pages/homepage/components/sidebar/Sidebar.js b/src/newsreader/js/pages/homepage/components/sidebar/Sidebar.js index 706e90b..b435b7d 100644 --- a/src/newsreader/js/pages/homepage/components/sidebar/Sidebar.js +++ b/src/newsreader/js/pages/homepage/components/sidebar/Sidebar.js @@ -34,7 +34,7 @@ class Sidebar extends React.Component { )}
      - + {categoryItems}
    diff --git a/src/newsreader/js/pages/homepage/constants.js b/src/newsreader/js/pages/homepage/constants.js index 22184b9..0f5629b 100644 --- a/src/newsreader/js/pages/homepage/constants.js +++ b/src/newsreader/js/pages/homepage/constants.js @@ -1,5 +1,6 @@ export const RULE_TYPE = 'RULE'; export const CATEGORY_TYPE = 'CATEGORY'; +export const SAVED_TYPE = 'SAVED'; export const SUBREDDIT = 'subreddit'; export const FEED = 'feed'; diff --git a/src/newsreader/js/pages/homepage/reducers/posts.js b/src/newsreader/js/pages/homepage/reducers/posts.js index bb06f3d..dd795a0 100644 --- a/src/newsreader/js/pages/homepage/reducers/posts.js +++ b/src/newsreader/js/pages/homepage/reducers/posts.js @@ -4,7 +4,6 @@ import { objectsFromArray } from '../../../utils.js'; import { CATEGORY_TYPE, RULE_TYPE } from '../constants.js'; import { - SELECT_POST, MARKING_POST, MARK_POST_READ, RECEIVE_POST, diff --git a/src/newsreader/js/pages/homepage/reducers/selected.js b/src/newsreader/js/pages/homepage/reducers/selected.js index babcb82..b1f1f98 100644 --- a/src/newsreader/js/pages/homepage/reducers/selected.js +++ b/src/newsreader/js/pages/homepage/reducers/selected.js @@ -9,8 +9,9 @@ import { UNSELECT_POST, } from '../actions/posts.js'; -import { MARK_SECTION_READ } from '../actions/selected.js'; +import { MARK_SECTION_READ, SELECT_SAVED } from '../actions/selected.js'; import { MARK_POST_READ } from '../actions/posts.js'; +import { SAVED_TYPE } from '../constants.js'; const defaultState = { item: {}, next: false, lastReached: false, post: {} }; @@ -47,6 +48,13 @@ export const selected = (state = { ...defaultState }, action) => { next: false, lastReached: false, }; + case SELECT_SAVED: + return { + ...state, + item: { type: SAVED_TYPE }, + next: false, + lastReached: false, + }; case RECEIVE_POSTS: return { ...state,