Merge branch 'master' of github.com:samuelclay/newsblur

This commit is contained in:
Samuel Clay 2010-02-14 22:51:29 -05:00
commit 478827b51e
13 changed files with 378 additions and 150 deletions

View file

@ -10,7 +10,7 @@ from django.utils.safestring import mark_safe
from django.views.decorators.cache import cache_page
from django.views.decorators.http import require_POST
from apps.rss_feeds.models import Feed, Story, Tag
from apps.reader.models import UserSubscription, UserSubscriptionFolders, UserStory
from apps.reader.models import UserSubscription, UserStory
from apps.analyzer.models import ClassifierTitle, ClassifierAuthor, ClassifierFeed, ClassifierTag
from utils import json
from utils.user_functions import get_user

View file

@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
from django.shortcuts import render_to_response, get_list_or_404, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.template import RequestContext
from django.core.cache import cache
from apps.rss_feeds.models import Feed
from apps.reader.models import UserSubscription, UserSubscriptionFolders
from utils.json import json_encode
from utils import json
import utils.opml as opml
from django.contrib.auth.models import User
from django.http import HttpResponse, HttpRequest
@ -17,46 +19,128 @@ import codecs
def opml_upload(request):
xml_opml = None
message = "OK"
code = 1
if request.method == 'POST':
if 'file' in request.FILES:
file = request.FILES['file']
xml_opml = file.read()
data = opml_import(xml_opml, request.user).encode('utf-8')
opml_importer = OPMLImporter(xml_opml, request.user)
folders = opml_importer.process()
feeds = UserSubscription.objects.filter(user=user).values()
data = json.encode(dict(message=message, code=code, payload=dict(folders=folders, feeds=feeds)))
return HttpResponse(data, mimetype='text/plain')
def opml_import(xml_opml, user):
context = None
outline = opml.from_string(xml_opml)
feeds = []
message = "OK"
code = 1
for folder in outline:
print folder.text
for feed in folder:
print '\t%s - %s - %s' % (feed.title, type(feed.title), type(feed.title.decode('utf-8')))
feed_data = dict(feed_address=feed.xmlUrl, feed_link=feed.htmlUrl, feed_title=feed.title)
feeds.append(feed_data)
new_feed = Feed(**feed_data)
try:
new_feed.save()
except IntegrityError:
new_feed = Feed.objects.get(feed_address=feed.xmlUrl)
us = UserSubscription(feed=new_feed, user=user)
try:
us.save()
except IntegrityError:
us = UserSubscription.objects.get(feed=new_feed, user=user)
user_sub_folder = UserSubscriptionFolders(user=user, feed=new_feed, user_sub=us, folder=folder.text)
try:
user_sub_folder.save()
except IntegrityError:
print 'Can\'t save user_sub_folder'
# new_feed, _ = Feed.objects.get_or_create(feed_address=feed.xmlUrl, defaults=feed_data)
# us, _ = UserSubscription.objects.get_or_create(feed=new_feed, user=user)
# user_sub_folder, _ = UserSubscriptionFolders.objects.get_or_create(user=user, feed=new_feed, user_sub=us, defaults=dict(folder=folder.text))
data = json_encode(dict(message=message, code=code, payload=dict(feeds=feeds, feed_count=len(feeds))))
cache.delete('usersub:%s' % user)
class OPMLImporter:
def __init__(self, opml_xml, user):
self.user = user
self.opml_xml = opml_xml
return data
def process(self):
outline = opml.from_string(self.opml_xml)
folders = self.process_outline(outline)
UserSubscriptionFolders.objects.create(user=self.user, folders=json.encode(folders))
return folders
def process_outline(self, outline):
folders = []
for item in outline:
if not hasattr(item, 'xmlUrl'):
folder = item
print 'New Folder: %s' % folder.text
folders.append({folder.text: self.process_outline(folder)})
elif hasattr(item, 'xmlUrl'):
feed = item
print '\t%s - %s - %s' % (feed.title, feed.htmlUrl, feed.xmlUrl,)
feed_data = dict(feed_address=feed.xmlUrl, feed_link=feed.htmlUrl, feed_title=feed.title)
# feeds.append(feed_data)
feed_db, _ = Feed.objects.get_or_create(feed_address=feed.xmlUrl, defaults=dict(**feed_data))
us, _ = UserSubscription.objects.get_or_create(feed=feed_db, user=self.user)
folders.append(feed_db.pk)
return folders
if __name__ == '__main__':
opml_string = """<?xml version="1.0" encoding="UTF-8"?>
<!-- OPML generated by NetNewsWire -->
<opml version="1.1">
<head>
<title>mySubscriptions</title>
</head>
<body>
<outline text="New York" title="New York">
<outline text="Brownstoner" description="" title="Brownstoner" type="rss" version="RSS" htmlUrl="http://www.brownstoner.com/" xmlUrl="http://www.brownstoner.com/index.xml"/>
<outline text="Gothamist" description="" title="Gothamist" type="rss" version="RSS" htmlUrl="http://gothamist.com/" xmlUrl="http://gothamist.com/index.rdf"/>
<outline text="Brokelyn" description="" title="Brokelyn" type="rss" version="RSS" htmlUrl="http://www.brokelyn.com" xmlUrl="http://www.brokelyn.com/feed/"/>
<outline text="Streetsblog New York City" description="" title="Streetsblog New York City" type="rss" version="RSS" htmlUrl="http://www.streetsblog.org" xmlUrl="http://www.streetsblog.org/feed/"/>
<outline text="The Corduroy Appreciation Club" description="" title="The Corduroy Appreciation Club" type="rss" version="RSS" htmlUrl="http://corduroyclub.com" xmlUrl="http://corduroyclub.com/feed"/>
<outline text="FREEwilliamsburg" description="" title="FREEwilliamsburg" type="rss" version="RSS" htmlUrl="http://www.freewilliamsburg.com/" xmlUrl="http://www.freewilliamsburg.com/atom.xml"/>
<outline text="Scouting NY" description="" title="Scouting NY" type="rss" version="RSS" htmlUrl="http://www.scoutingny.com" xmlUrl="http://www.scoutingny.com/?feed=rss2"/>
<outline text="very small array" description="" title="very small array" type="rss" version="RSS" htmlUrl="http://www.verysmallarray.com" xmlUrl="http://www.verysmallarray.com/?feed=rss2"/>
<outline text="Frugal Traveler" description="" title="Frugal Traveler" type="rss" version="RSS" htmlUrl="http://frugaltraveler.blogs.nytimes.com/" xmlUrl="http://frugaltraveler.blogs.nytimes.com/feed/"/>
</outline>
<outline text="Tech" title="Tech">
<outline text="Joel on Software" description="" title="Joel on Software" type="rss" version="RSS" htmlUrl="http://www.joelonsoftware.com" xmlUrl="http://www.joelonsoftware.com/rss.xml"/>
<outline text="Daring Fireball" description="" title="Daring Fireball" type="rss" version="RSS" htmlUrl="http://daringfireball.net/" xmlUrl="http://daringfireball.net/index.xml"/>
<outline text="Techcrunch" description="" title="Techcrunch" type="rss" version="RSS" htmlUrl="http://techcrunch.com" xmlUrl="http://feeds.feedburner.com/Techcrunch"/>
<outline text="Coding Horror" description="" title="Coding Horror" type="rss" version="RSS" htmlUrl="http://www.codinghorror.com/blog/" xmlUrl="http://feeds.feedburner.com/codinghorror/"/>
<outline text="John Resig" description="" title="John Resig" type="rss" version="RSS" htmlUrl="http://ejohn.org" xmlUrl="http://feeds.feedburner.com/JohnResig"/>
<outline text="Ted Dziuba" description="" title="Ted Dziuba" type="rss" version="RSS" htmlUrl="http://teddziuba.com/" xmlUrl="http://teddziuba.com/atom.xml"/>
<outline text="Ajaxian » Front Page" description="" title="Ajaxian » Front Page" type="rss" version="RSS" htmlUrl="http://ajaxian.com" xmlUrl="http://ajaxian.com/index.xml"/>
<outline text="James Padolsey" description="" title="James Padolsey" type="rss" version="RSS" htmlUrl="http://james.padolsey.com" xmlUrl="http://james.padolsey.com/feed/"/>
<outline text="David Cramer's Blog" description="" title="David Cramer's Blog" type="rss" version="RSS" htmlUrl="http://www.davidcramer.net" xmlUrl="http://www.davidcramer.net/feed"/>
<outline text="MacRumors : Mac News and Rumors" description="" title="MacRumors : Mac News and Rumors" type="rss" version="RSS" htmlUrl="http://www.macrumors.com" xmlUrl="http://www.macrumors.com/macrumors.xml"/>
<outline text="wonko.com" description="" title="wonko.com" type="rss" version="RSS" htmlUrl="http://wonko.com/" xmlUrl="http://feeds.feedburner.com/wonko"/>
<outline text="Devthought" description="" title="Devthought" type="rss" version="RSS" htmlUrl="http://devthought.com" xmlUrl="http://feeds2.feedburner.com/devthought?format=xml"/>
<outline text="jQuery Blog" description="" title="jQuery Blog" type="rss" version="RSS" htmlUrl="http://blog.jquery.com" xmlUrl="http://blog.jquery.com/feed/"/>
<outline text="Hacker News - Excerpts" description="" title="Hacker News - Excerpts" type="rss" version="RSS" htmlUrl="http://news.ycombinator.com/" xmlUrl="http://andrewtrusty.appspot.com/readability/feed?url=http%3A//news.ycombinator.com/rss"/>
<outline text="Waxy.org" description="" title="Waxy.org" type="rss" version="RSS" htmlUrl="http://waxy.org/" xmlUrl="http://waxy.org/index.xml"/>
<outline text="Lazy Pythonista" description="" title="Lazy Pythonista" type="rss" version="RSS" htmlUrl="http://lazypython.blogspot.com/" xmlUrl="http://lazypython.blogspot.com/feeds/posts/default?alt=rss"/>
<outline text="cdixon.org - chris dixon's blog" description="" title="cdixon.org - chris dixon's blog" type="rss" version="RSS" htmlUrl="http://cdixon.org" xmlUrl="http://cdixon.org/feed/"/>
<outline text="Bill the Lizard" description="" title="Bill the Lizard" type="rss" version="RSS" htmlUrl="http://www.billthelizard.com/" xmlUrl="http://www.billthelizard.com/feeds/posts/default"/>
<outline text="inessential.com" description="" title="inessential.com" type="rss" version="RSS" htmlUrl="http://inessential.com/" xmlUrl="http://inessential.com/xml/rss.xml"/>
</outline>
<outline text="Blogs" title="Blogs">
<outline text="You Look Nice Today" description="" title="You Look Nice Today" type="rss" version="RSS" htmlUrl="http://youlooknicetoday.com" xmlUrl="http://youlooknicetoday.com/rss.xml"/>
<outline text="The Lens and the Eyebrow" description="" title="The Lens and the Eyebrow" type="rss" version="RSS" htmlUrl="http://blog.seemichaelsphotos.com" xmlUrl="http://blog.seemichaelsphotos.com/rss2.aspx"/>
<outline text="43 Folders" description="" title="43 Folders" type="rss" version="RSS" htmlUrl="http://www.43folders.com" xmlUrl="http://www.43folders.com/rss.xml"/>
<outline text="The Doree Chronicles" description="" title="The Doree Chronicles" type="rss" version="RSS" htmlUrl="http://doree.tumblr.com/" xmlUrl="http://doree.tumblr.com/rss"/>
<outline text="the impossible cool." description="" title="the impossible cool." type="rss" version="RSS" htmlUrl="http://theimpossiblecool.tumblr.com/" xmlUrl="http://theimpossiblecool.tumblr.com/rss"/>
<outline text="kottke.org" description="" title="kottke.org" type="rss" version="RSS" htmlUrl="http://kottke.org/" xmlUrl="http://feeds.kottke.org/main"/>
<outline text="Philip Greenspun's Weblog" description="" title="Philip Greenspun's Weblog" type="rss" version="RSS" htmlUrl="http://blogs.law.harvard.edu/philg" xmlUrl="http://blogs.law.harvard.edu/philg/feed/"/>
<outline text="Rands In Repose" description="" title="Rands In Repose" type="rss" version="RSS" htmlUrl="http://www.randsinrepose.com/" xmlUrl="http://www.randsinrepose.com/index.xml"/>
<outline text="the selby - photos in your space" description="" title="the selby - photos in your space" type="rss" version="RSS" htmlUrl="http://www.theselby.com" xmlUrl="http://feeds2.feedburner.com/Theselby-PhotosInYourSpace?format=xml"/>
<outline text="The Storybird blog" description="" title="The Storybird blog" type="rss" version="RSS" htmlUrl="http://blog.storybird.com" xmlUrl="http://feeds.feedburner.com/TheStorybirdBlog?format=xml"/>
<outline text="You are what you share" description="" title="You are what you share" type="rss" version="RSS" htmlUrl="http://markury.com/" xmlUrl="http://markury.com/rss"/>
<outline text="Bird feed" description="" title="Bird feed" type="rss" version="RSS" htmlUrl="http://storybird.tumblr.com/" xmlUrl="http://storybird.tumblr.com/rss"/>
<outline text="one day at a time" description="" title="one day at a time" type="rss" version="RSS" htmlUrl="http://abangupjob.tumblr.com/" xmlUrl="http://abangupjob.tumblr.com/rss"/>
<outline text="Pretty in the City • Karyn Bosnak" description="" title="Pretty in the City • Karyn Bosnak" type="rss" version="RSS" htmlUrl="http://www.prettyinthecity.com/blog/" xmlUrl="http://www.prettyinthecity.com/blog/rss.xml"/>
<outline text="An Entirely Other Day: Full Feed" description="" title="An Entirely Other Day: Full Feed" type="rss" version="RSS" htmlUrl="http://www.eod.com/blog/" xmlUrl="http://feeds.feedburner.com/eod_full"/>
<outline text="hipster puppies" description="" title="hipster puppies" type="rss" version="RSS" htmlUrl="http://hipsterpuppies.tumblr.com/" xmlUrl="http://hipsterpuppies.tumblr.com/rss"/>
<outline text="The Bloglets" title="The Bloglets">
<outline text="FAIL Blog: Pictures and Videos of Owned, Pwnd and Fail Moments" description="" title="FAIL Blog: Pictures and Videos of Owned, Pwnd and Fail Moments" type="rss" version="RSS" htmlUrl="http://failblog.org" xmlUrl="http://feeds.feedburner.com/failblog"/>
<outline text="Iconic Photos" description="" title="Iconic Photos" type="rss" version="RSS" htmlUrl="http://iconicphotos.wordpress.com" xmlUrl="http://iconicphotos.wordpress.com/feed/"/>
<outline text="nerdboyfriend" description="" title="nerdboyfriend" type="rss" version="RSS" htmlUrl="http://nerdboyfriend.com" xmlUrl="http://nerdboyfriend.com/?feed=rss2"/>
<outline text="The Cellar - Image of the Day" description="" title="The Cellar - Image of the Day" type="rss" version="RSS" htmlUrl="http://cellar.org" xmlUrl="http://cellar.org/external.php?type=rss2&amp;forumids=10"/>
<outline text="The Sartorialist" description="" title="The Sartorialist" type="rss" version="RSS" htmlUrl="http://thesartorialist.blogspot.com/" xmlUrl="http://thesartorialist.blogspot.com/feeds/posts/default?alt=rss"/>
</outline>
</outline>
<outline text="Cooking" title="Cooking">
<outline text="Easy Recipes For Everyday Cooking by Savory Sweet Life" description="" title="Easy Recipes For Everyday Cooking by Savory Sweet Life" type="rss" version="RSS" htmlUrl="http://savorysweetlife.com" xmlUrl="http://savorysweetlife.com/?feed=rss2"/>
<outline text="SALAD &amp; CANDY" description="" title="SALAD &amp; CANDY" type="rss" version="RSS" htmlUrl="http://saladandcandy.com/" xmlUrl="http://saladandcandy.com/rss"/>
<outline text="smitten kitchen" description="" title="smitten kitchen" type="rss" version="RSS" htmlUrl="http://smittenkitchen.com" xmlUrl="http://feeds.feedburner.com/smittenkitchen"/>
<outline text="Salt &amp; Fat" description="" title="Salt &amp; Fat" type="rss" version="RSS" htmlUrl="http://saltandfat.com/" xmlUrl="http://saltandfat.com/rss"/>
</outline>
<outline text="Samuel Clay's OfBrooklyn.com" description="" title="Samuel Clay's OfBrooklyn.com" type="rss" version="RSS" htmlUrl="http://conesus.com" xmlUrl="http://www.ofbrooklyn.com/feeds/all/"/>
</body>
</opml>"""
user = User.objects.get(username='conesus')
opml_importer = OPMLImporter(opml_string, user)
data = opml_importer.process()
print data

