diff --git a/src/newsreader/js/pages/homepage/App.js b/src/newsreader/js/pages/homepage/App.js index 5b5acde..1834bee 100644 --- a/src/newsreader/js/pages/homepage/App.js +++ b/src/newsreader/js/pages/homepage/App.js @@ -56,7 +56,7 @@ const mapStateToProps = state => { const { error } = state.error; if (!isEqual(state.selected.post, {})) { - const ruleId = state.selected.post.rule; + const ruleId = state.selected.post.rule.id; const rule = state.rules.items[ruleId]; const category = state.categories.items[rule.category]; diff --git a/src/newsreader/js/pages/homepage/components/postlist/PostItem.js b/src/newsreader/js/pages/homepage/components/postlist/PostItem.js index ffb6832..ddd60db 100644 --- a/src/newsreader/js/pages/homepage/components/postlist/PostItem.js +++ b/src/newsreader/js/pages/homepage/components/postlist/PostItem.js @@ -16,7 +16,7 @@ import { formatDatetime } from '../../../../utils.js'; class PostItem extends React.Component { render() { const rule = { ...this.props.post.rule }; - const post = { ...this.props.post, rule: rule.id }; + const post = { ...this.props.post }; const token = Cookies.get('csrftoken'); const publicationDate = formatDatetime(post.publicationDate); diff --git a/src/newsreader/js/pages/homepage/components/postlist/PostList.js b/src/newsreader/js/pages/homepage/components/postlist/PostList.js index 136df40..197d81b 100644 --- a/src/newsreader/js/pages/homepage/components/postlist/PostList.js +++ b/src/newsreader/js/pages/homepage/components/postlist/PostList.js @@ -6,6 +6,7 @@ import { fetchPostsBySection, fetchSavedPosts } from '../../actions/posts.js'; import { SAVED_TYPE } from '../../constants.js'; import { filterPosts } from './filters.js'; +import LoadingIndicator from '../../../../components/LoadingIndicator.js'; import PostItem from './PostItem.js'; class PostList extends React.Component { @@ -55,7 +56,7 @@ class PostList extends React.Component { } render() { - const isLastItem = this.props.postsByType.length - 1 == index; + const isLastItem = this.props.postsByType.toReversed().find(item => !item.read); const postItems = this.props.postsByType.map((item, index) => { const defaultProps = { @@ -68,7 +69,7 @@ class PostList extends React.Component { timezone: this.props.timezone, }; - if (isLastItem && !item.read) { + if (isLastItem?.id === item.id) { return ; } else { return ; diff --git a/src/newsreader/js/pages/homepage/components/postlist/filters.js b/src/newsreader/js/pages/homepage/components/postlist/filters.js index 8439fc9..b3dd16a 100644 --- a/src/newsreader/js/pages/homepage/components/postlist/filters.js +++ b/src/newsreader/js/pages/homepage/components/postlist/filters.js @@ -4,31 +4,10 @@ const isEmpty = (object = {}) => { return Object.keys(object).length === 0; }; -const sortOrdering = (firstPost, secondPost) => { - const dateOrdering = - new Date(firstPost.publicationDate) < new Date(secondPost.publicationDate); - - if (firstPost.read && !secondPost.read) { - return 1; - } else if (secondPost.read && !firstPost.read) { - return -1; - } - - 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; + return posts.filter(post => { + return post.rule.id === rule.id; }); - - const filteredData = filteredPosts.map(post => ({ ...post, rule: { ...rule } })); - - return filteredData.sort(sortOrdering); }; export const filterPostsByCategory = (category = {}, rules = [], posts = []) => { @@ -36,24 +15,13 @@ export const filterPostsByCategory = (category = {}, rules = [], posts = []) => return rule.category === category.id; }); - const filteredData = filteredRules.map(rule => { - const filteredPosts = posts.filter(post => { - return post.rule === rule.id; - }); + const ruleIds = filteredRules.map(rule => rule.id); - return filteredPosts.map(post => ({ ...post, rule: { ...rule } })); - }); - - const sortedPosts = [...filteredData.flat()].sort(sortOrdering); - - return sortedPosts; + return [...posts].filter(post => ruleIds.includes(post.rule.id)); }; 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); + return [...posts].filter(post => post.saved); }; export const filterPosts = state => { diff --git a/src/newsreader/js/pages/homepage/reducers/posts.js b/src/newsreader/js/pages/homepage/reducers/posts.js index d422ca7..cdd44fa 100644 --- a/src/newsreader/js/pages/homepage/reducers/posts.js +++ b/src/newsreader/js/pages/homepage/reducers/posts.js @@ -12,56 +12,78 @@ import { } from '../actions/posts.js'; import { MARK_SECTION_READ } from '../actions/selected.js'; -const defaultState = { items: {}, isFetching: false, isUpdating: false }; +const sortOrdering = (firstPost, secondPost) => { + const dateOrdering = + new Date(firstPost.publicationDate) < new Date(secondPost.publicationDate); + + if (firstPost.read && !secondPost.read) { + return 1; + } else if (secondPost.read && !firstPost.read) { + return -1; + } + + return dateOrdering; +}; + +const defaultState = { items: [], isFetching: false, isUpdating: false }; export const posts = (state = { ...defaultState }, action) => { switch (action.type) { case REQUEST_POSTS: return { ...state, isFetching: true }; - // TODO: save post per category/rule in corresponding reducers case RECEIVE_POST: + const postIndex = state.items.findIndex(post => post.id === action.post.id); + let newItems = [...state.items]; + + if (postIndex >= 0) { + newItems.splice(postIndex, 1, { ...action.post }); + } else { + newItems = [...state.items, { ...action.post }]; + } + + newItems.sort(sortOrdering); + return { ...state, - items: { ...state.items, [action.post.id]: { ...action.post } }, + items: newItems, }; - // TODO: save posts per category/rule in corresponding reducers case RECEIVE_POSTS: - const receivedItems = objectsFromArray(action.posts, 'id'); + const receivedPosts = objectsFromArray(action.posts, 'id'); + + let mergedPosts = { ...objectsFromArray(state.items, 'id'), ...receivedPosts }; + let sortedPosts = Object.values(mergedPosts).sort(sortOrdering); return { ...state, isFetching: false, - items: { ...state.items, ...receivedItems }, + items: sortedPosts, }; case MARK_SECTION_READ: - const updatedPosts = {}; - let relatedPosts = []; + let posts = []; + // TODO: test these cases switch (action.section.type) { case CATEGORY_TYPE: - relatedPosts = Object.values({ ...state.items }).filter(post => { - return post.rule in { ...action.section.rules }; + posts = [...state.items].forEach(post => { + if (!(post.rule.id in { ...action.section.rules })) return; + + post.read = true; }); break; case RULE_TYPE: - relatedPosts = Object.values({ ...state.items }).filter(post => { - return post.rule === action.section.id; + posts = [...state.items].forEach(post => { + if (post.rule.id != action.section.id) return; + + post.read = true; }); break; } - relatedPosts.forEach(post => { - updatedPosts[post.id] = { ...post, read: true }; - }); - return { ...state, - items: { - ...state.items, - ...updatedPosts, - }, + items: posts, }; case MARKING_POST: return { ...state, isUpdating: true }; diff --git a/src/newsreader/news/core/serializers.py b/src/newsreader/news/core/serializers.py index 38619a1..d77469a 100644 --- a/src/newsreader/news/core/serializers.py +++ b/src/newsreader/news/core/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers +from newsreader.news.collection.serializers import RuleSerializer from newsreader.news.core.models import Category, Post @@ -9,6 +10,8 @@ class PostSerializer(serializers.ModelSerializer): ) remoteIdentifier = serializers.CharField(source="remote_identifier", required=False) + rule = RuleSerializer() + class Meta: model = Post fields = (