From c6e69496cf747f5577915d8ea0055477bfbe0b6f Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Tue, 27 Oct 2020 23:06:25 +0100 Subject: [PATCH 1/7] Initial setup --- src/newsreader/accounts/forms.py | 4 +++- .../migrations/0013_user_auto_mark_read.py | 20 +++++++++++++++++++ src/newsreader/accounts/models.py | 9 +++++++++ .../js/pages/homepage/components/PostModal.js | 2 ++ src/newsreader/news/core/views.py | 1 + 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/newsreader/accounts/migrations/0013_user_auto_mark_read.py diff --git a/src/newsreader/accounts/forms.py b/src/newsreader/accounts/forms.py index 7a29f99..6247eff 100644 --- a/src/newsreader/accounts/forms.py +++ b/src/newsreader/accounts/forms.py @@ -1,9 +1,11 @@ from django import forms from newsreader.accounts.models import User +from newsreader.core.forms import CheckboxInput class UserSettingsForm(forms.ModelForm): class Meta: model = User - fields = ("first_name", "last_name") + fields = ("first_name", "last_name", "auto_mark_read") + widgets = {"auto_mark_read": CheckboxInput} diff --git a/src/newsreader/accounts/migrations/0013_user_auto_mark_read.py b/src/newsreader/accounts/migrations/0013_user_auto_mark_read.py new file mode 100644 index 0000000..3d975e0 --- /dev/null +++ b/src/newsreader/accounts/migrations/0013_user_auto_mark_read.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.7 on 2020-10-27 21:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [("accounts", "0012_remove_user_task")] + + operations = [ + migrations.AddField( + model_name="user", + name="auto_mark_read", + field=models.BooleanField( + default=True, + help_text="Wether posts should be marked as read after x amount of seconds of reading", + verbose_name="Auto read marking", + ), + ) + ] diff --git a/src/newsreader/accounts/models.py b/src/newsreader/accounts/models.py index 2451445..c46dd93 100644 --- a/src/newsreader/accounts/models.py +++ b/src/newsreader/accounts/models.py @@ -45,6 +45,15 @@ class User(AbstractUser): twitter_oauth_token = models.CharField(max_length=255, blank=True, null=True) twitter_oauth_token_secret = models.CharField(max_length=255, blank=True, null=True) + # settings + auto_mark_read = models.BooleanField( + _("Auto read marking"), + default=True, + help_text=_( + "Wether posts should be marked as read after x amount of seconds of reading" + ), + ) + username = None objects = UserManager() diff --git a/src/newsreader/js/pages/homepage/components/PostModal.js b/src/newsreader/js/pages/homepage/components/PostModal.js index 153f2a8..a17afbd 100644 --- a/src/newsreader/js/pages/homepage/components/PostModal.js +++ b/src/newsreader/js/pages/homepage/components/PostModal.js @@ -12,6 +12,7 @@ import { } from '../constants.js'; import { formatDatetime } from '../../../utils.js'; +// TODO add "Mark as read" button class PostModal extends React.Component { modalListener = ::this.modalListener; readTimer = null; @@ -21,6 +22,7 @@ class PostModal extends React.Component { const markPostRead = this.props.markPostRead; const token = Cookies.get('csrftoken'); + // TODO set timer depending on user setting if (!post.read) { this.readTimer = setTimeout(markPostRead, 3000, post, token); } diff --git a/src/newsreader/news/core/views.py b/src/newsreader/news/core/views.py index 18ec95c..c2ff4d5 100644 --- a/src/newsreader/news/core/views.py +++ b/src/newsreader/news/core/views.py @@ -27,6 +27,7 @@ class NewsView(TemplateView): ), "categoriesUrl": reverse_lazy("news:core:category-update", args=(0,)), "timezone": settings.TIME_ZONE, + "autoMarking": self.request.user.auto_mark_read, }, } From 67e06ee50e33d679ced3209b7592a25c52dad25d Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Wed, 28 Oct 2020 22:33:52 +0100 Subject: [PATCH 2/7] Add non-functional mark as read button --- .../js/pages/homepage/components/PostModal.js | 17 ++++++++++------- src/newsreader/scss/components/post/_post.scss | 12 +++++++++--- .../scss/components/sidebar/_sidebar.scss | 4 ++++ .../scss/elements/button/_read-button.scss | 2 -- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/newsreader/js/pages/homepage/components/PostModal.js b/src/newsreader/js/pages/homepage/components/PostModal.js index a17afbd..ee1c983 100644 --- a/src/newsreader/js/pages/homepage/components/PostModal.js +++ b/src/newsreader/js/pages/homepage/components/PostModal.js @@ -12,7 +12,6 @@ import { } from '../constants.js'; import { formatDatetime } from '../../../utils.js'; -// TODO add "Mark as read" button class PostModal extends React.Component { modalListener = ::this.modalListener; readTimer = null; @@ -65,12 +64,16 @@ class PostModal extends React.Component { return (
- this.props.unSelectPost()} - > - Close - +
+ {/* TODO: make button functional */} + Mark as read + this.props.unSelectPost()} + > + Close + +