View file

@ -165,15 +165,11 @@ class UserStory(models.Model):
class UserSubscriptionFolders(models.Model):
user = models.ForeignKey(User)
user_sub = models.ForeignKey(UserSubscription)
feed = models.ForeignKey(Feed)
folder = models.CharField(max_length=255)
folders = models.TextField(default="{}")
def __unicode__(self):
return ('[' + self.feed.feed_title + '] '
+ self.folder)
return "[%s]: %s" % (self.user, len(self.folders),)
class Meta:
verbose_name_plural = "folders"
verbose_name = "folder"
unique_together = ("user", "user_sub")
verbose_name = "folder"

View file

@ -57,40 +57,23 @@ def logout(request):
def load_feeds(request):
user = get_user(request)
feeds = {}
folders = UserSubscriptionFolders.objects.get(user=user)
user_subs = UserSubscription.objects.select_related('feed').filter(user=user)
us = UserSubscriptionFolders.objects.select_related('feed', 'user_sub').filter(
user=user
)
# logging.info('UserSubs: %s' % us)
feeds = []
folders = []
for sub in us:
try:
sub.feed.unread_count_positive = sub.user_sub.unread_count_positive
sub.feed.unread_count_neutral = sub.user_sub.unread_count_neutral
sub.feed.unread_count_negative = sub.user_sub.unread_count_negative
except:
logging.warn("Subscription %s does not exist outside of Folder." % (sub.feed))
sub.delete()
else:
if sub.folder not in folders:
folders.append(sub.folder)
feeds.append({'folder': sub.folder, 'feeds': []})
for folder in feeds:
if folder['folder'] == sub.folder:
folder['feeds'].append(sub.feed)
for sub in user_subs:
feeds[sub.feed.pk] = {
'id': sub.feed.pk,
'feed_title': sub.feed.feed_title,
'feed_link': sub.feed.feed_link,
'unread_count_positive': sub.unread_count_positive,
'unread_count_neutral': sub.unread_count_neutral,
'unread_count_negative': sub.unread_count_negative,
}
# Alphabetize folders, then feeds inside folders
feeds.sort(lambda x, y: cmp(x['folder'].lower(), y['folder'].lower()))
for feed in feeds:
feed['feeds'].sort(lambda x, y: cmp(x.feed_title.lower(), y.feed_title.lower()))
for f in feed['feeds']:
f.feed_address = mark_safe(f.feed_address)
f.page_data = None
data = json.encode(feeds)
return HttpResponse(data, mimetype='application/json')
data = dict(feeds=feeds, folders=json.decode(folders.folders))
return HttpResponse(json.encode(data), mimetype='application/json')
def load_single_feed(request):
user = get_user(request)

