Add front-end bits
This commit is contained in:
parent
2c8c5e5635
commit
5bec9ce5ee
12 changed files with 87 additions and 16 deletions
|
|
@ -31,6 +31,7 @@ class App extends React.Component {
|
||||||
post={this.props.post}
|
post={this.props.post}
|
||||||
rule={this.props.rule}
|
rule={this.props.rule}
|
||||||
category={this.props.category}
|
category={this.props.category}
|
||||||
|
selectedType={this.props.selectedType}
|
||||||
feedUrl={this.props.feedUrl}
|
feedUrl={this.props.feedUrl}
|
||||||
subredditUrl={this.props.subredditUrl}
|
subredditUrl={this.props.subredditUrl}
|
||||||
timelineUrl={this.props.timelineUrl}
|
timelineUrl={this.props.timelineUrl}
|
||||||
|
|
@ -62,6 +63,7 @@ const mapStateToProps = state => {
|
||||||
error,
|
error,
|
||||||
rule,
|
rule,
|
||||||
post: state.selected.post,
|
post: state.selected.post,
|
||||||
|
selectedType: state.selected.item.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
export const fetchPostsBySection = (section, next = false) => {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
if (section.unread === 0) {
|
if (section.unread === 0) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { receiveRule, requestRule } from './rules.js';
|
||||||
import { CATEGORY_TYPE, RULE_TYPE } from '../constants.js';
|
import { CATEGORY_TYPE, RULE_TYPE } from '../constants.js';
|
||||||
|
|
||||||
export const MARK_SECTION_READ = 'MARK_SECTION_READ';
|
export const MARK_SECTION_READ = 'MARK_SECTION_READ';
|
||||||
|
export const SELECT_SAVED = 'SELECT_SAVED';
|
||||||
|
|
||||||
|
export const selectSaved = () => ({ type: SELECT_SAVED });
|
||||||
|
|
||||||
export const markSectionRead = section => ({
|
export const markSectionRead = section => ({
|
||||||
type: MARK_SECTION_READ,
|
type: MARK_SECTION_READ,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { unSelectPost, markPostRead, toggleSaved } from '../actions/posts.js';
|
||||||
import {
|
import {
|
||||||
CATEGORY_TYPE,
|
CATEGORY_TYPE,
|
||||||
RULE_TYPE,
|
RULE_TYPE,
|
||||||
|
SAVED_TYPE,
|
||||||
FEED,
|
FEED,
|
||||||
SUBREDDIT,
|
SUBREDDIT,
|
||||||
TWITTER_TIMELINE,
|
TWITTER_TIMELINE,
|
||||||
|
|
@ -21,7 +22,7 @@ class PostModal extends React.Component {
|
||||||
const markPostRead = this.props.markPostRead;
|
const markPostRead = this.props.markPostRead;
|
||||||
const token = Cookies.get('csrftoken');
|
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);
|
this.readTimer = setTimeout(markPostRead, 3000, post, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +52,8 @@ class PostModal extends React.Component {
|
||||||
const token = Cookies.get('csrftoken');
|
const token = Cookies.get('csrftoken');
|
||||||
const publicationDate = formatDatetime(post.publicationDate);
|
const publicationDate = formatDatetime(post.publicationDate);
|
||||||
const titleClassName = post.read ? 'post__title post__title--read' : 'post__title';
|
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';
|
const savedIconClass = post.saved ? 'saved-icon saved-icon--saved' : 'saved-icon';
|
||||||
|
|
||||||
let ruleUrl = '';
|
let ruleUrl = '';
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
CATEGORY_TYPE,
|
CATEGORY_TYPE,
|
||||||
RULE_TYPE,
|
RULE_TYPE,
|
||||||
|
SAVED_TYPE,
|
||||||
FEED,
|
FEED,
|
||||||
SUBREDDIT,
|
SUBREDDIT,
|
||||||
TWITTER_TIMELINE,
|
TWITTER_TIMELINE,
|
||||||
|
|
@ -43,7 +44,7 @@ class PostItem extends React.Component {
|
||||||
<span className="posts-info__date" title={publicationDate}>
|
<span className="posts-info__date" title={publicationDate}>
|
||||||
{publicationDate} {this.props.timezone} {post.author && `By ${post.author}`}
|
{publicationDate} {this.props.timezone} {post.author && `By ${post.author}`}
|
||||||
</span>
|
</span>
|
||||||
{this.props.selected.type == CATEGORY_TYPE && (
|
{[CATEGORY_TYPE, SAVED_TYPE].includes(this.props.selected.type) && (
|
||||||
<span className="badge">
|
<span className="badge">
|
||||||
<a href={ruleUrl} target="_blank" rel="noopener noreferrer">
|
<a href={ruleUrl} target="_blank" rel="noopener noreferrer">
|
||||||
{rule.name}
|
{rule.name}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { isEqual } from 'lodash';
|
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 { filterPosts } from './filters.js';
|
||||||
|
|
||||||
import LoadingIndicator from '../../../../components/LoadingIndicator.js';
|
import LoadingIndicator from '../../../../components/LoadingIndicator.js';
|
||||||
|
|
@ -33,11 +34,15 @@ class PostList extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
paginate() {
|
paginate() {
|
||||||
|
if (this.props.selected.type === SAVED_TYPE) {
|
||||||
|
this.props.fetchSavedPosts(this.props.next);
|
||||||
|
} else {
|
||||||
this.props.fetchPostsBySection(this.props.selected, this.props.next);
|
this.props.fetchPostsBySection(this.props.selected, this.props.next);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const postItems = this.props.postsBySection.map((item, index) => {
|
const postItems = this.props.postsByType.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<PostItem
|
<PostItem
|
||||||
key={index}
|
key={index}
|
||||||
|
|
@ -55,7 +60,7 @@ class PostList extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className="post-message">
|
<div className="post-message">
|
||||||
<div className="post-message__block">
|
<div className="post-message__block">
|
||||||
<i class="fas fa-arrow-left" />
|
<i className="fas fa-arrow-left" />
|
||||||
<p className="post-message__text">Select an item to show its unread posts</p>
|
<p className="post-message__text">Select an item to show its unread posts</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -83,7 +88,7 @@ class PostList extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
isFetching: state.posts.isFetching,
|
isFetching: state.posts.isFetching,
|
||||||
postsBySection: filterPosts(state),
|
postsByType: filterPosts(state),
|
||||||
next: state.selected.next,
|
next: state.selected.next,
|
||||||
lastReached: state.selected.lastReached,
|
lastReached: state.selected.lastReached,
|
||||||
selected: state.selected.item,
|
selected: state.selected.item,
|
||||||
|
|
@ -91,6 +96,7 @@ const mapStateToProps = state => ({
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
fetchPostsBySection: (rule, next = false) => dispatch(fetchPostsBySection(rule, next)),
|
fetchPostsBySection: (rule, next = false) => dispatch(fetchPostsBySection(rule, next)),
|
||||||
|
fetchSavedPosts: (next = false) => dispatch(fetchSavedPosts(next)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
|
export default connect(mapStateToProps, mapDispatchToProps)(PostList);
|
||||||
|
|
|
||||||
|
|
@ -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 = {}) => {
|
const isEmpty = (object = {}) => {
|
||||||
return Object.keys(object).length === 0;
|
return Object.keys(object).length === 0;
|
||||||
|
|
@ -17,6 +17,10 @@ const sortOrdering = (firstPost, secondPost) => {
|
||||||
return dateOrdering;
|
return dateOrdering;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const savedOrdering = (firstPost, secondPost) => {
|
||||||
|
return new Date(firstPost.publicationDate) < new Date(secondPost.publicationDate);
|
||||||
|
};
|
||||||
|
|
||||||
export const filterPostsByRule = (rule = {}, posts = []) => {
|
export const filterPostsByRule = (rule = {}, posts = []) => {
|
||||||
const filteredPosts = posts.filter(post => {
|
const filteredPosts = posts.filter(post => {
|
||||||
return post.rule === rule.id;
|
return post.rule === rule.id;
|
||||||
|
|
@ -45,15 +49,24 @@ export const filterPostsByCategory = (category = {}, rules = [], posts = []) =>
|
||||||
return sortedPosts;
|
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 => {
|
export const filterPosts = state => {
|
||||||
const posts = Object.values({ ...state.posts.items });
|
const posts = Object.values({ ...state.posts.items });
|
||||||
|
const rules = Object.values({ ...state.rules.items });
|
||||||
|
|
||||||
switch (state.selected.item.type) {
|
switch (state.selected.item.type) {
|
||||||
case CATEGORY_TYPE:
|
case CATEGORY_TYPE:
|
||||||
const rules = Object.values({ ...state.rules.items });
|
|
||||||
return filterPostsByCategory({ ...state.selected.item }, rules, posts);
|
return filterPostsByCategory({ ...state.selected.item }, rules, posts);
|
||||||
case RULE_TYPE:
|
case RULE_TYPE:
|
||||||
return filterPostsByRule({ ...state.selected.item }, posts);
|
return filterPostsByRule({ ...state.selected.item }, posts);
|
||||||
|
case SAVED_TYPE:
|
||||||
|
return filterPostsBySaved(rules, posts);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,27 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
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 {
|
class SavedItem extends React.Component {
|
||||||
|
handleSelect() {
|
||||||
|
this.props.selectSaved();
|
||||||
|
this.props.fetchSavedPosts();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const className =
|
||||||
|
this.props.selected.type === SAVED_TYPE
|
||||||
|
? 'sidebar__container sidebar__container--selected'
|
||||||
|
: 'sidebar__container';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="sidebar__item">
|
<li className="sidebar__item">
|
||||||
<div className="sidebar__container">
|
<div className={className}>
|
||||||
<span className="sidebar__icon saved-icon" />
|
<span className="sidebar__icon saved-icon" />
|
||||||
<div className="sidebar__text">
|
<div className="sidebar__text" onClick={() => this.handleSelect()}>
|
||||||
<span>Saved posts</span>
|
<span>Saved posts</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class Sidebar extends React.Component {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ul className="sidebar__nav">
|
<ul className="sidebar__nav">
|
||||||
<SavedItem />
|
<SavedItem selected={this.props.selected.item} />
|
||||||
{categoryItems}
|
{categoryItems}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export const RULE_TYPE = 'RULE';
|
export const RULE_TYPE = 'RULE';
|
||||||
export const CATEGORY_TYPE = 'CATEGORY';
|
export const CATEGORY_TYPE = 'CATEGORY';
|
||||||
|
export const SAVED_TYPE = 'SAVED';
|
||||||
|
|
||||||
export const SUBREDDIT = 'subreddit';
|
export const SUBREDDIT = 'subreddit';
|
||||||
export const FEED = 'feed';
|
export const FEED = 'feed';
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { objectsFromArray } from '../../../utils.js';
|
||||||
import { CATEGORY_TYPE, RULE_TYPE } from '../constants.js';
|
import { CATEGORY_TYPE, RULE_TYPE } from '../constants.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SELECT_POST,
|
|
||||||
MARKING_POST,
|
MARKING_POST,
|
||||||
MARK_POST_READ,
|
MARK_POST_READ,
|
||||||
RECEIVE_POST,
|
RECEIVE_POST,
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ import {
|
||||||
UNSELECT_POST,
|
UNSELECT_POST,
|
||||||
} from '../actions/posts.js';
|
} 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 { MARK_POST_READ } from '../actions/posts.js';
|
||||||
|
import { SAVED_TYPE } from '../constants.js';
|
||||||
|
|
||||||
const defaultState = { item: {}, next: false, lastReached: false, post: {} };
|
const defaultState = { item: {}, next: false, lastReached: false, post: {} };
|
||||||
|
|
||||||
|
|
@ -47,6 +48,13 @@ export const selected = (state = { ...defaultState }, action) => {
|
||||||
next: false,
|
next: false,
|
||||||
lastReached: false,
|
lastReached: false,
|
||||||
};
|
};
|
||||||
|
case SELECT_SAVED:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
item: { type: SAVED_TYPE },
|
||||||
|
next: false,
|
||||||
|
lastReached: false,
|
||||||
|
};
|
||||||
case RECEIVE_POSTS:
|
case RECEIVE_POSTS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue