mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Adding oldest/newer order to feeds and river. Also adding unread/all read filter to both as well. Including stubbed segmented control for switching between modes on a per-feed basis. Still needs global preference and glue to hook them up.
This commit is contained in:
parent
be3c34aa87
commit
a4414eebde
7 changed files with 151 additions and 60 deletions
|
@ -84,9 +84,10 @@ class UserSubscription(models.Model):
|
|||
break
|
||||
else:
|
||||
self.delete()
|
||||
|
||||
def unread_stories(self, withscores=False):
|
||||
|
||||
def get_stories(self, offset=0, limit=6, order='newest', read_filter='all', withscores=False):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_POOL)
|
||||
ignore_user_stories = False
|
||||
|
||||
stories_key = 'F:%s' % (self.feed_id)
|
||||
read_stories_key = 'RS:%s:%s' % (self.user_id, self.feed_id)
|
||||
|
@ -94,48 +95,64 @@ class UserSubscription(models.Model):
|
|||
|
||||
if not r.exists(stories_key):
|
||||
return []
|
||||
elif not r.exists(read_stories_key):
|
||||
elif read_filter != 'unread' or not r.exists(read_stories_key):
|
||||
ignore_user_stories = True
|
||||
unread_stories_key = stories_key
|
||||
else:
|
||||
r.sdiffstore(unread_stories_key, stories_key, read_stories_key)
|
||||
|
||||
sorted_stories_key = 'zF:%s' % (self.feed_id)
|
||||
unread_ranked_stories_key = 'zU:%s:%s' % (self.user_id, self.feed_id)
|
||||
r.zinterstore(unread_ranked_stories_key, [sorted_stories_key, unread_stories_key])
|
||||
|
||||
current_time = int(time.time())
|
||||
mark_read_time = time.mktime(self.mark_read_date.timetuple())
|
||||
story_guids = r.zrevrangebyscore(unread_ranked_stories_key, current_time,
|
||||
mark_read_time, withscores=withscores)
|
||||
if order == 'oldest':
|
||||
byscorefunc = r.zrangebyscore
|
||||
min_score = mark_read_time
|
||||
max_score = current_time
|
||||
else:
|
||||
byscorefunc = r.zrevrangebyscore
|
||||
min_score = current_time
|
||||
max_score = mark_read_time
|
||||
story_ids = byscorefunc(unread_ranked_stories_key, min_score,
|
||||
max_score, start=offset, num=limit,
|
||||
withscores=withscores)
|
||||
|
||||
r.delete(unread_ranked_stories_key)
|
||||
if r.exists(read_stories_key):
|
||||
r.expire(unread_ranked_stories_key, 24*60*60)
|
||||
if not ignore_user_stories:
|
||||
r.delete(unread_stories_key)
|
||||
|
||||
return story_guids
|
||||
return story_ids
|
||||
|
||||
@classmethod
|
||||
def unread_feed_stories(cls, user_id, feed_ids, offset=0, limit=6):
|
||||
def feed_stories(cls, user_id, feed_ids, offset=0, limit=6, order='newest', read_filter='all'):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STORY_POOL)
|
||||
|
||||
|
||||
if order == 'oldest':
|
||||
range_func = r.zrange
|
||||
else:
|
||||
range_func = r.zrevrange
|
||||
|
||||
if not isinstance(feed_ids, list):
|
||||
feed_ids = [feed_ids]
|
||||
|
||||
unread_ranked_stories_keys = 'zU:%s' % (user_id)
|
||||
if offset and r.exists(unread_ranked_stories_keys):
|
||||
story_guids = r.zrevrange(unread_ranked_stories_keys, offset, limit)
|
||||
story_guids = range_func(unread_ranked_stories_keys, offset, limit)
|
||||
return story_guids
|
||||
else:
|
||||
r.delete(unread_ranked_stories_keys)
|
||||
|
||||
for feed_id in feed_ids:
|
||||
us = cls.objects.get(user=user_id, feed=feed_id)
|
||||
story_guids = us.unread_stories(withscores=True)
|
||||
story_guids = us.get_stories(offset=offset, limit=limit,
|
||||
order=order, read_filter=read_filter,
|
||||
withscores=True)
|
||||
|
||||
if story_guids:
|
||||
r.zadd(unread_ranked_stories_keys, **dict(story_guids))
|
||||
|
||||
story_guids = r.zrevrange(unread_ranked_stories_keys, offset, limit)
|
||||
story_guids = range_func(unread_ranked_stories_keys, offset, limit)
|
||||
r.expire(unread_ranked_stories_keys, 24*60*60)
|
||||
|
||||
return story_guids
|
||||
|
@ -257,8 +274,10 @@ class UserSubscription(models.Model):
|
|||
self.unread_count_updated = now
|
||||
self.oldest_unread_story_date = now
|
||||
self.needs_unread_recalc = False
|
||||
|
||||
MUserStory.delete_old_stories(self.user_id, self.feed_id)
|
||||
|
||||
# No longer removing old user read stories, since they're needed for social,
|
||||
# and they get cleaned up automatically when new stories come in.
|
||||
# MUserStory.delete_old_stories(self.user_id, self.feed_id)
|
||||
|
||||
self.save()
|
||||
|
||||
|
|
|
@ -394,6 +394,8 @@ def load_single_feed(request, feed_id):
|
|||
offset = int(request.REQUEST.get('offset', 0))
|
||||
limit = int(request.REQUEST.get('limit', 6))
|
||||
page = int(request.REQUEST.get('page', 1))
|
||||
order = request.REQUEST.get('order', 'newest')
|
||||
read_filter = request.REQUEST.get('read_filter', 'all')
|
||||
dupe_feed_id = None
|
||||
userstories_db = None
|
||||
user_profiles = {}
|
||||
|
@ -401,19 +403,21 @@ def load_single_feed(request, feed_id):
|
|||
|
||||
if page: offset = limit * (page-1)
|
||||
if not feed_id: raise Http404
|
||||
|
||||
try:
|
||||
feed = Feed.objects.get(id=feed_id)
|
||||
except Feed.DoesNotExist:
|
||||
feed_address = request.REQUEST.get('feed_address')
|
||||
dupe_feed = DuplicateFeed.objects.filter(duplicate_address=feed_address)
|
||||
if dupe_feed:
|
||||
feed = dupe_feed[0].feed
|
||||
dupe_feed_id = feed_id
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
stories = feed.get_stories(offset, limit)
|
||||
|
||||
feed_address = request.REQUEST.get('feed_address')
|
||||
feed = Feed.get_by_id(feed_id, feed_address=feed_address)
|
||||
if not feed:
|
||||
raise Http404
|
||||
|
||||
usersub = UserSubscription.objects.get(user=user, feed=feed)
|
||||
|
||||
if read_filter == 'unread' or order == 'oldest':
|
||||
story_ids = usersub.get_stories(order=order, read_filter=read_filter, offset=offset, limit=limit)
|
||||
story_date_order = "%sstory_date" % ('' if order == 'oldest' else '-')
|
||||
mstories = MStory.objects(id__in=story_ids).order_by(story_date_order)
|
||||
stories = Feed.format_stories(mstories)
|
||||
else:
|
||||
stories = feed.get_stories(offset, limit)
|
||||
try:
|
||||
stories, user_profiles = MSharedStory.stories_with_comments_and_profiles(stories, user.pk)
|
||||
except redis.ConnectionError:
|
||||
|
@ -433,7 +437,6 @@ def load_single_feed(request, feed_id):
|
|||
|
||||
checkpoint1 = time.time()
|
||||
|
||||
usersub = UserSubscription.objects.get(user=user, feed=feed)
|
||||
userstories = []
|
||||
if usersub and stories:
|
||||
story_ids = [story['id'] for story in stories]
|
||||
|
@ -574,6 +577,8 @@ def load_river_stories(request):
|
|||
feed_ids = [int(feed_id) for feed_id in request.REQUEST.getlist('feeds') if feed_id]
|
||||
original_feed_ids = list(feed_ids)
|
||||
page = int(request.REQUEST.get('page', 1))
|
||||
order = request.REQUEST.get('order', 'newest')
|
||||
read_filter = request.REQUEST.get('read_filter', 'unread')
|
||||
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
|
||||
|
||||
if not feed_ids:
|
||||
|
@ -583,8 +588,10 @@ def load_river_stories(request):
|
|||
offset = (page-1) * limit
|
||||
limit = page * limit - 1
|
||||
|
||||
story_ids = UserSubscription.unread_feed_stories(user.pk, feed_ids, offset=offset, limit=limit)
|
||||
mstories = MStory.objects(id__in=story_ids)
|
||||
story_ids = UserSubscription.feed_stories(user.pk, feed_ids, offset=offset, limit=limit,
|
||||
order=order, read_filter=read_filter)
|
||||
story_date_order = "%sstory_date" % ('' if order == 'oldest' else '-')
|
||||
mstories = MStory.objects(id__in=story_ids).order_by(story_date_order)
|
||||
stories = Feed.format_stories(mstories)
|
||||
found_feed_ids = list(set([story['story_feed_id'] for story in stories]))
|
||||
|
||||
|
|
|
@ -704,18 +704,19 @@ class Feed(models.Model):
|
|||
return feed
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, feed_id):
|
||||
feed = None
|
||||
|
||||
def get_by_id(cls, feed_id, feed_address=None):
|
||||
try:
|
||||
feed = Feed.objects.get(pk=feed_id)
|
||||
return feed
|
||||
except Feed.DoesNotExist:
|
||||
# Feed has been merged after updating. Find the right feed.
|
||||
duplicate_feeds = DuplicateFeed.objects.filter(duplicate_feed_id=feed_id)
|
||||
if duplicate_feeds:
|
||||
feed = duplicate_feeds[0].feed
|
||||
|
||||
return feed
|
||||
return duplicate_feeds[0].feed
|
||||
if feed_address:
|
||||
duplicate_feeds = DuplicateFeed.objects.filter(duplicate_address=feed_address)
|
||||
if duplicate_feeds:
|
||||
return duplicate_feeds[0].feed
|
||||
|
||||
def add_update_stories(self, stories, existing_stories, verbose=False):
|
||||
ret_values = {
|
||||
|
|
|
@ -995,11 +995,17 @@ background: transparent;
|
|||
margin-left: 24px;
|
||||
}
|
||||
|
||||
#story_titles .NB-feedbar .NB-feedbar-settings {
|
||||
background: transparent url("/media/embed/icons/silk/cog.png") no-repeat center center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
padding: 0 24px 0 12px;
|
||||
}
|
||||
#story_titles .NB-feedbar .NB-feedbar-train-feed {
|
||||
background: transparent url("/media/embed/icons/silk/bricks.png") no-repeat center center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
/* display: none;*/
|
||||
cursor: pointer;
|
||||
padding: 0 0 0 38px;
|
||||
}
|
||||
|
@ -1007,7 +1013,6 @@ background: transparent;
|
|||
background: transparent url('/media/embed/icons/silk/chart_curve.png') no-repeat center center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
/* display: none;*/
|
||||
cursor: pointer;
|
||||
margin:3px 0 0 -6px;
|
||||
padding:0 24px 0 6px;
|
||||
|
@ -5307,6 +5312,37 @@ form.opml_import_form input {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.NB-menu-manage .NB-menu-manage-controls {
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
padding: 2px 0 4px;
|
||||
}
|
||||
.NB-menu-manage li.NB-menu-manage-controls:hover {
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
cursor: default;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-controls .segmented-control {
|
||||
display: inline-block;
|
||||
margin: 4px 0 0 36px;
|
||||
height: 14px;
|
||||
width: auto;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-controls .segmented-control:last-child {
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-controls .segmented-control li {
|
||||
clear: none;
|
||||
padding: 1px 12px 0;
|
||||
font-size: 10px;
|
||||
color: #E0E0E0;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-controls .segmented-control li.NB-active {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ==================== */
|
||||
/* = Statistics Modal = */
|
||||
/* ==================== */
|
||||
|
|
|
@ -56,14 +56,20 @@ NEWSBLUR.Models.Feed = Backbone.Model.extend({
|
|||
NEWSBLUR.assets.rename_feed(this.id, new_title);
|
||||
},
|
||||
|
||||
get_view: function($feed) {
|
||||
return _.detect(this.views, function(view) {
|
||||
get_view: function($feed, fallback) {
|
||||
var found_view = _.detect(this.views, function(view) {
|
||||
if ($feed) {
|
||||
return view.el == $feed.get(0);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!found_view && fallback && this.views.length) {
|
||||
found_view = this.views[0];
|
||||
}
|
||||
|
||||
return found_view;
|
||||
},
|
||||
|
||||
is_social: function() {
|
||||
|
|
|
@ -2308,9 +2308,16 @@
|
|||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-menu-manage-title' }, 'Statistics')
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-feed-settings' }, [
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-menu-manage-title' }, 'Site settings')
|
||||
$.make('li', { className: 'NB-menu-separator' }),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-controls' }, [
|
||||
$.make('ul', { className: 'segmented-control NB-menu-manage-feed-order' }, [
|
||||
$.make('li', { className: 'NB-feed-order-oldest' }, 'Oldest'),
|
||||
$.make('li', { className: 'NB-feed-order-newest NB-active' }, 'Newest first')
|
||||
]),
|
||||
$.make('ul', { className: 'segmented-control NB-menu-manage-feed-unreads' }, [
|
||||
$.make('li', { className: 'NB-feed-unreads-all NB-active' }, 'All stories'),
|
||||
$.make('li', { className: 'NB-feed-unreads-only' }, 'Unread only')
|
||||
])
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-separator' }),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-feed-train' }, [
|
||||
|
@ -2319,11 +2326,15 @@
|
|||
$.make('div', { className: 'NB-menu-manage-subtitle' }, 'What you like and dislike.')
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-separator' }),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-feed-recommend' }, [
|
||||
// $.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-feed-recommend' }, [
|
||||
// $.make('div', { className: 'NB-menu-manage-image' }),
|
||||
// $.make('div', { className: 'NB-menu-manage-title' }, 'Recommend this site')
|
||||
// ]),
|
||||
// $.make('li', { className: 'NB-menu-separator' }),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-feed-settings' }, [
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-menu-manage-title' }, 'Recommend this site')
|
||||
$.make('div', { className: 'NB-menu-manage-title' }, 'Site settings')
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-separator' }),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-move NB-menu-manage-feed-move' }, [
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-menu-manage-title' }, 'Move to folder')
|
||||
|
@ -2907,7 +2918,7 @@
|
|||
var $select = $('select', $confirm);
|
||||
if (_.isNumber(feed_id)) {
|
||||
var feed = this.model.get_feed(feed_id);
|
||||
var feed_view = feed.get_view($feed);
|
||||
var feed_view = feed.get_view($feed, true);
|
||||
var in_folder = feed_view.options.folder_title;
|
||||
} else {
|
||||
folder_view = NEWSBLUR.assets.folders.get_view($feed);
|
||||
|
@ -4173,15 +4184,6 @@
|
|||
self.mark_feed_as_read(feed_id, $t);
|
||||
$t.fadeOut(400);
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-feedbar-statistics' }, function($t, $p){
|
||||
self.open_feed_statistics_modal();
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-feedbar-train-feed' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
if (!$('.NB-task-manage').hasClass('NB-disabled')) {
|
||||
self.open_feed_intelligence_modal(1, self.active_feed, !self.flags.social_view);
|
||||
}
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-story-title-indicator' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
self.show_hidden_story_titles();
|
||||
|
@ -4363,6 +4365,10 @@
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-menu-manage-controls' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-menu-manage-rename' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -6,6 +6,9 @@ NEWSBLUR.Views.FeedTitleView = Backbone.View.extend({
|
|||
},
|
||||
|
||||
events: {
|
||||
"click .NB-feedbar-train-feed" : "open_trainer",
|
||||
"click .NB-feedbar-statistics" : "open_statistics",
|
||||
"click .NB-feedbar-settings" : "open_settings",
|
||||
"contextmenu" : "show_manage_menu",
|
||||
"click .NB-feedlist-manage-icon" : "show_manage_menu",
|
||||
"dblclick" : "open_feed_link",
|
||||
|
@ -71,8 +74,7 @@ NEWSBLUR.Views.FeedTitleView = Backbone.View.extend({
|
|||
<% } %>\
|
||||
<%= feed.get("feed_title") %>\
|
||||
<% if (type == "story") { %>\
|
||||
<span class="NB-feedbar-train-feed" title="Train Intelligence"></span>\
|
||||
<span class="NB-feedbar-statistics" title="Statistics"></span>\
|
||||
<span class="NB-feedbar-settings" title="Site settings"></span>\
|
||||
<% } %>\
|
||||
</span>\
|
||||
<% if (type == "story") { %>\
|
||||
|
@ -272,6 +274,20 @@ NEWSBLUR.Views.FeedTitleView = Backbone.View.extend({
|
|||
|
||||
remove_hover_inverse: function() {
|
||||
this.$el.removeClass('NB-hover-inverse');
|
||||
},
|
||||
|
||||
open_trainer: function() {
|
||||
if (!$('.NB-task-manage').hasClass('NB-disabled')) {
|
||||
NEWSBLUR.reader.open_feed_intelligence_modal(1, null, !NEWSBLUR.reader.flags.social_view);
|
||||
}
|
||||
},
|
||||
|
||||
open_statistics: function() {
|
||||
NEWSBLUR.reader.open_feed_statistics_modal();
|
||||
},
|
||||
|
||||
open_settings: function(e) {
|
||||
this.show_manage_menu(e);
|
||||
}
|
||||
|
||||
});
|
Loading…
Add table
Reference in a new issue