Add front-end bits

This commit is contained in:
Sonny Bakker 2021-02-27 13:13:51 +01:00
parent 2c8c5e5635
commit 5bec9ce5ee
12 changed files with 87 additions and 16 deletions

View file

@ -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,
};
}

View file

@ -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) {

View file

@ -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,

View file

@ -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 = '';

View file

@ -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 {
<span className="posts-info__date" title={publicationDate}>
{publicationDate} {this.props.timezone} {post.author && `By ${post.author}`}
</span>
{this.props.selected.type == CATEGORY_TYPE && (
{[CATEGORY_TYPE, SAVED_TYPE].includes(this.props.selected.type) && (
<span className="badge">
<a href={ruleUrl} target="_blank" rel="noopener noreferrer">
{rule.name}

View file

@ -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 (
<PostItem
key={index}
@ -55,7 +60,7 @@ class PostList extends React.Component {
return (
<div className="post-message">
<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>
</div>
</div>
@ -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);

View file

@ -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 [];

View file

@ -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 (
<li className="sidebar__item">
<div className="sidebar__container">
<div className={className}>
<span className="sidebar__icon saved-icon" />
<div className="sidebar__text">
<div className="sidebar__text" onClick={() => this.handleSelect()}>
<span>Saved posts</span>
</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);

View file

@ -34,7 +34,7 @@ class Sidebar extends React.Component {
)}
<ul className="sidebar__nav">
<SavedItem />
<SavedItem selected={this.props.selected.item} />
{categoryItems}
</ul>

View file

@ -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';

View file

@ -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,

View file

@ -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,