View file

@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand
from django.core.handlers.wsgi import WSGIHandler
from apps.rss_feeds.models import Feed, Story
from django.core.cache import cache
from apps.reader.models import UserSubscription, UserSubscriptionFolders, UserStory
from apps.reader.models import UserSubscription, UserStory
from optparse import OptionParser, make_option
import os
import logging

View file

@ -3,7 +3,7 @@ from django.core.handlers.wsgi import WSGIHandler
from apps.rss_feeds.models import Feed, Story
from django.core.cache import cache
from django.db.models import Q
from apps.reader.models import UserSubscription, UserSubscriptionFolders, UserStory
from apps.reader.models import UserSubscription, UserStory
from optparse import OptionParser, make_option
from utils.management_functions import daemonize
import os

View file

@ -164,12 +164,16 @@ class Feed(models.Model):
user_stories_count = 0
stories = Story.objects.filter(story_feed=self).order_by('-story_date')
print 'Found %s stories in %s. Trimming...' % (stories.count(), self)
for story in stories[1000:]:
user_stories = UserStory.objects.filter(story=story)
if stories.count() > 1000:
old_story = stories[1000]
user_stories = UserStory.objects.filter(feed=self,
read_date__lte=old_story.story_date)
user_stories_count = user_stories.count()
user_stories.delete()
story.delete()
stories_deleted_count += 1
old_stories = Story.objects.filter(story_feed=self,
story_date__lte=old_story.story_date)
stories_deleted_count = old_stories.count()
old_stories.delete()
if stories_deleted_count:
print "Trimming %s stories from %s. %s user stories." % (

View file

@ -135,6 +135,10 @@ a img {
text-transform: uppercase;
}
#feed_list .feeds {
margin-left: 22px;
}
#feed_list .feed {
position: relative;
cursor: pointer;
@ -149,11 +153,11 @@ a img {
#feed_list img.feed_favicon {
position: absolute;
top: 2px;
left: 24px;
left: 2px;
}
#feed_list .feed_title {
display: block;
padding: 4px 42px 2px 45px;
padding: 4px 42px 2px 23px;
text-decoration: none;
color: #272727;
line-height: 1.3em;