{`${post.title} `}

diff --git a/src/newsreader/scss/components/post/_post.scss b/src/newsreader/scss/components/post/_post.scss index ae94f6c..7a8ea22 100644 --- a/src/newsreader/scss/components/post/_post.scss +++ b/src/newsreader/scss/components/post/_post.scss @@ -15,6 +15,15 @@ cursor: initial; + &__actions { + display: flex; + justify-content: flex-end; + width: 100%; + + padding: 20px 50px 0; + gap: 20px; + } + &__header { display: flex; flex-direction: column; @@ -81,9 +90,6 @@ } &__close-button { - position: relative; - margin: 1% 2% 0 0; - align-self: flex-end; background-color: $blue; color: $white; diff --git a/src/newsreader/scss/components/sidebar/_sidebar.scss b/src/newsreader/scss/components/sidebar/_sidebar.scss index f13faf3..c70594a 100644 --- a/src/newsreader/scss/components/sidebar/_sidebar.scss +++ b/src/newsreader/scss/components/sidebar/_sidebar.scss @@ -20,4 +20,8 @@ padding: 2px 10px 5px 10px; } } + + .read-button { + margin: 20px 0 0 0; + } } diff --git a/src/newsreader/scss/elements/button/_read-button.scss b/src/newsreader/scss/elements/button/_read-button.scss index 2c345b5..71e8e75 100644 --- a/src/newsreader/scss/elements/button/_read-button.scss +++ b/src/newsreader/scss/elements/button/_read-button.scss @@ -1,8 +1,6 @@ .read-button { @extend .button; - margin: 20px 0 0 0; - color: $white; background-color: $green; From 2b76c560c28c9dfe2436838a22da635534f01a9f Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Thu, 29 Oct 2020 22:42:46 +0100 Subject: [PATCH 3/7] Make mark read button functional --- .../js/pages/homepage/components/PostModal.js | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/newsreader/js/pages/homepage/components/PostModal.js b/src/newsreader/js/pages/homepage/components/PostModal.js index ee1c983..09867c9 100644 --- a/src/newsreader/js/pages/homepage/components/PostModal.js +++ b/src/newsreader/js/pages/homepage/components/PostModal.js @@ -49,30 +49,40 @@ class PostModal extends React.Component { render() { const post = this.props.post; + const token = Cookies.get('csrftoken'); const publicationDate = formatDatetime(post.publicationDate); const titleClassName = post.read ? 'post__title post__title--read' : 'post__title'; - let ruleUrl = ''; + const readButtonDisabled = post.read || this.props.isMarkingPost; - if (this.props.rule.type === SUBREDDIT) { - ruleUrl = `${this.props.subredditUrl}/${this.props.rule.id}/`; - } else if (this.props.rule.type === TWITTER_TIMELINE) { - ruleUrl = `${this.props.timelineUrl}/${this.props.rule.id}/`; - } else { - ruleUrl = `${this.props.feedUrl}/${this.props.rule.id}/`; + let ruleUrl = ''; + switch (this.props.rule.type) { + case SUBREDDIT: + ruleUrl = `${this.props.subredditUrl}/${this.props.rule.id}/`; + break; + case TWITTER_TIMELINE: + ruleUrl = `${this.props.timelineUrl}/${this.props.rule.id}/`; + break; + default: + ruleUrl = `${this.props.feedUrl}/${this.props.rule.id}/`; + break; } return (
- {/* TODO: make button functional */} - Mark as read - !readButtonDisabled && this.props.markPostRead(post, token)} + > + Mark as read + +

{`${post.title} `}

@@ -121,4 +131,7 @@ const mapDispatchToProps = dispatch => ({ markPostRead: (post, token) => dispatch(markPostRead(post, token)), }); -export default connect(null, mapDispatchToProps)(PostModal); +// TODO use different boolean for saving mark post as read state +const mapStateToProps = state => ({ isMarkingPost: state.posts.isFetching }); + +export default connect(mapStateToProps, mapDispatchToProps)(PostModal); From d60b01faa3fa8f32539f7a81d97a23bbe90c790e Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Thu, 29 Oct 2020 22:52:53 +0100 Subject: [PATCH 4/7] Enable/disable auto marking based on user setting --- src/newsreader/js/pages/homepage/App.js | 1 + src/newsreader/js/pages/homepage/components/PostModal.js | 3 +-- src/newsreader/js/pages/homepage/index.js | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/newsreader/js/pages/homepage/App.js b/src/newsreader/js/pages/homepage/App.js index b66d256..da76ef7 100644 --- a/src/newsreader/js/pages/homepage/App.js +++ b/src/newsreader/js/pages/homepage/App.js @@ -40,6 +40,7 @@ class App extends React.Component { timelineUrl={this.props.timelineUrl} categoriesUrl={this.props.categoriesUrl} timezone={this.props.timezone} + autoMarking={this.props.autoMarking} /> )} diff --git a/src/newsreader/js/pages/homepage/components/PostModal.js b/src/newsreader/js/pages/homepage/components/PostModal.js index 09867c9..91017dd 100644 --- a/src/newsreader/js/pages/homepage/components/PostModal.js +++ b/src/newsreader/js/pages/homepage/components/PostModal.js @@ -21,8 +21,7 @@ class PostModal extends React.Component { const markPostRead = this.props.markPostRead; const token = Cookies.get('csrftoken'); - // TODO set timer depending on user setting - if (!post.read) { + if (this.props.autoMarking && !post.read) { this.readTimer = setTimeout(markPostRead, 3000, post, token); } diff --git a/src/newsreader/js/pages/homepage/index.js b/src/newsreader/js/pages/homepage/index.js index 5279482..645592d 100644 --- a/src/newsreader/js/pages/homepage/index.js +++ b/src/newsreader/js/pages/homepage/index.js @@ -22,6 +22,7 @@ if (page) { timelineUrl={timelineUrl.substring(1, timelineUrl.length - 4)} categoriesUrl={categoriesUrl.substring(1, categoriesUrl.length - 4)} timezone={settings.timezone} + autoMarking={settings.autoMarking} /> , page From 36a3b54b7be3109c78a9bc70147011cc5ecc3923 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Fri, 30 Oct 2020 21:15:55 +0100 Subject: [PATCH 5/7] Fix js detail urls --- src/newsreader/js/pages/homepage/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/newsreader/js/pages/homepage/index.js b/src/newsreader/js/pages/homepage/index.js index 645592d..b934ad3 100644 --- a/src/newsreader/js/pages/homepage/index.js +++ b/src/newsreader/js/pages/homepage/index.js @@ -17,10 +17,10 @@ if (page) { ReactDOM.render( From 66c8ab1a8c5c6579789e13d66a2a9697abd31145 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Fri, 30 Oct 2020 21:16:47 +0100 Subject: [PATCH 6/7] Show error messages above post modal --- src/newsreader/js/pages/homepage/App.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/newsreader/js/pages/homepage/App.js b/src/newsreader/js/pages/homepage/App.js index da76ef7..0b2aedb 100644 --- a/src/newsreader/js/pages/homepage/App.js +++ b/src/newsreader/js/pages/homepage/App.js @@ -26,10 +26,6 @@ class App extends React.Component { timezone={this.props.timezone} /> - {this.props.error && ( - - )} - {!isEqual(this.props.post, {}) && ( )} + + {this.props.error && ( + + )} ); } From f0a0ad776dedfb90c35096a98a15238bef78fd2f Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Fri, 30 Oct 2020 22:55:21 +0100 Subject: [PATCH 7/7] Update actions/reducers --- src/newsreader/js/pages/homepage/actions/posts.js | 5 +++++ .../js/pages/homepage/components/PostModal.js | 3 +-- src/newsreader/js/pages/homepage/reducers/posts.js | 8 +++++++- src/newsreader/js/tests/homepage/actions/post.test.js | 10 +++++++++- src/newsreader/js/tests/homepage/reducers/post.test.js | 4 +++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/newsreader/js/pages/homepage/actions/posts.js b/src/newsreader/js/pages/homepage/actions/posts.js index b7ad5cb..f04f3e1 100644 --- a/src/newsreader/js/pages/homepage/actions/posts.js +++ b/src/newsreader/js/pages/homepage/actions/posts.js @@ -9,6 +9,7 @@ export const RECEIVE_POST = 'RECEIVE_POST'; export const REQUEST_POSTS = 'REQUEST_POSTS'; export const MARK_POST_READ = 'MARK_POST_READ'; +export const MARKING_POST = 'MARKING_POST'; export const requestPosts = () => ({ type: REQUEST_POSTS }); @@ -30,10 +31,14 @@ export const postRead = (post, section) => ({ section, }); +export const markingPostRead = () => ({ type: MARKING_POST }); + export const markPostRead = (post, token) => { return (dispatch, getState) => { const { selected } = getState(); + dispatch(markingPostRead()); + const url = `/api/posts/${post.id}/`; const options = { method: 'PATCH', diff --git a/src/newsreader/js/pages/homepage/components/PostModal.js b/src/newsreader/js/pages/homepage/components/PostModal.js index 91017dd..ab508ae 100644 --- a/src/newsreader/js/pages/homepage/components/PostModal.js +++ b/src/newsreader/js/pages/homepage/components/PostModal.js @@ -130,7 +130,6 @@ const mapDispatchToProps = dispatch => ({ markPostRead: (post, token) => dispatch(markPostRead(post, token)), }); -// TODO use different boolean for saving mark post as read state -const mapStateToProps = state => ({ isMarkingPost: state.posts.isFetching }); +const mapStateToProps = state => ({ isMarkingPost: state.posts.isMarking }); export default connect(mapStateToProps, mapDispatchToProps)(PostModal); diff --git a/src/newsreader/js/pages/homepage/reducers/posts.js b/src/newsreader/js/pages/homepage/reducers/posts.js index 220c59b..608deb2 100644 --- a/src/newsreader/js/pages/homepage/reducers/posts.js +++ b/src/newsreader/js/pages/homepage/reducers/posts.js @@ -5,6 +5,8 @@ import { CATEGORY_TYPE, RULE_TYPE } from '../constants.js'; import { SELECT_POST, + MARKING_POST, + MARK_POST_READ, RECEIVE_POST, RECEIVE_POSTS, REQUEST_POSTS, @@ -13,7 +15,7 @@ import { SELECT_CATEGORY } from '../actions/categories.js'; import { SELECT_RULE } from '../actions/rules.js'; import { MARK_SECTION_READ } from '../actions/selected.js'; -const defaultState = { items: {}, isFetching: false }; +const defaultState = { items: {}, isFetching: false, isMarking: false }; export const posts = (state = { ...defaultState }, action) => { switch (action.type) { @@ -62,6 +64,10 @@ export const posts = (state = { ...defaultState }, action) => { ...updatedPosts, }, }; + case MARKING_POST: + return { ...state, isMarking: true }; + case MARK_POST_READ: + return { ...state, isMarking: false }; default: return state; } diff --git a/src/newsreader/js/tests/homepage/actions/post.test.js b/src/newsreader/js/tests/homepage/actions/post.test.js index e8f84de..ce2ffdc 100644 --- a/src/newsreader/js/tests/homepage/actions/post.test.js +++ b/src/newsreader/js/tests/homepage/actions/post.test.js @@ -14,12 +14,18 @@ describe('post actions', () => { fetchMock.restore(); }); - it('should create an action request posts', () => { + it('should create an action to request posts', () => { const expectedAction = { type: actions.REQUEST_POSTS }; expect(actions.requestPosts()).toEqual(expectedAction); }); + it('should create an action to mark a post read', () => { + const expectedAction = { type: actions.MARKING_POST }; + + expect(actions.markingPostRead()).toEqual(expectedAction); + }); + it('should create an action receive a post', () => { const post = { id: 2067, @@ -147,6 +153,7 @@ describe('post actions', () => { }); const expectedActions = [ + { type: actions.MARKING_POST }, { type: actions.RECEIVE_POST, post: { ...post, read: true }, @@ -362,6 +369,7 @@ describe('post actions', () => { }); const expectedActions = [ + { type: actions.MARKING_POST }, { type: actions.RECEIVE_POST, post: {} }, { type: errorActions.RECEIVE_API_ERROR, error: TypeError(errorMessage) }, ]; diff --git a/src/newsreader/js/tests/homepage/reducers/post.test.js b/src/newsreader/js/tests/homepage/reducers/post.test.js index ef4234a..6fe728f 100644 --- a/src/newsreader/js/tests/homepage/reducers/post.test.js +++ b/src/newsreader/js/tests/homepage/reducers/post.test.js @@ -12,7 +12,7 @@ describe('post actions', () => { it('should return state after requesting posts', () => { const action = { type: actions.REQUEST_POSTS }; - const expectedState = { ...defaultState, isFetching: true }; + const expectedState = { ...defaultState, isFetching: true, isMarking: false }; expect(reducer(undefined, action)).toEqual(expectedState); }); @@ -40,6 +40,7 @@ describe('post actions', () => { const expectedState = { ...defaultState, isFetching: false, + isMarking: false, items: { [post.id]: post }, }; @@ -85,6 +86,7 @@ describe('post actions', () => { const expectedState = { ...defaultState, isFetching: false, + isMarking: false, items: expectedPosts, };