diff --git a/apps/reader/models.py b/apps/reader/models.py index 65b673930..e41c10b3d 100644 --- a/apps/reader/models.py +++ b/apps/reader/models.py @@ -404,31 +404,37 @@ class UserSubscription(models.Model): r.srem(read_stories_key, *stale_story_hashes) r.srem("RS:%s" % self.feed_id, *stale_story_hashes) - def mark_feed_read(self): + def mark_feed_read(self, cutoff_date=None): if (self.unread_count_negative == 0 and self.unread_count_neutral == 0 and self.unread_count_positive == 0 and not self.needs_unread_recalc): return - now = datetime.datetime.utcnow() - + recount = True # Use the latest story to get last read time. - latest_story = MStory.objects(story_feed_id=self.feed.pk).order_by('-story_date').only('story_date').limit(1) - if latest_story and len(latest_story) >= 1: - latest_story_date = latest_story[0]['story_date']\ - + datetime.timedelta(seconds=1) + if cutoff_date: + cutoff_date = cutoff_date + datetime.timedelta(seconds=1) else: - latest_story_date = now + latest_story = MStory.objects(story_feed_id=self.feed.pk).order_by('-story_date').only('story_date').limit(1) + if latest_story and len(latest_story) >= 1: + cutoff_date = (latest_story[0]['story_date'] + + datetime.timedelta(seconds=1)) + else: + cutoff_date = datetime.datetime.utcnow() + recount = False - self.last_read_date = latest_story_date - self.mark_read_date = latest_story_date - self.unread_count_negative = 0 - self.unread_count_positive = 0 - self.unread_count_neutral = 0 - self.unread_count_updated = now - self.oldest_unread_story_date = now - self.needs_unread_recalc = False + self.last_read_date = cutoff_date + self.mark_read_date = cutoff_date + self.oldest_unread_story_date = cutoff_date + if not recount: + self.unread_count_negative = 0 + self.unread_count_positive = 0 + self.unread_count_neutral = 0 + self.unread_count_updated = cutoff_date + self.needs_unread_recalc = False + else: + self.needs_unread_recalc = True self.save() diff --git a/apps/reader/views.py b/apps/reader/views.py index 4326dd5f4..90b248f1a 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -1271,9 +1271,11 @@ def mark_story_as_unread(request): def mark_feed_as_read(request): r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL) feed_ids = request.REQUEST.getlist('feed_id') + cutoff_timestamp = int(request.REQUEST.get('cutoff_timestamp', 0)) multiple = len(feed_ids) > 1 code = 1 errors = [] + cutoff_date = datetime.datetime.fromtimestamp(cutoff_timestamp) if cutoff_timestamp else None for feed_id in feed_ids: if 'social:' in feed_id: @@ -1300,7 +1302,7 @@ def mark_feed_as_read(request): continue try: - marked_read = sub.mark_feed_read() + marked_read = sub.mark_feed_read(cutoff_date=cutoff_date) if marked_read: r.publish(request.user.username, 'feed:%s' % feed_id) except IntegrityError, e: diff --git a/media/js/newsblur/common/assetmodel.js b/media/js/newsblur/common/assetmodel.js index 8c9dd97bd..5bf2216c9 100644 --- a/media/js/newsblur/common/assetmodel.js +++ b/media/js/newsblur/common/assetmodel.js @@ -245,18 +245,21 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({ }, callback); }, - mark_feed_as_read: function(feed_id, callback) { + mark_feed_as_read: function(feed_id, cutoff_timestamp, mark_active, callback) { var self = this; var feed_ids = _.isArray(feed_id) ? _.select(feed_id, function(f) { return f; }) : [feed_id]; this.make_request('/reader/mark_feed_as_read', { - feed_id: feed_ids + feed_id: feed_ids, + cutoff_timestamp: cutoff_timestamp }, callback); - if (feed_id == NEWSBLUR.reader.active_feed) { + if (mark_active) { this.stories.each(function(story) { + if (cutoff_timestamp && + parseInt(story.get('story_timestamp'), 10) > cutoff_timestamp) return; story.set('read_status', true); }); } diff --git a/media/js/newsblur/reader/reader.js b/media/js/newsblur/reader/reader.js index ed945d11f..7d357ee9d 100644 --- a/media/js/newsblur/reader/reader.js +++ b/media/js/newsblur/reader/reader.js @@ -1922,44 +1922,33 @@ } }, - mark_feed_as_read: function(feed_id) { + mark_feed_as_read: function(feed_id, days_back) { feed_id = feed_id || this.active_feed; - - this.model.mark_feed_as_read([feed_id]); - this.mark_feed_as_read_update_counts(feed_id); - - if (feed_id == this.active_feed) { - this.model.stories.each(function(story) { - story.set('read_status', true); - }); + var cutoff_timestamp = NEWSBLUR.utils.days_back_to_timestamp(days_back); + if (!days_back && this.model.stories.length && + this.model.stories.first().get('story_feed_id') == feed_id && + NEWSBLUR.assets.view_setting(feed_id, 'order') == 'newest') { + cutoff_timestamp = this.model.stories.first().get('story_timestamp'); } + + this.model.mark_feed_as_read([feed_id], cutoff_timestamp, feed_id == this.active_feed, _.bind(function() { + this.feeds_unread_count(feed_id); + }, this)); }, - mark_folder_as_read: function(folder) { + mark_folder_as_read: function(folder, days_back) { var folder = folder || this.active_folder; var feeds = folder.feed_ids_in_folder(); - - this.model.mark_feed_as_read(feeds); - _.each(feeds, _.bind(function(feed_id) { - this.mark_feed_as_read_update_counts(feed_id); - }, this)); + var cutoff_timestamp = NEWSBLUR.utils.days_back_to_timestamp(days_back); + if (!days_back && this.model.stories.length && + _.contains(feeds, this.model.stories.first().get('story_feed_id')) && + NEWSBLUR.assets.view_setting(folder.id, 'order') == 'newest') { + cutoff_timestamp = this.model.stories.first().get('story_timestamp'); + } - if (folder == this.active_folder) { - this.model.stories.each(function(story) { - story.set('read_status', true); - }); - } - }, - - mark_feed_as_read_update_counts: function(feed_id) { - if (feed_id) { - var feed = this.model.get_feed(feed_id); - if (!feed) return; - - feed.set('ps', 0); - feed.set('nt', 0); - feed.set('ng', 0); - } + this.model.mark_feed_as_read(feeds, cutoff_timestamp, folder == this.active_folder, _.bind(function() { + this.feeds_unread_count(feeds); + }, this)); }, open_story_trainer: function(story_id, feed_id, options) { @@ -4150,6 +4139,12 @@ }, this), Math.random() * delay); }, + feeds_unread_count: function(feed_ids, options) { + options = options || {}; + + this.model.feed_unread_count(feed_ids, options.callback); + }, + update_interactions_count: function() { this.model.interactions_count(function(data) { NEWSBLUR.app.sidebar_header.update_interactions_count(data.interactions_count); diff --git a/media/js/newsblur/reader/reader_utils.js b/media/js/newsblur/reader/reader_utils.js index 57be92abf..d31b11d9c 100644 --- a/media/js/newsblur/reader/reader_utils.js +++ b/media/js/newsblur/reader/reader_utils.js @@ -210,6 +210,13 @@ NEWSBLUR.utils = { } return interval; + }, + + days_back_to_timestamp: function(days_back) { + days_back = days_back || 0; + var now = Math.round((new Date()).getTime() / 1000); + + return now - (days_back * 60*60*24); } diff --git a/media/js/newsblur/views/feed_title_view.js b/media/js/newsblur/views/feed_title_view.js index df24e3741..d67adb5a5 100644 --- a/media/js/newsblur/views/feed_title_view.js +++ b/media/js/newsblur/views/feed_title_view.js @@ -11,6 +11,7 @@ NEWSBLUR.Views.FeedTitleView = Backbone.View.extend({ "dblclick .feed_counts" : "mark_feed_as_read", "dblclick" : "open_feed_link", "click .NB-feedbar-mark-feed-read" : "mark_feed_as_read", + "click .NB-feedbar-mark-feed-read-time" : "mark_feed_as_read_days", "click .NB-feedbar-mark-feed-read-expand" : "expand_mark_read", "click .NB-feedbar-train-feed" : "open_trainer", "click .NB-feedbar-statistics" : "open_statistics", @@ -69,10 +70,10 @@ NEWSBLUR.Views.FeedTitleView = Backbone.View.extend({ <% if (type == "story") { %>\
\