127
media/js/jquery.tinysort.js Normal file
View file

@ -0,0 +1,127 @@
/*
* jQuery TinySort - A plugin to sort child nodes by (sub) contents or attributes.
*
* Version: 1.0.3
*
* Copyright (c) 2008 Ron Valstar
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* description
* - A plugin to sort child nodes by (sub) contents or attributes.
*
* Usage:
* $("ul#people>li").tsort();
* $("ul#people>li").tsort("span.surname");
* $("ul#people>li").tsort("span.surname",{order:"desc"});
* $("ul#people>li").tsort({place:"end"});
*
* Change default like so:
* $.tinysort.defaults.order = "desc";
*
* in this update:
* - tested with jQuery 1.4.1
* - correct isNum return
*
* in last update:
* - matching numerics did not work for trailing zero's, replaced with regexp (which should now work for + and - signs as well)
*
* Todos
* - fix mixed literal/numeral values
* - determine if I have to use setArray or pushStack
*
*/
;(function($) {
// default settings
$.tinysort = {
id: "TinySort"
,version: "1.0.3"
,defaults: {
order: "asc" // order: asc, desc or rand
,attr: "" // order by attribute value
,place: "start" // place ordered elements at position: start, end, org (original position), first
,returns: false // return all elements or only the sorted ones (true/false)
}
};
$.fn.extend({
tinysort: function(_find,_settings) {
if (_find&&typeof(_find)!="string") {
_settings = _find;
_find = null;
}
var oSettings = $.extend({}, $.tinysort.defaults, _settings);
var oElements = {}; // contains sortable- and non-sortable list per parent
this.each(function(i) {
// element or sub selection
var mElm = (!_find||_find=="")?$(this):$(this).find(_find);
// text or attribute value
var sSort = oSettings.order=="rand"?""+Math.random():(oSettings.attr==""?mElm.text():mElm.attr(oSettings.attr));
// to sort or not to sort
var mParent = $(this).parent();
if (!oElements[mParent]) oElements[mParent] = {s:[],n:[]}; // s: sort, n: not sort
if (mElm.length>0) oElements[mParent].s.push({s:sSort,e:$(this),n:i}); // s:string, e:element, n:number
else oElements[mParent].n.push({e:$(this),n:i});
});
//
// sort
for (var sParent in oElements) {
var oParent = oElements[sParent];
oParent.s.sort(
function zeSort(a,b) {
var x = a.s.toLowerCase?a.s.toLowerCase():a.s;
var y = b.s.toLowerCase?b.s.toLowerCase():b.s;
if (isNum(a.s)&&isNum(b.s)) {
x = parseFloat(a.s);
y = parseFloat(b.s);
}
return (oSettings.order=="asc"?1:-1)*(x<y?-1:(x>y?1:0));
}
);
}
//
// order elements and fill new order
var aNewOrder = [];
for (var sParent in oElements) {
var oParent = oElements[sParent];
var aOrg = []; // list for original position
var iLow = $(this).length;
switch (oSettings.place) {
case "first": $.each(oParent.s,function(i,obj) { iLow = Math.min(iLow,obj.n) }); break;
case "org": $.each(oParent.s,function(i,obj) { aOrg.push(obj.n) }); break;
case "end": iLow = oParent.n.length; break;
default: iLow = 0;
}
var aCnt = [0,0]; // count how much we've sorted for retreival from either the sort list or the non-sort list (oParent.s/oParent.n)
for (var i=0;i<$(this).length;i++) {
var bSList = i>=iLow&&i<iLow+oParent.s.length;
if (contains(aOrg,i)) bSList = true;
var mEl = (bSList?oParent.s:oParent.n)[aCnt[bSList?0:1]].e;
mEl.parent().append(mEl);
if (bSList||!oSettings.returns) aNewOrder.push(mEl.get(0));
aCnt[bSList?0:1]++;
}
}
//
return this.setArray(aNewOrder); // setArray or pushStack?
}
});
// is numeric
function isNum(n) {
var x = /^\s*?[\+-]?(\d*\.?\d*?)\s*?$/.exec(n);
return x&&x.length>0?x[1]:false;
};
// array contains
function contains(a,n) {
var bInside = false;
$.each(a,function(i,m) {
if (!bInside) bInside = m==n;
});
return bInside;
};
// set functions
$.fn.TinySort = $.fn.Tinysort = $.fn.tsort = $.fn.tinysort;
})(jQuery);

