From fd4db68f7b626b0ba09d494a51f18976b9106985 Mon Sep 17 00:00:00 2001 From: Samuel Clay Date: Tue, 15 Nov 2016 18:18:31 -0800 Subject: [PATCH] Notifications now save their filter and type. Also updates the UI. --- apps/notifications/models.py | 17 ++- apps/notifications/urls.py | 3 +- apps/notifications/views.py | 36 ++++- media/css/reader.css | 60 +++++++-- media/js/newsblur/common/assetmodel.js | 12 ++ media/js/newsblur/models/feeds.js | 2 + .../newsblur/reader/reader_notifications.js | 27 ++-- .../newsblur/views/feed_notification_view.js | 125 +++++++++++++----- urls.py | 2 +- 9 files changed, 221 insertions(+), 63 deletions(-) diff --git a/apps/notifications/models.py b/apps/notifications/models.py index 5f19f97c1..20e102f7b 100644 --- a/apps/notifications/models.py +++ b/apps/notifications/models.py @@ -22,6 +22,7 @@ class MUserFeedNotification(mongo.Document): user_id = mongo.IntField() feed_id = mongo.IntField() frequency = mongoengine_fields.IntEnumField(NotificationFrequency) + is_focus = mongo.BooleanField() last_notification_date = mongo.DateTimeField(default=datetime.datetime.now) is_email = mongo.BooleanField() is_web = mongo.BooleanField() @@ -58,12 +59,14 @@ class MUserFeedNotification(mongo.Document): for feed in notifications: notifications_by_feed[feed.feed_id] = { - 'notifications': [], - 'notification_freq': feed.frequency + 'notification_types': [], + 'notification_filter': "focus" if feed.is_focus else "unread", } - if feed.is_email: notifications_by_feed[feed.feed_id]['notifications'].append('email') - if feed.is_web: notifications_by_feed[feed.feed_id]['notifications'].append('web') - if feed.is_ios: notifications_by_feed[feed.feed_id]['notifications'].append('ios') - if feed.is_android: notifications_by_feed[feed.feed_id]['notifications'].append('android') + if feed.is_email: notifications_by_feed[feed.feed_id]['notification_types'].append('email') + if feed.is_web: notifications_by_feed[feed.feed_id]['notification_types'].append('web') + if feed.is_ios: notifications_by_feed[feed.feed_id]['notification_types'].append('ios') + if feed.is_android: notifications_by_feed[feed.feed_id]['notification_types'].append('android') - return notifications_by_feed \ No newline at end of file + return notifications_by_feed + + \ No newline at end of file diff --git a/apps/notifications/urls.py b/apps/notifications/urls.py index f2108d4f3..223fcaed8 100644 --- a/apps/notifications/urls.py +++ b/apps/notifications/urls.py @@ -3,5 +3,6 @@ from apps.notifications import views from oauth2_provider import views as op_views urlpatterns = patterns('', - url(r'^/?$', views.notifications_by_feed, name='notifications-by-feed'), + url(r'^$', views.notifications_by_feed, name='notifications-by-feed'), + url(r'^feed/?$', views.set_notifications_for_feed, name='set-notifications-for-feed'), ) \ No newline at end of file diff --git a/apps/notifications/views.py b/apps/notifications/views.py index 0a74285e7..737c9acb8 100644 --- a/apps/notifications/views.py +++ b/apps/notifications/views.py @@ -10,4 +10,38 @@ def notifications_by_feed(request): user = get_user(request) notifications_by_feed = MUserFeedNotification.feeds_for_user(user.pk) - return notifications_by_feed \ No newline at end of file + return notifications_by_feed + +@ajax_login_required +@json.json_view +def set_notifications_for_feed(request): + user = get_user(request) + feed_id = request.POST['feed_id'] + notification_types = request.POST.getlist('notification_types') + notification_filter = request.POST.get('notification_filter') + + try: + notification = MUserFeedNotification.objects.get(user_id=user.pk, feed_id=feed_id) + except MUserFeedNotification.DoesNotExist: + params = { + "user_id": user.pk, + "feed_id": feed_id, + } + notification = MUserFeedNotification.objects.create(**params) + + notification.is_focus = bool(notification_filter == "focus") + notification.is_email = bool('email' in notification_types) + notification.is_ios = bool('ios' in notification_types) + notification.is_android = bool('android' in notification_types) + notification.is_web = bool('web' in notification_types) + notification.save() + + if (not notification.is_email and + not notification.is_ios and + not notification.is_android and + not notification.is_web): + notification.delete() + + notifications_by_feed = MUserFeedNotification.feeds_for_user(user.pk) + + return {"notifications_by_feed": notifications_by_feed} diff --git a/media/css/reader.css b/media/css/reader.css index cde9f53dc..38f7e57b9 100644 --- a/media/css/reader.css +++ b/media/css/reader.css @@ -8821,6 +8821,10 @@ form.opml_import_form input { /* = Notifications Modal = */ /* ======================= */ +.NB-modal-notifications .NB-modal-title .NB-icon { + background: transparent url('/media/embed/icons/circular/menu_icn_notifications_128.png'); + background-size: 28px; +} .NB-modal.NB-modal-notifications .NB-fieldset { border-bottom: none; width: 100%; @@ -8836,21 +8840,54 @@ form.opml_import_form input { float: left; overflow: hidden; } +.NB-modal-notifications .NB-modal-section-site { + margin: 12px 0 0; +} + .NB-feed-notification { width: 100%; + overflow: hidden; + position: relative; + padding: 10px 0; + border-bottom: 1px solid #F0F0F0; } -.NB-feed-notification .NB-feed-notification-filter { - float: right; - margin: 0 12px; +.NB-feed-notification:last-child { + border-bottom: none; } + .NB-feed-notification .NB-feed-title { font-size: 12px; + padding: 0 260px 0 24px; } .NB-feed-notification .NB-feed-icon { width: 16px; height: 16px; + position: absolute; + top: 10px; + left: 0; +} +.NB-feed-notification .NB-feed-frequency-icon { float: left; - margin: 0 7px 0 0; + clear: left; + width: 16px; + height: 16px; + margin: 4px 7px 0 0; +} +.NB-feed-notification .NB-feed-frequency { + float: left; + color: #A0A0A0; + font-size: 10px; + margin: 2px 0 0 0; +} + +.NB-feed-notification .NB-feed-notification-controls { + position: relative; + top: 0; + right: 0; +} +.NB-feed-notification .NB-feed-notification-filter { + float: right; + margin: 0 12px; } .NB-feed-notification .NB-feed-notification-filter .NB-unread-icon, .NB-feed-notification .NB-feed-notification-filter .NB-focus-icon { @@ -8867,21 +8904,24 @@ form.opml_import_form input { background-size: 8px; } .NB-feed-notification .segmented-control { - margin-top: -2px; - margin-bottom: 2px; + margin: 0 0 4px 0; } .NB-feed-notification .segmented-control li { padding: 2px 6px; font-size: 10px; + min-width: 36px; } -.NB-feed-notification .NB-feed-notifications { +.NB-feed-notification .segmented-control li:not(.NB-active) { + color: #A0A0A0; +} +.NB-feed-notification .NB-feed-notification-types { float: right; + clear: right; } -.NB-modal-notifications .NB-modal-section-site { - margin: 12px 0 0; +.NB-feed-notification .NB-feed-notification-filter-focus { + width: 82px; } - /* ===================== */ /* = Feedchooser Modal = */ /* ===================== */ diff --git a/media/js/newsblur/common/assetmodel.js b/media/js/newsblur/common/assetmodel.js index 102103be4..43adf06d4 100644 --- a/media/js/newsblur/common/assetmodel.js +++ b/media/js/newsblur/common/assetmodel.js @@ -1482,6 +1482,18 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({ } }, + set_notifications_for_feed: function(feed, callback) { + if (NEWSBLUR.Globals.is_authenticated) { + this.make_request('/notifications/feed/', { + 'feed_id': feed.id, + 'notification_types': feed.get('notification_types'), + 'notification_filter': feed.get('notification_filter') + }, callback); + } else { + if ($.isFunction(callback)) callback(); + } + }, + send_story_email: function(data, callback, error_callback) { if (NEWSBLUR.Globals.is_authenticated) { this.make_request('/reader/send_story_email', data, callback, error_callback, {'timeout': 6000}); diff --git a/media/js/newsblur/models/feeds.js b/media/js/newsblur/models/feeds.js index c8eb84381..7ffc1f093 100644 --- a/media/js/newsblur/models/feeds.js +++ b/media/js/newsblur/models/feeds.js @@ -271,6 +271,8 @@ NEWSBLUR.Collections.Feeds = Backbone.Collection.extend({ this.bind('change', this.detect_active_feed); }, + comparator: 'feed_title', + // =========== // = Actions = // =========== diff --git a/media/js/newsblur/reader/reader_notifications.js b/media/js/newsblur/reader/reader_notifications.js index 5e7d8f222..7c009ca12 100644 --- a/media/js/newsblur/reader/reader_notifications.js +++ b/media/js/newsblur/reader/reader_notifications.js @@ -34,9 +34,13 @@ _.extend(NEWSBLUR.ReaderNotifications.prototype, { var frequency = this.feed.get('notification_frequency'); var notifications = this.feed.get('notifications'); - if (this.feed) { - NEWSBLUR.Modal.prototype.initialize_feed.call(this, feed_id); - } + NEWSBLUR.Modal.prototype.initialize_feed.call(this, feed_id); + + var $site = $(".NB-modal-section-site", this.$modal); + $site.html(this.make_feed_notification(this.feed)); + + var $all = $(".NB-modal-section-all", this.$modal); + $all.html(this.make_feed_notifications()); this.resize(); }, @@ -68,7 +72,12 @@ _.extend(NEWSBLUR.ReaderNotifications.prototype, { this.make_feed_chooser() ])), $.make('div', { className: 'NB-modal-loading' }), - $.make('h2', { className: 'NB-modal-title' }, 'Notifications'), + $.make('h2', { className: 'NB-modal-title' }, [ + $.make('div', { className: 'NB-modal-loading' }), + $.make('div', { className: 'NB-icon' }), + 'Notificatons', + $.make('div', { className: 'NB-icon-dropdown' }) + ]), (this.feed && $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ $.make('fieldset', [ $.make('legend', 'Site Notifications'), @@ -80,7 +89,7 @@ _.extend(NEWSBLUR.ReaderNotifications.prototype, { $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ $.make('fieldset', [ $.make('legend', 'All Notifications'), - $.make('div', { className: 'NB-modal-section'}, [ + $.make('div', { className: 'NB-modal-section NB-modal-section-all'}, [ this.make_feed_notifications() ]) ]) @@ -104,13 +113,15 @@ _.extend(NEWSBLUR.ReaderNotifications.prototype, { }, make_feed_notifications: function() { - var notifications = this.model.get_feeds().filter(function(feed) { - return feed.get('notifications'); + var site_feed_id = this.model.id; + var notifications = this.model.get_feeds().select(function(feed) { + return feed.get('notification_types') && feed.id != site_feed_id; }); var $feeds = []; + notifications.sort(function(a, b) { return a.get('feed_title') < b.get('feed_title'); }); for (var feed in notifications) { - $feeds.push(this.make_feed_notification(feed)); + $feeds.push(this.make_feed_notification(notifications[feed])); } return $feeds; diff --git a/media/js/newsblur/views/feed_notification_view.js b/media/js/newsblur/views/feed_notification_view.js index 6b04eed0f..8eb2bd93e 100644 --- a/media/js/newsblur/views/feed_notification_view.js +++ b/media/js/newsblur/views/feed_notification_view.js @@ -1,62 +1,72 @@ NEWSBLUR.Views.FeedNotificationView = Backbone.View.extend({ events: { - "click .NB-feed-notifications-email" : "toggle_email", - "click .NB-feed-notifications-ios" : "toggle_ios", - "click .NB-feed-notifications-android" : "toggle_android", - "click .NB-feed-notifications-web" : "toggle_web" + "click .NB-feed-notification-filter-unread": "toggle_unread", + "click .NB-feed-notification-filter-focus" : "toggle_focus", + "click .NB-feed-notification-email" : "toggle_email", + "click .NB-feed-notification-ios" : "toggle_ios", + "click .NB-feed-notification-android" : "toggle_android", + "click .NB-feed-notification-web" : "toggle_web" }, - initialize: function() { + initialize: function(m) { + console.log(['feed notificaiton', this.model, m]); }, render: function() { var feed = this.model; var $feed = $(_.template('
\ - \ - \ +
\ +
    \ +
  • \ +
    \ + Unread stories\ +
  • \ +
  • \ +
    \ + Focus\ +
  • \ +
\ +
    \ +
  • Email
  • \ +
  • Web
  • \ +
  • iOS
  • \ +
  • Android
  • \ +
\ +
\ \
<%= feed.get("feed_title") %>
\ +
\ +
<%= frequency %>
\
\ ', { feed : feed, selected : this.options.selected, - frequency_count : this.frequency_count() + frequency : feed && this.frequency(feed.get('average_stories_per_month')), + frequency_count : this.frequency_count(), + is_email : _.contains(feed.get('notification_types'), 'email'), + is_ios : _.contains(feed.get('notification_types'), 'ios'), + is_android : _.contains(feed.get('notification_types'), 'android'), + is_web : _.contains(feed.get('notification_types'), 'web'), + is_focus : feed.get('notification_filter') == 'focus' })); - //
\ - // \ - //
\ - //
\ - // <%= frequency_count %> a day\ - //
\ - this.$el.replaceWith($feed); this.setElement($feed); return this; }, + frequency: function(count) { + if (count == 0) { + return "No stories published last month"; + } else if (count < 30) { + return Inflector.pluralize("story", count, true) + " per month"; + } else if (count >= 30) { + return Inflector.pluralize("story", Math.round(count / 30.0), true) + " per day"; + } + }, + frequency_count: function() { var freq = this.model.get('notification_frequency'); var story_count = this.model.get('stories_per_month') / 30.0; @@ -76,7 +86,52 @@ NEWSBLUR.Views.FeedNotificationView = Backbone.View.extend({ // ========== toggle_email: function() { + this.toggle_type('email'); + }, + + toggle_ios: function() { + this.toggle_type('ios'); + }, + + toggle_android: function() { + this.toggle_type('android'); + }, + + toggle_web: function() { + this.toggle_type('web'); + }, + + toggle_type: function(type) { + var notification_types = this.model.get('notification_types') || []; + var is_type = _.contains(notification_types, type); + if (is_type) { + notification_types.splice(notification_types.indexOf(type), 1); + } else { + notification_types.push(type); + } + this.model.set('notification_types', notification_types); + this.save(); + }, + + toggle_focus: function() { + this.model.set('notification_filter', 'focus'); + + this.save(); + }, + + toggle_unread: function() { + this.model.set('notification_filter', 'unread'); + + this.save(); + }, + + save: function() { + NEWSBLUR.assets.set_notifications_for_feed(this.model, function() { + + }); + + this.render(); } }); \ No newline at end of file diff --git a/urls.py b/urls.py index 526b87212..cebe95e12 100644 --- a/urls.py +++ b/urls.py @@ -30,7 +30,7 @@ urlpatterns = patterns('', (r'^import/', include('apps.feed_import.urls')), (r'^api/', include('apps.api.urls')), (r'^recommendations/', include('apps.recommendations.urls')), - (r'^notifications/?', include('apps.notifications.urls')), + (r'^notifications/?', include('apps.notifications.urls')), (r'^statistics/', include('apps.statistics.urls')), (r'^social/', include('apps.social.urls')), (r'^oauth/', include('apps.oauth.urls')),