mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Double duty: Adding /rss_feeds/search_feed endpoint for discovering feeds through the API. Also adding in a new preference for the sort order of feeds. A bit broken, but it'll do for now. Thanks to @heliostatic for the idea.
This commit is contained in:
parent
8cf598fcbe
commit
cc94f3fd12
9 changed files with 156 additions and 29 deletions
|
@ -49,6 +49,7 @@ class UserSubscription(models.Model):
|
|||
feed['nt'] = self.unread_count_neutral
|
||||
feed['ng'] = self.unread_count_negative
|
||||
feed['active'] = self.active
|
||||
feed['feed_opens'] = self.feed_opens
|
||||
if not self.active and self.user.profile.is_premium:
|
||||
feed['active'] = True
|
||||
self.active = True
|
||||
|
|
|
@ -13,6 +13,7 @@ from django.db import models
|
|||
from django.db import IntegrityError
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.db.models.query import QuerySet
|
||||
from mongoengine.queryset import OperationError
|
||||
from mongoengine.base import ValidationError
|
||||
from apps.rss_feeds.tasks import UpdateFeeds
|
||||
|
@ -118,37 +119,54 @@ class Feed(models.Model):
|
|||
pass
|
||||
|
||||
@classmethod
|
||||
def get_feed_from_url(cls, url):
|
||||
def get_feed_from_url(cls, url, create=True, aggressive=False, offset=0):
|
||||
feed = None
|
||||
|
||||
|
||||
def criteria(key, value):
|
||||
if aggressive:
|
||||
return {'%s__icontains' % key: value}
|
||||
else:
|
||||
return {'%s' % key: value}
|
||||
|
||||
def by_url(address):
|
||||
feed = cls.objects.filter(feed_address=address)
|
||||
feed = cls.objects.filter(**criteria('feed_address', address))
|
||||
if not feed:
|
||||
duplicate_feed = DuplicateFeed.objects.filter(duplicate_address=address).order_by('pk')
|
||||
if duplicate_feed:
|
||||
feed = [duplicate_feed[0].feed]
|
||||
duplicate_feed = DuplicateFeed.objects.filter(**criteria('duplicate_address', address)).order_by('pk')
|
||||
if duplicate_feed and len(duplicate_feed) > offset:
|
||||
feed = [duplicate_feed[offset].feed]
|
||||
if not feed:
|
||||
feed = cls.objects.filter(**criteria('feed_link', address))
|
||||
|
||||
return feed
|
||||
|
||||
url = urlnorm.normalize(url)
|
||||
|
||||
# Normalize and check for feed_address, dupes, and feed_link
|
||||
if not aggressive:
|
||||
url = urlnorm.normalize(url)
|
||||
feed = by_url(url)
|
||||
|
||||
if feed:
|
||||
feed = feed[0]
|
||||
else:
|
||||
|
||||
# Create if it looks good
|
||||
if feed and len(feed) > offset:
|
||||
feed = feed[offset]
|
||||
elif create:
|
||||
if feedfinder.isFeed(url):
|
||||
feed = cls.objects.create(feed_address=url)
|
||||
feed = feed.update()
|
||||
else:
|
||||
feed_finder_url = feedfinder.feed(url)
|
||||
if feed_finder_url:
|
||||
feed = by_url(feed_finder_url)
|
||||
if not feed:
|
||||
feed = cls.objects.create(feed_address=feed_finder_url)
|
||||
feed = feed.update()
|
||||
else:
|
||||
feed = feed[0]
|
||||
|
||||
|
||||
# Still nothing? Maybe the URL has some clues.
|
||||
if not feed:
|
||||
feed_finder_url = feedfinder.feed(url)
|
||||
if feed_finder_url:
|
||||
feed = by_url(feed_finder_url)
|
||||
if not feed and create:
|
||||
feed = cls.objects.create(feed_address=feed_finder_url)
|
||||
feed = feed.update()
|
||||
elif feed and len(feed) > offset:
|
||||
feed = feed[offset]
|
||||
|
||||
# Not created and not within bounds, so toss results.
|
||||
if isinstance(feed, QuerySet):
|
||||
return
|
||||
|
||||
return feed
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -3,6 +3,7 @@ from apps.rss_feeds import views
|
|||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^feed_autocomplete', views.feed_autocomplete, name='feed-autocomplete'),
|
||||
url(r'^search_feed', views.search_feed, name='search-feed'),
|
||||
url(r'^statistics/(?P<feed_id>\d+)', views.load_feed_statistics, name='feed-statistics'),
|
||||
url(r'^feed/(?P<feed_id>\d+)', views.load_single_feed, name='feed-info'),
|
||||
url(r'^exception_retry', views.exception_retry, name='exception-retry'),
|
||||
|
|
|
@ -15,6 +15,18 @@ from utils import json_functions as json, feedfinder
|
|||
from utils.feed_functions import relative_timeuntil, relative_timesince
|
||||
from utils.user_functions import get_user
|
||||
|
||||
|
||||
@json.json_view
|
||||
def search_feed(request):
|
||||
address = request.REQUEST['address']
|
||||
offset = int(request.REQUEST.get('offset', 0))
|
||||
feed = Feed.get_feed_from_url(address, create=False, aggressive=True, offset=offset)
|
||||
|
||||
if feed:
|
||||
return feed.canonical()
|
||||
else:
|
||||
return dict(code=-1, message="No feed found matching that XML or website address.")
|
||||
|
||||
@json.json_view
|
||||
def load_single_feed(request, feed_id):
|
||||
user = get_user(request)
|
||||
|
|
|
@ -4957,6 +4957,10 @@ background: transparent;
|
|||
vertical-align: middle;
|
||||
margin: -1px 6px 0 2px;
|
||||
}
|
||||
.NB-modal-preferences .NB-preference-feedorder label img {
|
||||
vertical-align: middle;
|
||||
margin: -3px 6px 0 2px;
|
||||
}
|
||||
.NB-modal-preferences .NB-preference-password .NB-preference-option {
|
||||
float: left;
|
||||
margin: 0 12px 0 0;
|
||||
|
|
|
@ -852,7 +852,7 @@
|
|||
'opacity': 0
|
||||
});
|
||||
$feed_list.html($feeds);
|
||||
this.sort_feeds($feed_list);
|
||||
// this.sort_feeds($feed_list);
|
||||
this.count_collapsed_unread_stories();
|
||||
$feed_list.animate({'opacity': 1}, {'duration': 700});
|
||||
this.hover_over_feed_titles($feed_list);
|
||||
|
@ -903,6 +903,49 @@
|
|||
}
|
||||
},
|
||||
|
||||
sort_items: function(items) {
|
||||
var self = this;
|
||||
var sort_order = this.model.preference('feed_order');
|
||||
|
||||
if (sort_order == 'ALPHABETICAL' || !sort_order) {
|
||||
return items.sort(function(a, b) {
|
||||
var feedA, feedB;
|
||||
if (_.isNumber(a)) feedA = self.model.get_feed(a);
|
||||
if (_.isNumber(b)) feedB = self.model.get_feed(b);
|
||||
if (feedA && feedB) {
|
||||
return feedA.feed_title > feedB.feed_title ? 1 : -1;
|
||||
} else if (feedA && !feedB) {
|
||||
return -1;
|
||||
} else if (!feedA && feedB) {
|
||||
return 1;
|
||||
} else if (!feedA && !feedB) {
|
||||
var folderA = _.keys(a)[0];
|
||||
var folderB = _.keys(b)[0];
|
||||
return folderA > folderB ? 1 : -1;
|
||||
}
|
||||
});
|
||||
} else if (sort_order == 'MOSTUSED') {
|
||||
return items.sort(function(a, b) {
|
||||
var feedA, feedB;
|
||||
if (_.isNumber(a)) feedA = self.model.get_feed(a);
|
||||
if (_.isNumber(b)) feedB = self.model.get_feed(b);
|
||||
if (feedA && feedB) {
|
||||
return feedA.feed_opens < feedB.feed_opens ? 1 :
|
||||
(feedA.feed_opens > feedB.feed_opens ? -1 :
|
||||
(feedA.feed_title > feedB.feed_title));
|
||||
} else if (feedA && !feedB) {
|
||||
return -1;
|
||||
} else if (!feedA && feedB) {
|
||||
return 1;
|
||||
} else if (!feedA && !feedB) {
|
||||
var folderA = _.keys(a)[0];
|
||||
var folderB = _.keys(b)[0];
|
||||
return folderA > folderB ? 1 : -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
sort_feeds: function($feeds) {
|
||||
$('.feed', $feeds).tsort('.feed_title');
|
||||
$('.folder', $feeds).tsort('.folder_title_text');
|
||||
|
@ -920,6 +963,8 @@
|
|||
var self = this;
|
||||
var $feeds = "";
|
||||
|
||||
items = this.sort_items(items);
|
||||
|
||||
for (var i in items) {
|
||||
var item = items[i];
|
||||
|
||||
|
@ -1093,8 +1138,8 @@
|
|||
}
|
||||
},
|
||||
change: function(e, ui) {
|
||||
$('.feed', ui.placeholder.closest('ul.folder')).tsort('.feed_title');
|
||||
$('li.folder', ui.placeholder.closest('ul.folder')).tsort('.folder_title_text');
|
||||
var $feeds = ui.placeholder.closest('ul.folder');
|
||||
self.sort_feeds($feeds);
|
||||
},
|
||||
stop: function(e, ui) {
|
||||
setTimeout(function() {
|
||||
|
@ -1102,8 +1147,7 @@
|
|||
}, 100);
|
||||
ui.item.removeClass('NB-feed-sorting');
|
||||
self.$s.$feed_list.removeClass('NB-feed-sorting');
|
||||
$('.feed', e.target).tsort('.feed_title');
|
||||
$('li.folder', e.target).tsort('.folder_title_text');
|
||||
self.sort_feeds(e.target);
|
||||
self.save_feed_order();
|
||||
ui.item.css({'backgroundColor': '#D7DDE6'})
|
||||
.animate({'backgroundColor': '#F0F076'}, {'duration': 800})
|
||||
|
|
|
@ -18,6 +18,7 @@ NEWSBLUR.ReaderPreferences.prototype = {
|
|||
this.handle_cancel();
|
||||
this.handle_change();
|
||||
this.open_modal();
|
||||
this.original_preferences = this.serialize_preferences();
|
||||
|
||||
this.$modal.bind('click', $.rescope(this.handle_click, this));
|
||||
},
|
||||
|
@ -198,6 +199,27 @@ NEWSBLUR.ReaderPreferences.prototype = {
|
|||
$.make('div', { className: 'NB-preference-sublabel' }, this.make_site_sidebar_count())
|
||||
])
|
||||
]),
|
||||
$.make('div', { className: 'NB-preference NB-preference-feedorder' }, [
|
||||
$.make('div', { className: 'NB-preference-options' }, [
|
||||
$.make('div', [
|
||||
$.make('input', { id: 'NB-preference-feedorder-1', type: 'radio', name: 'feed_order', value: 'ALPHABETICAL' }),
|
||||
$.make('label', { 'for': 'NB-preference-feedorder-1' }, [
|
||||
$.make('img', { src: NEWSBLUR.Globals.MEDIA_URL+'/img/icons/silk/pilcrow.png' }),
|
||||
'Alphabetical'
|
||||
])
|
||||
]),
|
||||
$.make('div', [
|
||||
$.make('input', { id: 'NB-preference-feedorder-2', type: 'radio', name: 'feed_order', value: 'MOSTUSED' }),
|
||||
$.make('label', { 'for': 'NB-preference-feedorder-2' }, [
|
||||
$.make('img', { src: NEWSBLUR.Globals.MEDIA_URL+'/img/icons/silk/report_user.png' }),
|
||||
'Most used at top, then alphabetical'
|
||||
])
|
||||
])
|
||||
]),
|
||||
$.make('div', { className: 'NB-preference-label'}, [
|
||||
'Site sidebar order'
|
||||
])
|
||||
]),
|
||||
$.make('div', { className: 'NB-preference NB-preference-hidestorychanges' }, [
|
||||
$.make('div', { className: 'NB-preference-options' }, [
|
||||
$.make('div', [
|
||||
|
@ -290,7 +312,7 @@ NEWSBLUR.ReaderPreferences.prototype = {
|
|||
$.make('a', { className: 'NB-splash-link', href: NEWSBLUR.URLs['opml-export'] }, 'Download OPML')
|
||||
]),
|
||||
$.make('div', { className: 'NB-preference-label'}, [
|
||||
'Backup Your Sites',
|
||||
'Backup your sites',
|
||||
$.make('div', { className: 'NB-preference-sublabel' }, 'Download this XML file as a backup.')
|
||||
])
|
||||
]),
|
||||
|
@ -306,7 +328,7 @@ NEWSBLUR.ReaderPreferences.prototype = {
|
|||
])
|
||||
]),
|
||||
$.make('div', { className: 'NB-preference-label'}, [
|
||||
'Change Password',
|
||||
'Change password',
|
||||
$.make('div', { className: 'NB-preference-error'})
|
||||
])
|
||||
]),
|
||||
|
@ -379,6 +401,12 @@ NEWSBLUR.ReaderPreferences.prototype = {
|
|||
return false;
|
||||
}
|
||||
});
|
||||
$('input[name=feed_order]', this.$modal).each(function() {
|
||||
if ($(this).val() == NEWSBLUR.Preferences.feed_order) {
|
||||
$(this).attr('checked', true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
$('input[name=hide_story_changes]', this.$modal).each(function() {
|
||||
if ($(this).val() == NEWSBLUR.Preferences.hide_story_changes) {
|
||||
$(this).attr('checked', true);
|
||||
|
@ -443,6 +471,9 @@ NEWSBLUR.ReaderPreferences.prototype = {
|
|||
NEWSBLUR.reader.switch_feed_view_unread_view();
|
||||
NEWSBLUR.reader.apply_story_styling(true);
|
||||
NEWSBLUR.reader.show_stories_preference_in_feed_view();
|
||||
if (self.original_preferences['feed_order'] != form['feed_order']) {
|
||||
NEWSBLUR.reader.make_feeds();
|
||||
}
|
||||
$.modal.close();
|
||||
});
|
||||
},
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
'new_window' : 1,
|
||||
'default_view' : 'page',
|
||||
'hide_read_feeds' : 0,
|
||||
'feed_order' : 'ALPHABETICAL',
|
||||
'hide_story_changes' : 0,
|
||||
'feed_view_single_story' : 0,
|
||||
'view_settings' : {},
|
||||
|
|
|
@ -42,6 +42,21 @@
|
|||
example: "samuel@ofbrooklyn.com"
|
||||
|
||||
- feeds:
|
||||
- url: /rss_feeds/search_feed
|
||||
method: GET
|
||||
short_desc: "Information about a feed from its address."
|
||||
long_desc:
|
||||
- "Retrieve information about a feed from its website or RSS address."
|
||||
params:
|
||||
- key: address
|
||||
desc: "Searches the RSS and website address and returns a feed."
|
||||
required: true
|
||||
example: 'techcrunch.com'
|
||||
- key: offset
|
||||
desc: "Try paging through feeds found by using the offset."
|
||||
optional: true
|
||||
example: 1
|
||||
|
||||
- url: /reader/feeds
|
||||
method: GET
|
||||
short_desc: "User's feeds, with unread counts, meta data, and optional favicons."
|
||||
|
|
Loading…
Add table
Reference in a new issue