View file

@ -25,6 +25,7 @@ NEWSBLUR.AssetModel = function() {
NEWSBLUR.AssetModel.Reader = function() {
this.feeds = {};
this.folders = [];
this.stories = {};
};
@ -135,9 +136,10 @@ NEWSBLUR.AssetModel.Reader.prototype = {
load_feeds: function(callback) {
var self = this;
var pre_callback = function(folders) {
self.folders = folders;
callback(folders);
var pre_callback = function(subscriptions) {
self.feeds = subscriptions.feeds;
self.folders = subscriptions.folders;
callback();
};
this.make_request('/reader/load_feeds', {}, pre_callback);
@ -182,15 +184,8 @@ NEWSBLUR.AssetModel.Reader.prototype = {
get_feed: function(feed_id, callback) {
var self = this;
for (fld in this.folders) {
var feeds = this.folders[fld].feeds;
for (f in feeds) {
if (feeds[f].id == feed_id) {
return feeds[f];
}
}
}
return null;
return this.feeds[feed_id];
},
get_feed_tags: function() {

View file

@ -334,63 +334,85 @@
load_feeds: function() {
var self = this;
var callback = function() {
var $feed_list = self.$feed_list.empty();
var folders = self.model.folders;
$('#story_taskbar').css({'display': 'block'});
// NEWSBLUR.log(['Subscriptions', {'folders':folders}]);
for (fo in folders) {
var feeds = folders[fo].feeds;
var $folder = $.make('div', { className: 'folder' }, [
$.make('span', { className: 'folder_title' }, folders[fo].folder),
$.make('div', { className: 'feeds' })
]);
for (f in feeds) {
var unread_class = '';
if (feeds[f].unread_count_positive) {
unread_class += ' unread_positive';
}
if (feeds[f].unread_count_neutral) {
unread_class += ' unread_neutral';
}
if (feeds[f].unread_count_negative) {
unread_class += ' unread_negative';
}
var $feed = $.make('div', { className: 'feed ' + unread_class }, [
$.make('span', {
className: 'unread_count unread_count_positive '
+ (feeds[f].unread_count_positive
? "unread_count_full"
: "unread_count_empty")
}, ''+feeds[f].unread_count_positive),
$.make('span', {
className: 'unread_count unread_count_neutral '
+ (feeds[f].unread_count_neutral
? "unread_count_full"
: "unread_count_empty")
}, ''+feeds[f].unread_count_neutral),
$.make('span', {
className: 'unread_count unread_count_negative '
+ (feeds[f].unread_count_negative
? "unread_count_full"
: "unread_count_empty")
}, ''+feeds[f].unread_count_negative),
$.make('img', { className: 'feed_favicon', src: self.google_favicon_url + feeds[f].feed_link }),
$.make('span', { className: 'feed_title' }, feeds[f].feed_title)
]).data('feed_id', feeds[f].id);
$('.feeds', $folder).append($feed);
}
$feed_list.append($folder);
}
$('.unread_count', $feed_list).corners('4px');
};
if ($('#feed_list').length) {
this.model.load_feeds(callback);
this.model.load_feeds($.rescope(this.make_feeds, this));
}
},
make_feeds: function() {
var $feed_list = this.$feed_list.empty();
var folders = this.model.folders;
var feeds = this.model.feeds;
NEWSBLUR.log(['Making feeds', {'folders': folders, 'feeds': feeds}]);
$('#story_taskbar').css({'display': 'block'});
// NEWSBLUR.log(['Subscriptions', {'folders':folders}]);
var $folder = this.make_feeds_folder(folders);
$feed_list.append($folder);
$('.unread_count', $feed_list).corners('4px');
},
make_feeds_folder: function(items) {
var $feeds = $.make('div');
for (var i in items) {
var item = items[i];
if (typeof item == "number") {
var feed = this.model.feeds[item];
var unread_class = '';
if (feed.unread_count_positive) {
unread_class += ' unread_positive';
}
if (feed.unread_count_neutral) {
unread_class += ' unread_neutral';
}
if (feed.unread_count_negative) {
unread_class += ' unread_negative';
}
var $feed = $.make('div', { className: 'feed ' + unread_class }, [
$.make('span', {
className: 'unread_count unread_count_positive '
+ (feed.unread_count_positive
? "unread_count_full"
: "unread_count_empty")
}, ''+feed.unread_count_positive),
$.make('span', {
className: 'unread_count unread_count_neutral '
+ (feed.unread_count_neutral
? "unread_count_full"
: "unread_count_empty")
}, ''+feed.unread_count_neutral),
$.make('span', {
className: 'unread_count unread_count_negative '
+ (feed.unread_count_negative
? "unread_count_full"
: "unread_count_empty")
}, ''+feed.unread_count_negative),
$.make('img', { className: 'feed_favicon', src: this.google_favicon_url + feed.feed_link }),
$.make('span', { className: 'feed_title' }, feed.feed_title)
]).data('feed_id', feed.id);
$feeds.append($feed);
} else if (typeof item == "object") {
for (var o in item) {
var folder = item[o];
var $folder = $.make('div', { className: 'folder' }, [
$.make('span', { className: 'folder_title' }, o),
$.make('div', { className: 'feeds' }, this.make_feeds_folder(folder))
]);
$feeds.append($folder);
}
}
}
$('.feed', $feeds).tsort('.feed_title');
$('.folder', $feeds).tsort('.folder_title');
return $feeds;
},
// =====================
// = Story Titles Pane =
// =====================

View file

@ -108,7 +108,8 @@ elif DEV_SERVER1:
MEDIA_URL = '/media/'
DEBUG = True
# CACHE_BACKEND = 'locmem:///'
CACHE_BACKEND = 'memcached://127.0.0.1:11211'
# CACHE_BACKEND = 'memcached://127.0.0.1:11211'
CACHE_BACKEND = 'dummy:///'
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(message)s',
filename=LOG_FILE,
@ -181,6 +182,7 @@ COMPRESS_JS = {
'js/jquery.ui.core.js',
'js/jquery.ui.slider.js',
'js/jquery.layout.js',
'js/jquery.tinysort.js',
'js/jquery.fieldselection.js',
'js/newsblur/assetmodel.js',
@ -267,5 +269,16 @@ INSTALLED_APPS = (
'apps.registration',
'apps.opml_import',
'apps.profile',
'devserver',
# 'debug_toolbar'
)
DEVSERVER_MODULES = (
'devserver.modules.sql.SQLRealTimeModule',
'devserver.modules.sql.SQLSummaryModule',
'devserver.modules.profile.ProfileSummaryModule',
# Modules not enabled by default
'devserver.modules.profile.MemoryUseModule',
'devserver.modules.cache.CacheSummaryModule',
)

View file

@ -1,6 +1,6 @@
from apps.rss_feeds.models import Story
from django.core.cache import cache
from apps.reader.models import UserSubscription, UserSubscriptionFolders, UserStory
from apps.reader.models import UserSubscription, UserStory
from apps.rss_feeds.importer import PageImporter
from utils import feedparser, threadpool
from django.db import transaction