Merge branch 'master' into animations

This commit is contained in:
Samuel Clay 2011-07-12 08:53:15 -07:00
commit 38accaa3c0
52 changed files with 28930 additions and 9488 deletions

View file

@ -32,7 +32,7 @@ class MClassifierTitle(mongo.Document):
meta = {
'collection': 'classifier_title',
'indexes': ['feed_id', 'user_id', ('user_id', 'feed_id')],
'indexes': [('user_id', 'feed_id')],
'allow_inheritance': False,
}
@ -45,7 +45,7 @@ class MClassifierAuthor(mongo.Document):
meta = {
'collection': 'classifier_author',
'indexes': ['feed_id', 'user_id', ('user_id', 'feed_id')],
'indexes': [('user_id', 'feed_id')],
'allow_inheritance': False,
}
@ -58,7 +58,7 @@ class MClassifierFeed(mongo.Document):
meta = {
'collection': 'classifier_feed',
'indexes': ['feed_id', 'user_id', ('user_id', 'feed_id')],
'indexes': [('user_id', 'feed_id')],
'allow_inheritance': False,
}
@ -72,7 +72,7 @@ class MClassifierTag(mongo.Document):
meta = {
'collection': 'classifier_tag',
'indexes': ['feed_id', 'user_id', ('user_id', 'feed_id')],
'indexes': [('user_id', 'feed_id')],
'allow_inheritance': False,
}
@ -114,17 +114,13 @@ def apply_classifier_tags(classifiers, story):
def get_classifiers_for_user(user, feed_id, classifier_feeds=None, classifier_authors=None, classifier_titles=None, classifier_tags=None):
if classifier_feeds is None:
classifier_feeds = MClassifierFeed.objects(user_id=user.pk, feed_id=feed_id)
else: classifier_feeds.rewind()
classifier_feeds = list(MClassifierFeed.objects(user_id=user.pk, feed_id=feed_id))
if classifier_authors is None:
classifier_authors = MClassifierAuthor.objects(user_id=user.pk, feed_id=feed_id)
else: classifier_authors.rewind()
classifier_authors = list(MClassifierAuthor.objects(user_id=user.pk, feed_id=feed_id))
if classifier_titles is None:
classifier_titles = MClassifierTitle.objects(user_id=user.pk, feed_id=feed_id)
else: classifier_titles.rewind()
classifier_titles = list(MClassifierTitle.objects(user_id=user.pk, feed_id=feed_id))
if classifier_tags is None:
classifier_tags = MClassifierTag.objects(user_id=user.pk, feed_id=feed_id)
else: classifier_tags.rewind()
classifier_tags = list(MClassifierTag.objects(user_id=user.pk, feed_id=feed_id))
payload = {
'feeds': dict([(f.feed_id, f.score) for f in classifier_feeds]),

View file

@ -112,7 +112,7 @@ class OPMLImporter(Importer):
def process_outline(self, outline):
folders = []
for item in outline:
if not hasattr(item, 'xmlUrl'):
if not hasattr(item, 'xmlUrl') and hasattr(item, 'text'):
folder = item
# if hasattr(folder, 'text'):
# logging.info(' ---> [%s] ~FRNew Folder: %s' % (self.user, folder.text))

0
apps/mobile/__init__.py Normal file
View file

3
apps/mobile/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

23
apps/mobile/tests.py Normal file
View file

@ -0,0 +1,23 @@
"""
This file demonstrates two different styles of tests (one doctest and one
unittest). These will both pass when you run "manage.py test".
Replace these with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """
Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}

6
apps/mobile/urls.py Normal file
View file

@ -0,0 +1,6 @@
from django.conf.urls.defaults import *
from apps.mobile import views
urlpatterns = patterns('apps.mobile.views',
url(r'^$', views.index, name='mobile-index'),
)

15
apps/mobile/views.py Normal file
View file

@ -0,0 +1,15 @@
import os
import base64
import yaml
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template import RequestContext
from apps.profile.models import Profile
from apps.reader.models import UserSubscription, UserSubscriptionFolders
from utils import json_functions as json
from utils import log as logging
def index(request):
return render_to_response('mobile/mobile_workspace.xhtml', {},
context_instance=RequestContext(request))

View file

@ -7,7 +7,9 @@ class UserSubscriptionManager(models.Manager):
try:
return super(UserSubscriptionManager, self).get(*args, **kwargs)
except:
if 'feed' in kwargs:
if isinstance(kwargs.get('feed'), int):
feed_id = kwargs.get('feed')
elif 'feed' in kwargs:
feed_id = kwargs['feed'].pk
elif 'feed__pk' in kwargs:
feed_id = kwargs['feed__pk']

View file

@ -191,11 +191,11 @@ class UserSubscription(models.Model):
# if not silent:
# logging.info(' ---> [%s] Format stories: %s' % (self.user, datetime.datetime.now() - now))
classifier_feeds = MClassifierFeed.objects(user_id=self.user.pk, feed_id=self.feed.pk)
classifier_authors = MClassifierAuthor.objects(user_id=self.user.pk, feed_id=self.feed.pk)
classifier_titles = MClassifierTitle.objects(user_id=self.user.pk, feed_id=self.feed.pk)
classifier_tags = MClassifierTag.objects(user_id=self.user.pk, feed_id=self.feed.pk)
classifier_feeds = list(MClassifierFeed.objects(user_id=self.user.pk, feed_id=self.feed.pk))
classifier_authors = list(MClassifierAuthor.objects(user_id=self.user.pk, feed_id=self.feed.pk))
classifier_titles = list(MClassifierTitle.objects(user_id=self.user.pk, feed_id=self.feed.pk))
classifier_tags = list(MClassifierTag.objects(user_id=self.user.pk, feed_id=self.feed.pk))
# if not silent:
# logging.info(' ---> [%s] Classifiers: %s (%s)' % (self.user, datetime.datetime.now() - now, classifier_feeds.count() + classifier_authors.count() + classifier_tags.count() + classifier_titles.count()))
@ -204,13 +204,10 @@ class UserSubscription(models.Model):
}
for story in stories:
classifier_authors.rewind()
classifier_tags.rewind()
classifier_titles.rewind()
scores.update({
'author': apply_classifier_authors(classifier_authors, story),
'tags': apply_classifier_tags(classifier_tags, story),
'title': apply_classifier_titles(classifier_titles, story),
'author' : apply_classifier_authors(classifier_authors, story),
'tags' : apply_classifier_tags(classifier_tags, story),
'title' : apply_classifier_titles(classifier_titles, story),
})
max_score = max(scores['author'], scores['tags'], scores['title'])

View file

@ -63,6 +63,7 @@ def index(request):
active_count = UserSubscription.objects.filter(user=request.user, active=True).count() if authed else 0
train_count = UserSubscription.objects.filter(user=request.user, active=True, is_trained=False, feed__stories_last_month__gte=1).count() if authed else 0
recommended_feeds = RecommendedFeed.objects.filter(is_public=True, approved_date__lte=datetime.datetime.now()).select_related('feed')[:2]
unmoderated_feeds = RecommendedFeed.objects.filter(is_public=False, declined_date__isnull=True).select_related('feed')[:2]
statistics = MStatistics.all()
feedbacks = MFeedback.all()
@ -77,6 +78,7 @@ def index(request):
'train_count' : active_count - train_count,
'account_images' : range(1, 4),
'recommended_feeds' : recommended_feeds,
'unmoderated_feeds' : unmoderated_feeds,
'statistics' : statistics,
'feedbacks' : feedbacks,
'start_import_from_google_reader': request.session.get('import_from_google_reader', False),
@ -184,6 +186,7 @@ def load_feed_favicons(request):
def load_feeds_flat(request):
user = get_user(request)
include_favicons = request.REQUEST.get('include_favicons', False)
feeds = {}
try:
@ -197,14 +200,7 @@ def load_feeds_flat(request):
for sub in user_subs:
if sub.needs_unread_recalc:
sub.calculate_feed_scores(silent=True)
feeds[sub.feed.pk] = {
'id': sub.feed.pk,
'feed_title': sub.user_title or sub.feed.feed_title,
'feed_link': sub.feed.feed_link,
'ps': sub.unread_count_positive,
'nt': sub.unread_count_neutral,
'ng': sub.unread_count_negative,
}
feeds[sub.feed.pk] = sub.canonical(include_favicon=include_favicons)
folders = json.decode(folders.folders)
flat_folders = {}
@ -212,25 +208,24 @@ def load_feeds_flat(request):
def make_feeds_folder(items, parent_folder="", depth=0):
for item in items:
if isinstance(item, int) and item in feeds:
feed = feeds[item]
if not parent_folder:
parent_folder = ' '
if parent_folder in flat_folders:
flat_folders[parent_folder].append(feed)
flat_folders[parent_folder].append(item)
else:
flat_folders[parent_folder] = [feed]
flat_folders[parent_folder] = [item]
elif isinstance(item, dict):
for folder_name in item:
folder = item[folder_name]
flat_folder_name = "%s%s%s" % (
parent_folder,
" - " if parent_folder else "",
" - " if parent_folder and parent_folder != ' ' else "",
folder_name
)
make_feeds_folder(folder, flat_folder_name, depth+1)
make_feeds_folder(folders)
data = dict(flat_folders=flat_folders, user=user.username)
data = dict(flat_folders=flat_folders, feeds=feeds, user=user.username)
return data
@json.json_view
@ -278,16 +273,16 @@ def refresh_feeds(request):
@json.json_view
def load_single_feed(request, feed_id):
start = datetime.datetime.utcnow()
user = get_user(request)
offset = int(request.REQUEST.get('offset', 0))
limit = int(request.REQUEST.get('limit', 12))
page = int(request.REQUEST.get('page', 1))
if page:
offset = limit * (page-1)
start = time.time()
user = get_user(request)
offset = int(request.REQUEST.get('offset', 0))
limit = int(request.REQUEST.get('limit', 12))
page = int(request.REQUEST.get('page', 1))
dupe_feed_id = None
if not feed_id:
raise Http404
userstories_db = None
if page: offset = limit * (page-1)
if not feed_id: raise Http404
try:
feed = Feed.objects.get(id=feed_id)
@ -303,10 +298,12 @@ def load_single_feed(request, feed_id):
stories = feed.get_stories(offset, limit)
# Get intelligence classifier for user
classifier_feeds = MClassifierFeed.objects(user_id=user.pk, feed_id=feed_id)
classifier_authors = MClassifierAuthor.objects(user_id=user.pk, feed_id=feed_id)
classifier_titles = MClassifierTitle.objects(user_id=user.pk, feed_id=feed_id)
classifier_tags = MClassifierTag.objects(user_id=user.pk, feed_id=feed_id)
classifier_feeds = list(MClassifierFeed.objects(user_id=user.pk, feed_id=feed_id))
classifier_authors = list(MClassifierAuthor.objects(user_id=user.pk, feed_id=feed_id))
classifier_titles = list(MClassifierTitle.objects(user_id=user.pk, feed_id=feed_id))
classifier_tags = list(MClassifierTag.objects(user_id=user.pk, feed_id=feed_id))
checkpoint1 = time.time()
usersub = UserSubscription.objects.get(user=user, feed=feed)
userstories = []
@ -323,8 +320,9 @@ def load_single_feed(request, feed_id):
elif hasattr(us.story, 'id') and isinstance(us.story.id, unicode):
userstories.append(us.story.id) # TODO: Remove me after migration from story.id->guid
checkpoint2 = time.time()
for story in stories:
[x.rewind() for x in [classifier_feeds, classifier_authors, classifier_tags, classifier_titles]]
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now)
@ -348,6 +346,8 @@ def load_single_feed(request, feed_id):
'tags': apply_classifier_tags(classifier_tags, story),
'title': apply_classifier_titles(classifier_titles, story),
}
checkpoint3 = time.time()
# Intelligence
feed_tags = json.decode(feed.data.popular_tags) if feed.data.popular_tags else []
@ -358,14 +358,19 @@ def load_single_feed(request, feed_id):
if usersub:
usersub.feed_opens += 1
usersub.save()
diff = datetime.datetime.utcnow()-start
timediff = float("%s.%.2s" % (diff.seconds, (diff.microseconds / 1000)))
timediff = time.time()-start
last_update = relative_timesince(feed.last_update)
logging.user(request.user, "~FYLoading feed: ~SB%s%s ~SN(%s seconds)" % (
logging.user(request.user, "~FYLoading feed: ~SB%s%s ~SN(%.4s seconds)" % (
feed, ('~SN/p%s' % page) if page > 1 else '', timediff))
FeedLoadtime.objects.create(feed=feed, loadtime=timediff)
if timediff >= 1:
diff1 = checkpoint1-start
diff2 = checkpoint2-start
diff3 = checkpoint3-start
logging.user(request.user, "~FYSlow feed load: ~SB%.4s/%.4s(%s)/%.4s" % (
diff1, diff2, userstories_db and userstories_db.count(), diff3))
data = dict(stories=stories,
feed_tags=feed_tags,
feed_authors=feed_authors,
@ -429,7 +434,7 @@ def load_river_stories(request):
user = get_user(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', 0))+1
page = int(request.REQUEST.get('page', 1))
read_stories_count = int(request.REQUEST.get('read_stories_count', 0))
bottom_delta = datetime.timedelta(days=settings.DAYS_OF_UNREAD)

View file

@ -1,3 +1,4 @@
import datetime
from django import template
from apps.reader.models import UserSubscription
from utils.user_functions import get_user
@ -7,7 +8,7 @@ from apps.rss_feeds.models import MFeedIcon
register = template.Library()
@register.inclusion_tag('recommendations/render_recommended_feed.xhtml', takes_context=True)
def render_recommended_feed(context, recommended_feeds):
def render_recommended_feed(context, recommended_feeds, unmoderated=False):
user = get_user(context['user'])
usersub = None
@ -18,11 +19,13 @@ def render_recommended_feed(context, recommended_feeds):
if recommended_feed:
return {
'recommended_feed': recommended_feed,
'description': recommended_feed.description or recommended_feed.feed.data.feed_tagline,
'usersub': usersub,
'feed_icon': feed_icon and feed_icon[0],
'user': context['user'],
'has_next_page': len(recommended_feeds) > 1
'recommended_feed' : recommended_feed,
'description' : recommended_feed.description or recommended_feed.feed.data.feed_tagline,
'usersub' : usersub,
'feed_icon' : feed_icon and feed_icon[0],
'user' : context['user'],
'has_next_page' : len(recommended_feeds) > 1,
'unmoderated' : unmoderated,
'today' : datetime.datetime.now(),
}

View file

@ -4,5 +4,7 @@ from apps.recommendations import views
urlpatterns = patterns('',
url(r'^load_recommended_feed', views.load_recommended_feed, name='load-recommended-feed'),
url(r'^save_recommended_feed', views.save_recommended_feed, name='save-recommended-feed'),
url(r'^approve_feed', views.approve_feed, name='approve-recommended-feed'),
url(r'^decline_feed', views.decline_feed, name='decline-recommended-feed'),
url(r'^load_feed_info/(?P<feed_id>\d+)', views.load_feed_info, name='load-recommended-feed-info'),
)

View file

@ -1,3 +1,4 @@
import re
import datetime
from utils import log as logging
from django.http import HttpResponse
@ -7,17 +8,21 @@ from apps.recommendations.models import RecommendedFeed
from apps.reader.models import UserSubscription
from apps.rss_feeds.models import Feed, MFeedIcon
from utils import json_functions as json
from utils.user_functions import get_user, ajax_login_required
from utils.user_functions import get_user, ajax_login_required, admin_only
def load_recommended_feed(request):
user = get_user(request)
page = int(request.REQUEST.get('page', 0))
usersub = None
refresh = request.REQUEST.get('refresh')
now = datetime.datetime.now
user = get_user(request)
page = int(request.REQUEST.get('page', 0))
usersub = None
refresh = request.REQUEST.get('refresh')
now = datetime.datetime.now
unmoderated = request.REQUEST.get('unmoderated', False) == 'true'
recommended_feeds = RecommendedFeed.objects.filter(is_public=True, approved_date__lte=now)[page:page+2]
if unmoderated:
recommended_feeds = RecommendedFeed.objects.filter(is_public=False, declined_date__isnull=True)[page:page+2]
else:
recommended_feeds = RecommendedFeed.objects.filter(is_public=True, approved_date__lte=now)[page:page+2]
if recommended_feeds and request.user.is_authenticated():
usersub = UserSubscription.objects.filter(user=user, feed=recommended_feeds[0].feed)
if refresh != 'true' and page > 0:
@ -34,6 +39,8 @@ def load_recommended_feed(request):
'feed_icon' : feed_icon and feed_icon[0],
'has_next_page' : len(recommended_feeds) > 1,
'has_previous_page' : page != 0,
'unmoderated' : unmoderated,
'today' : datetime.datetime.now(),
}, context_instance=RequestContext(request))
else:
return HttpResponse("")
@ -71,4 +78,33 @@ def save_recommended_feed(request):
)
)
return dict(code=code if created else -1)
return dict(code=code if created else -1)
@admin_only
@ajax_login_required
def approve_feed(request):
feed_id = request.POST['feed_id']
feed = get_object_or_404(Feed, pk=int(feed_id))
date = request.POST['date']
recommended_feed = RecommendedFeed.objects.filter(feed=feed)[0]
year, month, day = re.search(r'(\d{4})-(\d{1,2})-(\d{1,2})', date).groups()
recommended_feed.is_public = True
recommended_feed.approved_date = datetime.date(int(year), int(month), int(day))
recommended_feed.save()
return load_recommended_feed(request)
@admin_only
@ajax_login_required
def decline_feed(request):
feed_id = request.POST['feed_id']
feed = get_object_or_404(Feed, pk=int(feed_id))
recommended_feeds = RecommendedFeed.objects.filter(feed=feed)
for recommended_feed in recommended_feeds:
recommended_feed.is_public = False
recommended_feed.declined_date = datetime.datetime.now()
recommended_feed.save()
return load_recommended_feed(request)

View file

@ -737,7 +737,6 @@ class Feed(models.Model):
story_published_now = story.get('published_now', False)
start_date = story_pub_date - datetime.timedelta(hours=8)
end_date = story_pub_date + datetime.timedelta(hours=8)
existing_stories.rewind()
for existing_story in existing_stories:
content_ratio = 0

View file

@ -117,11 +117,13 @@ class MFeedback(mongo.Document):
subject = mongo.StringField()
url = mongo.StringField()
style = mongo.StringField()
order = mongo.IntField()
meta = {
'collection': 'feedback',
'allow_inheritance': False,
'indexes': ['style'],
'ordering': ['order'],
}
def __unicode__(self):
@ -131,13 +133,16 @@ class MFeedback(mongo.Document):
def collect_feedback(cls):
data = urllib2.urlopen('https://getsatisfaction.com/newsblur/topics.widget').read()
data = json.decode(data[1:-1])
i = 0
if len(data):
cls.objects.delete()
for feedback in data:
feedback['order'] = i
i += 1
for removal in ['about', 'less than']:
if removal in feedback['date']:
feedback['date'] = feedback['date'].replace(removal, '')
[MFeedback.objects.create(**feedback) for feedback in data]
[cls.objects.create(**feedback) for feedback in data]
@classmethod
def all(cls):

92
config/mongodb.dev.conf Normal file
View file

@ -0,0 +1,92 @@
# mongodb.conf
# Where to store the data.
# Note: if you run mongodb as a non-root user (recommended) you may
# need to create and set permissions for this directory manually,
# e.g., if the parent directory isn't mutable by the mongodb user.
dbpath=/Users/conesus/newsblur/data/db/unsharded
#where to log
logpath=/Users/conesus/newsblur/data/unsharded.log
logappend=false
#port = 27017
slowms=100
rest = true
profile = 2
# Enables periodic logging of CPU utilization and I/O wait
#cpu = true
# Turn on/off security. Off is currently the default
noauth = true
#auth = true
# Verbose logging output.
verbose = true
# Inspect all client data for validity on receipt (useful for
# developing drivers)
#objcheck = true
# Enable db quota management
#quota = true
# Set oplogging level where n is
# 0=off (default)
# 1=W
# 2=R
# 3=both
# 7=W+some reads
#diaglog = 0
# Diagnostic/debugging option
#nocursors = true
# Ignore query hints
#nohints = true
# Disable the HTTP interface (Defaults to localhost:27018).
#nohttpinterface = true
# Turns off server-side scripting. This will result in greatly limited
# functionality
#noscripting = true
# Turns off table scans. Any query that would do a table scan fails.
#notablescan = true
# Disable data file preallocation.
#noprealloc = true
# Specify .ns file size for new databases.
# nssize = <size>
# Accout token for Mongo monitoring server.
#mms-token = <token>
# Server name for Mongo monitoring server.
#mms-name = <server-name>
# Ping interval for Mongo monitoring server.
#mms-interval = <seconds>
# Replication Options
# in master/slave replicated mongo databases, specify here whether
# this is a slave or master
#slave = true
#source = master.example.com
# Slave only: specify a single database to replicate
#only = master.example.com
# or
#master = true
#source = slave.example.com
# in replica set configuration, specify the name of the replica set
# replSet = setname
journal = true

7
fabfile.py vendored
View file

@ -44,6 +44,7 @@ def deploy():
run('git pull')
run('kill -HUP `cat logs/gunicorn.pid`')
run('curl -s http://www.newsblur.com > /dev/null')
run('curl -s http://www.newsblur.com/m/ > /dev/null')
run('curl -s http://www.newsblur.com/api/add_site_load_script/ABCDEF > /dev/null')
compress_media()
@ -54,6 +55,7 @@ def deploy_full():
run('./manage.py migrate')
run('sudo supervisorctl restart gunicorn')
run('curl -s http://www.newsblur.com > /dev/null')
run('curl -s http://www.newsblur.com/m/ > /dev/null')
compress_media()
@roles('web')
@ -62,6 +64,7 @@ def staging():
run('git pull')
run('kill -HUP `cat logs/gunicorn.pid`')
run('curl -s http://dev.newsblur.com > /dev/null')
run('curl -s http://dev.newsblur.com/m/ > /dev/null')
compress_media()
@roles('web')
@ -71,6 +74,7 @@ def staging_full():
run('./manage.py migrate')
run('kill -HUP `cat logs/gunicorn.pid`')
run('curl -s http://dev.newsblur.com > /dev/null')
run('curl -s http://dev.newsblur.com/m/ > /dev/null')
compress_media()
@roles('task')
@ -94,6 +98,9 @@ def compress_media():
with cd('media/js'):
run('rm -f *.gz')
run('for js in *-compressed-*.js; do gzip -9 $js -c > $js.gz; done;')
with cd('media/css/mobile'):
run('rm -f *.gz')
run('for css in *-compressed-*.css; do gzip -9 $css -c > $css.gz; done;')
with cd('media/css'):
run('rm -f *.gz')
run('for css in *-compressed-*.css; do gzip -9 $css -c > $css.gz; done;')

1
media/css/mobile/images Symbolic link
View file

@ -0,0 +1 @@
../../img/mobile

File diff suppressed because it is too large Load diff

297
media/css/mobile/mobile.css Normal file
View file

@ -0,0 +1,297 @@
/* ========== */
/* = Global = */
/* ========== */
body {
overflow-y: scroll !important;
}
.NB-favicon {
width: 16px;
height: 16px;
vertical-align: text-top;
}
.NB-story-tags {
overflow: hidden;
line-height: 12px;
height: 14px;
margin: 0 0 6px 0;
}
.NB-story-tag {
float: left;
font-weight: normal;
font-size: 9px;
padding: 0px 4px 1px;
margin: 0 2px 2px;
background-color: #E3DFD4;
color: #878787;
text-shadow: 0 1px 0 #E9E9E9;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
}
.NB-story-author {
color: #969696;
font-size: 10px;
text-transform: uppercase;
margin: 0 16px 8px 0;
text-shadow: 0 1px 0 #F9F9F9;
float: left;
}
.NB-story-date {
float: right;
font-size: 11px;
color: #252D6C;
}
.NB-story .NB-icon-score {
margin-top: 12px;
height: 16px;
width: 16px;
left: 4px;
top: 17px;
}
.NB-story.NB-read .NB-icon-score {
opacity: .2;
}
.NB-story.NB-score-positive .NB-icon-score {
background: transparent url('../../img/icons/silk/bullet_green.png') no-repeat 0 0;
}
.NB-story.NB-score-neutral .NB-icon-score {
background: transparent url('../../img/icons/silk/bullet_yellow.png') no-repeat 0 0;
}
.NB-story.NB-score-negative .NB-icon-score {
background: transparent url('../../img/icons/silk/bullet_red.png') no-repeat 0 0;
}
.NB-story .NB-story-title {
clear: left;
}
.ui-btn-up-c,
.ui-btn-hover-c {
border: 1px solid #E4E4E4;
color: #111;
background-color: #E0E0E0;
background-image: -moz-linear-gradient(top, #FCFCFC, #F3F3F3); /* FF3.6 */
background-image: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#F3F3F3)); /* Saf4+, Chrome */
background-image: linear-gradient(top, #FCFCFC, #F3F3F3);
}
.ui-btn-active {
text-shadow: 0 -1px 1px #145072;
border: 1px solid #155678;
background-color: #4596ce;
background-image: -moz-linear-gradient(top, #85bae4, #5393c5); /* FF3.6 */
background-image: -webkit-gradient(linear, left top, left bottom, from(#85bae4), to(#5393c5)); /* Saf4+, Chrome */
background-image: linear-gradient(top, #85bae4, #5393c5);
}
/* ============= */
/* = Feed List = */
/* ============= */
#NB-feed-list a {
padding-left: 36px;
padding-right: 36px;
}
#NB-feed-list .ui-li-icon {
width: 16px;
height: 16px;
margin-top: -2px;
}
#NB-feed-list .ui-li-count {
position: static;
float: right;
margin: 1px 2px 0 0;
padding: 3px 4px 2px;
white-space: normal;
border: none;
border-radius: 5px;
text-shadow: none;
}
#NB-feed-list .ui-li-count.ui-li-count-positive {
color: white;
background-color: #559F4D;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#559F4D), to(#3B7613));
background-image: -moz-linear-gradient(center bottom, #3B7613, #559F4D);
}
#NB-feed-list .ui-li-count.ui-li-count-neutral {
background-color: #F9C72A;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#F9C72A), to(#E4AB00));
background-image: -moz-linear-gradient(center bottom, #E4AB00, #F9C72A);
}
#NB-feed-list .ui-li-count.ui-li-count-negative {
color: white;
background-color: #CC2A2E;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#CC2A2E), to(#9B181B));
background-image: -moz-linear-gradient(center bottom, #9B181B, #CC2A2E);
}
/* ============== */
/* = Story List = */
/* ============== */
#NB-story-list .NB-story a {
padding-left: 24px;
padding-right: 36px;
}
#NB-story-list .NB-story-feed {
overflow: hidden;
text-shadow: 0 1px 0 #E9E9E9;
margin: 4px 0 0 0;
white-space: normal;
}
#NB-story-list .NB-story-feed .NB-story-feed-icon {
float: left;
margin: 0 4px 0 0;
}
#NB-story-list .NB-story-feed .NB-story-feed-icon img {
width: 16px;
height: 16px;
}
#NB-story-list .NB-story-feed .NB-story-feed-title {
float: left;
font-size: 12px;
margin: 2px 0 0 0;
color: #525252;
}
#NB-story-list .NB-read .NB-story-feed .NB-story-feed-title {
color: #8D8D8D;
}
#NB-story-list .NB-read .NB-story-feed img {
opacity: .6;
}
#NB-story-list .NB-story .NB-story-title {
text-shadow: 0 1px 0 #F3F3F3;
white-space: normal;
font-weight: bold;
}
#NB-story-list .NB-story.ui-btn-active .NB-story-title,
#NB-story-list .NB-story.ui-btn-active .NB-story-tags,
#NB-story-list .NB-story.ui-btn-active .NB-story-author,
#NB-story-list .NB-story.ui-btn-active .NB-story-date,
#NB-story-list .NB-story.ui-btn-active .NB-story-feed .NB-story-feed-title,
#NB-story-list .NB-read.ui-btn-active .NB-story-author,
#NB-story-list .NB-read.ui-btn-active .NB-story-date,
#NB-story-list .NB-read.ui-btn-active .NB-story-title,
#NB-story-list .NB-read.ui-btn-active .NB-story-feed .NB-story-feed-title {
text-shadow: inherit;
color: inherit;
}
#NB-story-list .NB-story.ui-btn-active .NB-story-feed .NB-story-feed-title,
#NB-story-list .NB-read.ui-btn-active .NB-story-feed .NB-story-feed-title {
text-shadow: 0 1px 0 #000;
}
#NB-story-list .NB-read .NB-story-title {
font-weight: normal;
color: #808080;
}
#NB-story-list .NB-read .NB-story-date {
color: #6D6D6D;
font-weight: normal;
}
#NB-story-list .NB-read .NB-story-author {
color: #B0B0B0;
}
#NB-story-list .NB-read .NB-story-tags {
opacity: .7;
}
#NB-story-list .NB-read.ui-btn-up-c,
#NB-story-list .NB-read.ui-btn-hover-c {
border: 1px solid #E4E4E4;
color: #111;
background-color: #E0E0E0;
background-image: -moz-linear-gradient(top, #FCFCFC, #F9F9F9); /* FF3.6 */
background-image: -webkit-gradient(linear, left top, left bottom, from(#FCFCFC), to(#F9F9F9)); /* Saf4+, Chrome */
background-image: linear-gradient(top, #FCFCFC, #F9F9F9);
}
/* ================ */
/* = Story Detail = */
/* ================ */
#NB-page-story .NB-story-feed-header {
margin: 8px 0;
}
#NB-page-story .ui-content {
padding: 0;
}
#NB-page-story .ui-header {
padding: 2px 0;
color: white;
font-size: 13px;
text-shadow: 1px 1px 0 #000;
}
#NB-page-story .ui-header.NB-inverse {
color: black;
text-shadow: 1px 1px 0 #E0E0E0;
}
#NB-page-story .ui-title {
text-overflow: ellipsis;
white-space: nowrap;
margin: 0 120px 0 106px;
}
#NB-page-story .ui-title .feed_title {
}
#NB-page-story .NB-favicon {
margin-right: 4px;
}
#NB-story-detail {
background-color: white;
}
#NB-story-detail .NB-story-header {
font-weight: bold;
font-size: 16px;
padding: 8px 24px 8px 24px;
background-color: #dadada;
background-image: -moz-linear-gradient(top, #EBEBEB, #CFCFCF); /* FF3.6 */
background-image: -ms-linear-gradient(top, #EBEBEB, #CFCFCF); /* IE10 */
background-image: -o-linear-gradient(top, #EBEBEB, #CFCFCF); /* Opera 11.10+ */
background-image: -webkit-gradient(linear, left top, left bottom, from(#EBEBEB), to(#CFCFCF)); /* Saf4+, Chrome */
background-image: -webkit-linear-gradient(top, #EBEBEB, #CFCFCF); /* Chrome 10+, Saf5.1+ */
background-image: linear-gradient(top, #EBEBEB, #CFCFCF);
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#EBEBEB', EndColorStr='#CFCFCF'); /* IE6IE9 */
border-bottom: 1px solid #ADADAD;
position: relative;
overflow: hidden;
}
#NB-story-detail .NB-story-header a {
text-decoration: none;
}
#NB-story-detail .NB-story-title {
color: #303030;
}
#NB-story-detail .NB-story-content {
text-shadow: none;
padding: 12px 24px;
color: #2b2b2b;
font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
line-height: 1.5em;
}
#NB-story-detail .NB-story-content p {
clear: both;
}
#NB-story-detail .NB-story-content blockquote {
background-color: #F0F0F0;
border-left: 1px solid #9B9B9B;
padding: .5em 2em;
margin: 0px;
}
#NB-story-detail .NB-story-content img {
max-width: 100%;
}
#NB-page-story .NB-icon-score {
margin-top: 12px;
height: 16px;
width: 16px;
left: 4px;
top: 17px;
}
#NB-page-story.NB-score-positive .NB-icon-score {
background: transparent url('../../img/icons/silk/bullet_green.png') no-repeat 0 0;
}
#NB-page-story.NB-score-neutral .NB-icon-score {
background: transparent url('../../img/icons/silk/bullet_yellow.png') no-repeat 0 0;
}
#NB-page-story.NB-score-negative .NB-icon-score {
background: transparent url('../../img/icons/silk/bullet_red.png') no-repeat 0 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -2,17 +2,20 @@
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1024</int>
<string key="IBDocument.SystemVersion">10H574</string>
<string key="IBDocument.InterfaceBuilderVersion">804</string>
<string key="IBDocument.SystemVersion">10J869</string>
<string key="IBDocument.InterfaceBuilderVersion">1305</string>
<string key="IBDocument.AppKitVersion">1038.35</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">123</string>
<string key="NS.object.0">300</string>
</object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="2"/>
<string>IBUITableViewCell</string>
<string>IBUIImageView</string>
<string>IBUILabel</string>
<string>IBProxyObject</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
@ -23,9 +26,7 @@
<object class="NSArray" key="dict.sortedKeys" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="dict.values" ref="0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
@ -50,8 +51,9 @@
<object class="IBUILabel" id="750430533">
<reference key="NSNextResponder" ref="994014136"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{133, 37}, {165, 15}}</string>
<string key="NSFrame">{{153, 37}, {145, 15}}</string>
<reference key="NSSuperview" ref="994014136"/>
<reference key="NSWindow"/>
<object class="NSColor" key="IBUIBackgroundColor" id="932003208">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAxIDEAA</bytes>
@ -86,6 +88,7 @@
<int key="NSvFlags">292</int>
<string key="NSFrame">{{20, 4}, {280, 31}}</string>
<reference key="NSSuperview" ref="994014136"/>
<reference key="NSWindow"/>
<reference key="IBUIBackgroundColor" ref="932003208"/>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">7</int>
@ -114,8 +117,9 @@
<object class="IBUILabel" id="194816084">
<reference key="NSNextResponder" ref="994014136"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{20, 37}, {113, 15}}</string>
<string key="NSFrame">{{20, 37}, {131, 15}}</string>
<reference key="NSSuperview" ref="994014136"/>
<reference key="NSWindow"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAxIDEgMAA</bytes>
@ -147,6 +151,7 @@
<int key="NSvFlags">292</int>
<string key="NSFrame">{{2, 12}, {16, 16}}</string>
<reference key="NSSuperview" ref="994014136"/>
<reference key="NSWindow"/>
<bool key="IBUIAutoresizesSubviews">NO</bool>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
@ -154,6 +159,7 @@
</object>
<string key="NSFrameSize">{300, 55}</string>
<reference key="NSSuperview" ref="749780469"/>
<reference key="NSWindow"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MCAwAA</bytes>
@ -167,6 +173,7 @@
</object>
<string key="NSFrameSize">{320, 55}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<reference key="IBUIBackgroundColor" ref="932003208"/>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUISeparatorStyle">1</int>
@ -297,17 +304,13 @@
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="dict.values" ref="0"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
<int key="maxID">23</int>
@ -366,173 +369,7 @@
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Classes/FeedDetailTableCell.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Source/NSObject+SBJSON.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Source/SBJsonWriter.h</string>
</object>
</object>
</object>
<object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSError.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSFileManager.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSKeyValueCoding.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSKeyValueObserving.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSKeyedArchiver.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSObject.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSRunLoop.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSThread.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSURL.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSURLConnection.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">QuartzCore.framework/Headers/CAAnimation.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">QuartzCore.framework/Headers/CALayer.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIAccessibility.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UINibLoading.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="603407049">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIResponder.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIImageView</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIImageView.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UILabel</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UILabel.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIResponder</string>
<string key="superclassName">NSObject</string>
<reference key="sourceIdentifier" ref="603407049"/>
</object>
<object class="IBPartialClassDescription">
<string key="className">UITableViewCell</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UITableViewCell.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UITextField.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIView</string>
<string key="superclassName">UIResponder</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIView.h</string>
<string key="minorKey">./Classes/FeedDetailTableCell.h</string>
</object>
</object>
</object>
@ -548,8 +385,7 @@
<integer value="3100" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<string key="IBDocument.LastKnownRelativeProjectPath">../NewsBlur.xcodeproj</string>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">123</string>
<string key="IBCocoaTouchPluginVersion">300</string>
</data>
</archive>

View file

@ -69,7 +69,7 @@
- (void)fetchFeedDetail {
if ([appDelegate.activeFeed objectForKey:@"id"] != nil) {
NSString *theFeedDetailURL = [[NSString alloc]
initWithFormat:@"http://nb.local.host:8000/reader/load_single_feed/?feed_id=%@",
initWithFormat:@"http://nb.local.host:8000/reader/feed/%@",
[appDelegate.activeFeed objectForKey:@"id"]];
//NSLog(@"Url: %@", theFeedDetailURL);
NSURL *urlFeedDetail = [NSURL URLWithString:theFeedDetailURL];
@ -150,12 +150,12 @@
NSDictionary *story = [appDelegate.activeFeedStories objectAtIndex:indexPath.row];
if ([[story objectForKey:@"story_authors"] class] != [NSNull class]) {
cell.storyAuthor.text = [story objectForKey:@"story_authors"];
cell.storyAuthor.text = [[story objectForKey:@"story_authors"] uppercaseString];
} else {
cell.storyAuthor.text = @"";
}
cell.storyTitle.text = [story objectForKey:@"story_title"];
cell.storyDate.text = [story objectForKey:@"long_parsed_date"];
cell.storyDate.text = [story objectForKey:@"short_parsed_date"];
if ([[story objectForKey:@"read_status"] intValue] != 1) {
// Unread story

View file

@ -57,6 +57,9 @@
- (void)setTitle:(NSString *)title;
- (void)showOriginalStory:(NSURL *)url;
- (void)closeOriginalStory;
- (int)indexOfNextStory;
- (int)indexOfPreviousStory;
- (int)indexOfActiveStory;
+ (int)computeStoryScore:(NSDictionary *)intelligence;
@end

View file

@ -119,6 +119,44 @@
[originalStoryViewController dismissModalViewControllerAnimated:YES];
}
- (int)indexOfNextStory {
int activeIndex = [self indexOfActiveStory];
NSUInteger activeFeedStoriesCount = [activeFeedStories count];
NSLog(@"ActiveStory: %d", activeIndex);
NSLog(@"ActiveStory: %d", activeFeedStoriesCount);
for (int i=activeIndex+1; i < activeFeedStoriesCount; i++) {
NSDictionary *story = [activeFeedStories objectAtIndex:i];
NSDecimalNumber *readStatus = [story objectForKey:@"read_status"];
NSLog(@"readStatus: %@", readStatus);
if (readStatus == 0) {
NSLog(@"NextStory: %d", i);
return i;
}
}
return -1;
}
- (int)indexOfPreviousStory {
NSInteger activeIndex = [self indexOfActiveStory];
for (int i=activeIndex-1; i >= 0; i--) {
NSDictionary *story = [activeFeedStories objectAtIndex:i];
if ([story objectForKey:@"read_status"] == 1) {
return i;
}
}
return -1;
}
- (int)indexOfActiveStory {
for (int i=0; i < [activeFeedStories count]; i++) {
NSDictionary *story = [activeFeedStories objectAtIndex:i];
if ([activeStory objectForKey:@"id"] == [story objectForKey:@"id"]) {
return i;
}
}
return -1;
}
+ (int)computeStoryScore:(NSDictionary *)intelligence {
int score = 0;
// int score_max = 0;

View file

@ -19,6 +19,7 @@
NSMutableData *responseData;
NSMutableArray * feedTitleList;
NSDictionary * dictFolders;
NSDictionary * dictFeeds;
NSMutableArray * dictFoldersArray;
IBOutlet UITableView * viewTableFeedTitles;
@ -40,6 +41,7 @@
@property (nonatomic, retain) NSMutableArray *feedTitleList;
@property (nonatomic, retain) NSMutableArray *dictFoldersArray;
@property (nonatomic, retain) NSDictionary *dictFolders;
@property (nonatomic, retain) NSDictionary *dictFeeds;
@property (nonatomic, retain) NSMutableData *responseData;
@end

View file

@ -22,6 +22,7 @@
@synthesize feedTitleList;
@synthesize dictFolders;
@synthesize dictFeeds;
@synthesize dictFoldersArray;
#pragma mark -
@ -38,6 +39,7 @@
- (void)viewDidLoad {
self.feedTitleList = [[NSMutableArray alloc] init];
self.dictFolders = [[NSDictionary alloc] init];
self.dictFeeds = [[NSDictionary alloc] init];
self.dictFoldersArray = [[NSMutableArray alloc] init];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Logout" style:UIBarButtonItemStylePlain target:self action:@selector(doLogoutButton)];
[appDelegate showNavigationBar:NO];
@ -84,6 +86,7 @@
- (void)dealloc {
[feedTitleList release];
[dictFolders release];
[dictFeeds release];
[dictFoldersArray release];
[appDelegate release];
[super dealloc];
@ -129,6 +132,7 @@
appDelegate.activeUsername = [results objectForKey:@"user"];
[appDelegate setTitle:[results objectForKey:@"user"]];
self.dictFolders = [results objectForKey:@"flat_folders"];
self.dictFeeds = [results objectForKey:@"feeds"];
//NSLog(@"Received Feeds: %@", dictFolders);
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"feed_title"
@ -139,19 +143,21 @@
for (id f in self.dictFolders) {
[self.dictFoldersArray addObject:f];
sortedArray = [[self.dictFolders objectForKey:f] sortedArrayUsingDescriptors:sortDescriptors];
[sortedFolders setValue:sortedArray forKey:f];
// NSArray *folder = [self.dictFolders objectForKey:f];
// NSLog(@"F: %@", f);
// NSLog(@"F: %@", folder);
// NSLog(@"F: %@", sortDescriptors);
// sortedArray = [folder sortedArrayUsingDescriptors:sortDescriptors];
// [sortedFolders setValue:sortedArray forKey:f];
}
self.dictFolders = sortedFolders;
// self.dictFolders = sortedFolders;
[self.dictFoldersArray sortUsingSelector:@selector(caseInsensitiveCompare:)];
[[self viewTableFeedTitles] reloadData];
[sortedFolders release];
[results release];
[jsonString release];
}
[jsonString release];
}
@ -219,8 +225,9 @@
// NSLog(@"Cell: %i: %@", section_index, f);
if (section_index == indexPath.section) {
NSArray *feeds = [self.dictFolders objectForKey:f];
// NSLog(@"Cell: %i: %@: %@", section_index, f, [feeds objectAtIndex:indexPath.row]);
cell.textLabel.text = [[feeds objectAtIndex:indexPath.row]
id feed_id = [feeds objectAtIndex:indexPath.row];
NSString *feed_id_str = [NSString stringWithFormat:@"%@",feed_id];
cell.textLabel.text = [[self.dictFeeds objectForKey:feed_id_str]
objectForKey:@"feed_title"];
return cell;
}
@ -233,10 +240,13 @@
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int section_index = 0;
for (id f in self.dictFoldersArray) {
// NSLog(@"Cell: %i: %@", section_index, f);
//NSLog(@"Cell: %i: %@", section_index, f);
if (section_index == indexPath.section) {
NSArray *feeds = [[NSArray alloc] initWithArray:[self.dictFolders objectForKey:f]];
[appDelegate setActiveFeed:[feeds objectAtIndex:indexPath.row]];
id feed_id = [feeds objectAtIndex:indexPath.row];
NSString *feed_id_str = [NSString stringWithFormat:@"%@",feed_id];
[appDelegate setActiveFeed:[self.dictFeeds
objectForKey:feed_id_str]];
[feeds release];
//NSLog(@"Active feed: %@", [appDelegate activeFeed]);
break;

View file

@ -14,7 +14,6 @@
<UIScrollViewDelegate> {
NewsBlurAppDelegate *appDelegate;
UIScrollView *scrollView;
UIWebView *webView;
UIToolbar *toolbar;
UIBarButtonItem *buttonPrevious;
@ -22,7 +21,6 @@
}
@property (nonatomic, retain) IBOutlet UIWebView *webView;
@property (nonatomic, retain) IBOutlet UIScrollView *scrollView;
@property (nonatomic, retain) IBOutlet UIToolbar *toolbar;
@property (nonatomic, retain) IBOutlet UIBarButtonItem *buttonPrevious;
@property (nonatomic, retain) IBOutlet UIBarButtonItem *buttonNext;
@ -32,5 +30,7 @@
- (void)showStory;
- (void)showOriginalSubview:(id)sender;
- (void)resizeWebView;
- (IBAction)doNextUnreadStory;
- (IBAction)doPreviousStory;
@end

View file

@ -16,7 +16,6 @@
@synthesize appDelegate;
@synthesize webView;
@synthesize scrollView;
@synthesize toolbar;
@synthesize buttonNext;
@synthesize buttonPrevious;
@ -52,11 +51,11 @@
NSString *urlString = @"http://nb.local.host:8000/reader/mark_story_as_read";
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[appDelegate.activeStory objectForKey:@"id"] forKey:@"story_id"];
[request setPostValue:[appDelegate.activeFeed objectForKey:@"id"] forKey:@"feed_id"];
[request setDelegate:self];
[request startAsynchronous];
// ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
// [request setPostValue:[appDelegate.activeStory objectForKey:@"id"] forKey:@"story_id"];
// [request setPostValue:[appDelegate.activeFeed objectForKey:@"id"] forKey:@"feed_id"];
// [request setDelegate:self];
// [request startAsynchronous];
}
@ -78,7 +77,7 @@
- (void)showStory {
NSLog(@"Loaded Story view: %@", [appDelegate.activeStory objectForKey:@"story_title"]);
// NSLog(@"Loaded Story view: %@", appDelegate.activeStory);
NSString *imgCssString = [NSString stringWithFormat:@"<style>"
"body {"
" line-height: 18px;"
@ -103,16 +102,75 @@
" font-weight: bold;"
" background-color: #E0E0E0;"
" border-bottom: 1px solid #A0A0A0;"
" padding: 12px 12px;"
" padding: 12px 12px 8px;"
" text-shadow: 1px 1px 0 #EFEFEF;"
"}"
".NB-story {"
" margin: 12px;"
"}"
".NB-story-author {"
" color: #969696;"
" font-size: 10px;"
" text-transform: uppercase;"
" margin: 0 16px 4px 0;"
" text-shadow: 0 1px 0 #F9F9F9;"
" float: left;"
"}"
".NB-story-tags {"
" clear: both;"
" overflow: hidden;"
" line-height: 12px;"
" height: 14px;"
" margin: 6px 0 0 0;"
" text-transform: uppercase;"
"}"
".NB-story-tag {"
" float: left;"
" font-weight: normal;"
" font-size: 9px;"
" padding: 0px 4px 0px;"
" margin: 0 4px 2px 0;"
" background-color: #C6CBC3;"
" color: #505050;"
" text-shadow: 0 1px 0 #E7E7E7;"
" border-radius: 4px;"
" -moz-border-radius: 4px;"
" -webkit-border-radius: 4px;"
"}"
".NB-story-date {"
" float: right;"
" font-size: 11px;"
" color: #252D6C;"
"}"
".NB-story-title {"
" clear: left;"
"}"
"</style>"];
NSString *story_author = @"";
if ([appDelegate.activeStory objectForKey:@"story_authors"]) {
NSString *author = [NSString stringWithFormat:@"%@",[appDelegate.activeStory objectForKey:@"story_authors"]];
if (author && ![author isEqualToString:@"<null>"]) {
story_author = [NSString stringWithFormat:@"<div class=\"NB-story-author\">%@</div>",author];
}
}
NSString *story_tags = @"";
if ([appDelegate.activeStory objectForKey:@"story_tags"]) {
NSArray *tag_array = [appDelegate.activeStory objectForKey:@"story_tags"];
if ([tag_array count] > 0) {
story_tags = [NSString stringWithFormat:@"<div class=\"NB-story-tags\"><div class=\"NB-story-tag\">%@</div></div>",
[tag_array componentsJoinedByString:@"</div><div class=\"NB-story-tag\">"]];
}
}
NSString *storyHeader = [NSString stringWithFormat:@"<div class=\"NB-header\">"
"<div class=\"NB-story-date\">%@</div>"
"%@"
"</div>", [appDelegate.activeStory objectForKey:@"story_title"]];
"<div class=\"NB-story-title\">%@</div>"
"%@"
"</div>",
[story_tags length] ? [appDelegate.activeStory objectForKey:@"long_parsed_date"] : [appDelegate.activeStory objectForKey:@"short_parsed_date"],
story_author,
[appDelegate.activeStory objectForKey:@"story_title"],
story_tags];
NSString *htmlString = [NSString stringWithFormat:@"%@ %@ <div class=\"NB-story\">%@</div>",
imgCssString, storyHeader,
[appDelegate.activeStory objectForKey:@"story_content"]];
@ -123,6 +181,38 @@
}
- (IBAction)doNextUnreadStory {
int nextIndex = [appDelegate indexOfNextStory];
if (nextIndex == -1) {
} else {
[appDelegate setActiveStory:[[appDelegate activeFeedStories] objectAtIndex:nextIndex]];
[self showStory];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:.5];
[UIView setAnimationBeginsFromCurrentState:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:NO];
[UIView commitAnimations];
}
}
- (IBAction)doPreviousStory {
NSInteger nextIndex = [appDelegate indexOfPreviousStory];
if (nextIndex == -1) {
} else {
[appDelegate setActiveStory:[[appDelegate activeFeedStories] objectAtIndex:nextIndex]];
[self showStory];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:.5];
[UIView setAnimationBeginsFromCurrentState:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:self.view cache:NO];
[UIView commitAnimations];
}
}
- (void)showOriginalSubview:(id)sender {
NSURL *url = [NSURL URLWithString:[appDelegate.activeStory
objectForKey:@"story_permalink"]];
@ -157,29 +247,11 @@
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
[self resizeWebView];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self resizeWebView];
}
- (void)resizeWebView {
CGRect frame = webView.frame;
frame.size.height = 1;
webView.frame = frame;
CGSize fittingSize = [webView sizeThatFits:CGSizeZero];
frame.size = fittingSize;
webView.frame = frame;
NSLog(@"heights: %f / %f", frame.size.width, frame.size.height, toolbar.frame.size.height);
toolbar.frame = CGRectMake(0, webView.frame.size.height, toolbar.frame.size.width, toolbar.frame.size.height);
webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
scrollView.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
}
- (void)dealloc {
[appDelegate release];
[webView release];

View file

@ -2,17 +2,22 @@
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1024</int>
<string key="IBDocument.SystemVersion">10J567</string>
<string key="IBDocument.InterfaceBuilderVersion">804</string>
<string key="IBDocument.SystemVersion">10J869</string>
<string key="IBDocument.InterfaceBuilderVersion">1305</string>
<string key="IBDocument.AppKitVersion">1038.35</string>
<string key="IBDocument.HIToolboxVersion">462.00</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">123</string>
<string key="NS.object.0">300</string>
</object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="14"/>
<string>IBUIWebView</string>
<string>IBUIBarButtonItem</string>
<string>IBUIToolbar</string>
<string>IBUIProgressView</string>
<string>IBUIView</string>
<string>IBProxyObject</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
@ -23,9 +28,7 @@
<object class="NSArray" key="dict.sortedKeys" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="dict.values" ref="0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
@ -42,67 +45,88 @@
<int key="NSvFlags">292</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIScrollView" id="55113995">
<object class="IBUIWebView" id="506862915">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">268</int>
<int key="NSvFlags">274</int>
<string key="NSFrameSize">{320, 416}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="155973878"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAxIDEAA</bytes>
</object>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIDataDetectorTypes">1</int>
<bool key="IBUIDetectsPhoneNumbers">YES</bool>
</object>
<object class="IBUIToolbar" id="155973878">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">266</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIWebView" id="279038753">
<reference key="NSNextResponder" ref="55113995"/>
<int key="NSvFlags">274</int>
<string key="NSFrameSize">{320, 466}</string>
<reference key="NSSuperview" ref="55113995"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAxIDEAA</bytes>
</object>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIMultipleTouchEnabled">YES</bool>
<object class="IBUIProgressView" id="484194819">
<reference key="NSNextResponder" ref="155973878"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{111, 17}, {78, 11}}</string>
<reference key="NSSuperview" ref="155973878"/>
<reference key="NSWindow"/>
<bool key="IBUIOpaque">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIDataDetectorTypes">15</int>
<bool key="IBUIDetectsPhoneNumbers">YES</bool>
</object>
<object class="IBUIToolbar" id="887718764">
<reference key="NSNextResponder" ref="55113995"/>
<int key="NSvFlags">298</int>
<string key="NSFrame">{{0, 415}, {320, 44}}</string>
<reference key="NSSuperview" ref="55113995"/>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<int key="IBUIContentMode">6</int>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<object class="NSMutableArray" key="IBUIItems">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIBarButtonItem" id="809166513">
<string key="IBUITitle">Item</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIStyle">1</int>
<reference key="IBUIToolbar" ref="887718764"/>
</object>
<object class="IBUIBarButtonItem" id="1040742603">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<reference key="IBUIToolbar" ref="887718764"/>
<int key="IBUISystemItemIdentifier">5</int>
</object>
<object class="IBUIBarButtonItem" id="54550830">
<string key="IBUITitle">Item</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIStyle">1</int>
<reference key="IBUIToolbar" ref="887718764"/>
</object>
</object>
<float key="IBUIProgress">0.5</float>
<int key="IBUIProgressViewStyle">1</int>
</object>
</object>
<string key="NSFrameSize">{320, 466}</string>
<string key="NSFrame">{{0, 416}, {320, 44}}</string>
<reference key="NSSuperview" ref="191373211"/>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIMultipleTouchEnabled">YES</bool>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="484194819"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
</object>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBUIDelaysContentTouches">NO</bool>
<bool key="IBUICanCancelContentTouches">NO</bool>
<object class="NSMutableArray" key="IBUIItems">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIBarButtonItem" id="542821156">
<string key="IBUITitle">Previous</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIStyle">1</int>
<reference key="IBUIToolbar" ref="155973878"/>
</object>
<object class="IBUIBarButtonItem" id="936960182">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<reference key="IBUIToolbar" ref="155973878"/>
<int key="IBUISystemItemIdentifier">5</int>
</object>
<object class="IBUIBarButtonItem" id="812131495">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<reference key="IBUICustomView" ref="484194819"/>
<reference key="IBUIToolbar" ref="155973878"/>
</object>
<object class="IBUIBarButtonItem" id="284060761">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<reference key="IBUIToolbar" ref="155973878"/>
<int key="IBUISystemItemIdentifier">5</int>
</object>
<object class="IBUIBarButtonItem" id="1065495688">
<string key="IBUITitle">Next unread</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIStyle">1</int>
<reference key="IBUIToolbar" ref="155973878"/>
</object>
</object>
<object class="NSColor" key="IBUITintColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MC4yMjcwMjkxMjggMC4zNjIxMzU3NzY0IDAuNDU2NTIxNzM5MQA</bytes>
</object>
</object>
</object>
<string key="NSFrameSize">{320, 460}</string>
<string key="NSFrame">{{0, 20}, {320, 460}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="506862915"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
@ -117,69 +141,69 @@
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">webView</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="279038753"/>
</object>
<int key="connectionID">17</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">buttonNext</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="54550830"/>
<reference key="destination" ref="1065495688"/>
</object>
<int key="connectionID">32</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">buttonPrevious</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="809166513"/>
</object>
<int key="connectionID">33</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="55113995"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">34</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">scrollView</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="55113995"/>
</object>
<int key="connectionID">35</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="279038753"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">36</int>
<int key="connectionID">50</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">toolbar</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="887718764"/>
<reference key="destination" ref="155973878"/>
</object>
<int key="connectionID">37</int>
<int key="connectionID">51</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="506862915"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">52</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">webView</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="506862915"/>
</object>
<int key="connectionID">54</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">view</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="55113995"/>
<reference key="destination" ref="191373211"/>
</object>
<int key="connectionID">38</int>
<int key="connectionID">55</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">buttonPrevious</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="542821156"/>
</object>
<int key="connectionID">56</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchEventConnection" key="connection">
<string key="label">doPreviousStory</string>
<reference key="source" ref="542821156"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">57</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchEventConnection" key="connection">
<string key="label">doNextUnreadStory</string>
<reference key="source" ref="1065495688"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">58</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
@ -196,7 +220,8 @@
<reference key="object" ref="191373211"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="55113995"/>
<reference ref="506862915"/>
<reference ref="155973878"/>
</object>
<reference key="parent" ref="0"/>
</object>
@ -212,45 +237,56 @@
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">19</int>
<reference key="object" ref="55113995"/>
<int key="objectID">39</int>
<reference key="object" ref="506862915"/>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">42</int>
<reference key="object" ref="155973878"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="279038753"/>
<reference ref="887718764"/>
<reference ref="542821156"/>
<reference ref="1065495688"/>
<reference ref="936960182"/>
<reference ref="812131495"/>
<reference ref="284060761"/>
</object>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">14</int>
<reference key="object" ref="279038753"/>
<reference key="parent" ref="55113995"/>
<int key="objectID">43</int>
<reference key="object" ref="542821156"/>
<reference key="parent" ref="155973878"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">23</int>
<reference key="object" ref="887718764"/>
<int key="objectID">44</int>
<reference key="object" ref="1065495688"/>
<reference key="parent" ref="155973878"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">45</int>
<reference key="object" ref="936960182"/>
<reference key="parent" ref="155973878"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">47</int>
<reference key="object" ref="812131495"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="809166513"/>
<reference ref="54550830"/>
<reference ref="1040742603"/>
<reference ref="484194819"/>
</object>
<reference key="parent" ref="55113995"/>
<reference key="parent" ref="155973878"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">24</int>
<reference key="object" ref="809166513"/>
<reference key="parent" ref="887718764"/>
<int key="objectID">46</int>
<reference key="object" ref="484194819"/>
<reference key="parent" ref="812131495"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">25</int>
<reference key="object" ref="54550830"/>
<reference key="parent" ref="887718764"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">26</int>
<reference key="object" ref="1040742603"/>
<reference key="parent" ref="887718764"/>
<int key="objectID">48</int>
<reference key="object" ref="284060761"/>
<reference key="parent" ref="155973878"/>
</object>
</object>
</object>
@ -262,15 +298,13 @@
<string>-2.CustomClassName</string>
<string>1.IBEditorWindowLastContentRect</string>
<string>1.IBPluginDependency</string>
<string>14.IBPluginDependency</string>
<string>14.IBViewBoundsToFrameTransform</string>
<string>19.IBPluginDependency</string>
<string>19.IBViewBoundsToFrameTransform</string>
<string>23.IBPluginDependency</string>
<string>23.IBViewBoundsToFrameTransform</string>
<string>24.IBPluginDependency</string>
<string>25.IBPluginDependency</string>
<string>26.IBPluginDependency</string>
<string>39.IBPluginDependency</string>
<string>42.IBPluginDependency</string>
<string>43.IBPluginDependency</string>
<string>44.IBPluginDependency</string>
<string>45.IBPluginDependency</string>
<string>46.IBPluginDependency</string>
<string>48.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
@ -279,17 +313,9 @@
<string>{{751, 380}, {320, 480}}</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAABBoAAAw/IAAA</bytes>
</object>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAAAAAAAAw+UAAA</bytes>
</object>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<object class="NSAffineTransform">
<bytes key="NSTransformStruct">P4AAAL+AAAAAAAAAw8UAAA</bytes>
</object>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
@ -298,20 +324,16 @@
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="dict.values" ref="0"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
<int key="maxID">38</int>
<int key="maxID">58</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
@ -374,7 +396,7 @@
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Classes/FeedDetailViewController.h</string>
<string key="minorKey">./Classes/FeedDetailViewController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
@ -421,7 +443,7 @@
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Classes/LoginViewController.h</string>
<string key="minorKey">./Classes/LoginViewController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
@ -438,23 +460,9 @@
<string key="candidateClassName">NewsBlurAppDelegate</string>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="763602146">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Classes/NewsBlurViewController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Source/NSObject+SBJSON.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Source/SBJsonWriter.h</string>
<string key="minorKey">./Classes/LogoutDelegate.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
@ -536,7 +544,7 @@
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Classes/NewsBlurAppDelegate.h</string>
<string key="minorKey">./Classes/NewsBlurAppDelegate.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
@ -606,7 +614,10 @@
</object>
</object>
</object>
<reference key="sourceIdentifier" ref="763602146"/>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/NewsBlurViewController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">OriginalStoryViewController</string>
@ -663,21 +674,42 @@
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Classes/OriginalStoryViewController.h</string>
<string key="minorKey">./Classes/OriginalStoryViewController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">StoryDetailViewController</string>
<string key="superclassName">UIViewController</string>
<object class="NSMutableDictionary" key="actions">
<string key="NS.key.0">showOriginalSubview:</string>
<string key="NS.object.0">id</string>
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>doNextUnreadStory</string>
<string>doPreviousStory</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>id</string>
<string>id</string>
</object>
</object>
<object class="NSMutableDictionary" key="actionInfosByName">
<string key="NS.key.0">showOriginalSubview:</string>
<object class="IBActionInfo" key="NS.object.0">
<string key="name">showOriginalSubview:</string>
<string key="candidateClassName">id</string>
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>doNextUnreadStory</string>
<string>doPreviousStory</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBActionInfo">
<string key="name">doNextUnreadStory</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBActionInfo">
<string key="name">doPreviousStory</string>
<string key="candidateClassName">id</string>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="outlets">
@ -687,7 +719,6 @@
<string>appDelegate</string>
<string>buttonNext</string>
<string>buttonPrevious</string>
<string>scrollView</string>
<string>toolbar</string>
<string>webView</string>
</object>
@ -696,7 +727,6 @@
<string>NewsBlurAppDelegate</string>
<string>UIBarButtonItem</string>
<string>UIBarButtonItem</string>
<string>UIScrollView</string>
<string>UIToolbar</string>
<string>UIWebView</string>
</object>
@ -708,7 +738,6 @@
<string>appDelegate</string>
<string>buttonNext</string>
<string>buttonPrevious</string>
<string>scrollView</string>
<string>toolbar</string>
<string>webView</string>
</object>
@ -726,10 +755,6 @@
<string key="name">buttonPrevious</string>
<string key="candidateClassName">UIBarButtonItem</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">scrollView</string>
<string key="candidateClassName">UIScrollView</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">toolbar</string>
<string key="candidateClassName">UIToolbar</string>
@ -742,269 +767,7 @@
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Classes/StoryDetailViewController.h</string>
</object>
</object>
</object>
<object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSError.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSFileManager.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSKeyValueCoding.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSKeyValueObserving.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSKeyedArchiver.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSObject.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSRunLoop.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSThread.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSURL.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">Foundation.framework/Headers/NSURLConnection.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">QuartzCore.framework/Headers/CAAnimation.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">QuartzCore.framework/Headers/CALayer.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIAccessibility.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UINibLoading.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="354476810">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIResponder.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIBarButtonItem</string>
<string key="superclassName">UIBarItem</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIBarButtonItem.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIBarItem</string>
<string key="superclassName">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIBarItem.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIControl</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIControl.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UINavigationController</string>
<string key="superclassName">UIViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="963649563">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UINavigationController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIResponder</string>
<string key="superclassName">NSObject</string>
<reference key="sourceIdentifier" ref="354476810"/>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIScrollView</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIScrollView.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UISearchBar</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UISearchBar.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UISearchDisplayController</string>
<string key="superclassName">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UISearchDisplayController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UISlider</string>
<string key="superclassName">UIControl</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UISlider.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UITableView</string>
<string key="superclassName">UIScrollView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UITableView.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UITextField</string>
<string key="superclassName">UIControl</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="117887094">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UITextField.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIToolbar</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIToolbar.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIView</string>
<reference key="sourceIdentifier" ref="117887094"/>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIView</string>
<string key="superclassName">UIResponder</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIView.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<reference key="sourceIdentifier" ref="963649563"/>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIPopoverController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UISplitViewController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UITabBarController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIViewController</string>
<string key="superclassName">UIResponder</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIViewController.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIWebView</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIWebView.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">UIWindow</string>
<string key="superclassName">UIView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">UIKit.framework/Headers/UIWindow.h</string>
<string key="minorKey">./Classes/StoryDetailViewController.h</string>
</object>
</object>
</object>
@ -1020,8 +783,7 @@
<integer value="3000" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<string key="IBDocument.LastKnownRelativeProjectPath">../NewsBlur.xcodeproj</string>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">123</string>
<string key="IBCocoaTouchPluginVersion">300</string>
</data>
</archive>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
type = "1"
version = "1.0">
</Bucket>

View file

@ -1,5 +1,5 @@
/*!
* jQuery JavaScript Library v1.6.1pre Live From Git (8bb6e95b66413c484006288691a82c44ba50554e)
* jQuery JavaScript Library v1.6.1
* http://jquery.com/
*
* Copyright 2011, John Resig
@ -11,7 +11,7 @@
* Copyright 2011, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Sun May 8 01:20:01 UTC 2011
* Date: Thu May 12 15:04:36 2011 -0400
*/
(function( window, undefined ) {
@ -204,7 +204,7 @@ jQuery.fn = jQuery.prototype = {
selector: "",
// The current version of jQuery being used
jquery: "1.6.1pre Live From Git (8bb6e95b66413c484006288691a82c44ba50554e)",
jquery: "1.6.1",
// The default length of a jQuery object is 0
length: 0,
@ -1055,7 +1055,7 @@ jQuery.extend({
if ( jQuery.isFunction( fn ) ) {
deferred[ handler ](function() {
returned = fn.apply( this, arguments );
if ( jQuery.isFunction( returned.promise ) ) {
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise().then( newDefer.resolve, newDefer.reject );
} else {
newDefer[ action ]( returned );
@ -1137,6 +1137,7 @@ jQuery.extend({
jQuery.support = (function() {
var div = document.createElement( "div" ),
documentElement = document.documentElement,
all,
a,
select,
@ -1284,7 +1285,7 @@ jQuery.support = (function() {
body.style[ i ] = bodyStyle[ i ];
}
body.appendChild( div );
document.documentElement.appendChild( body );
documentElement.insertBefore( body, documentElement.firstChild );
// Check if a disconnected checkbox will retain its checked
// value of true after appended to the DOM (IE6/7)
@ -1339,12 +1340,12 @@ jQuery.support = (function() {
marginDiv.style.marginRight = "0";
div.appendChild( marginDiv );
support.reliableMarginRight =
( parseInt( document.defaultView.getComputedStyle( marginDiv, null ).marginRight, 10 ) || 0 ) === 0;
( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
}
// Remove the body element we added
body.innerHTML = "";
document.documentElement.removeChild( body );
documentElement.removeChild( body );
// Technique from Juriy Zaytsev
// http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
@ -1475,7 +1476,7 @@ jQuery.extend({
}
if ( data !== undefined ) {
thisCache[ name ] = data;
thisCache[ jQuery.camelCase( name ) ] = data;
}
// TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
@ -1485,7 +1486,7 @@ jQuery.extend({
return thisCache[ internalKey ] && thisCache[ internalKey ].events;
}
return getByName ? thisCache[ name ] : thisCache;
return getByName ? thisCache[ jQuery.camelCase( name ) ] : thisCache;
},
removeData: function( elem, name, pvt /* Internal Use Only */ ) {
@ -2175,10 +2176,15 @@ jQuery.extend({
if ( pass && name in jQuery.attrFn ) {
return jQuery( elem )[ name ]( value );
}
// Fallback to prop when attributes are not supported
if ( !("getAttribute" in elem) ) {
return jQuery.prop( elem, name, value );
}
var ret, hooks,
notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
// Normalize the name if needed
name = notxml && jQuery.attrFix[ name ] || name;
@ -2255,7 +2261,7 @@ jQuery.extend({
// Setting the type on a radio button after the value resets the value in IE6-9
// Reset value to it's default in case type is set after value
// This is for element creation
var val = elem.getAttribute("value");
var val = elem.value;
elem.setAttribute( "type", value );
if ( val ) {
elem.value = val;
@ -2278,7 +2284,7 @@ jQuery.extend({
}
}
},
propFix: {
tabindex: "tabIndex",
readonly: "readOnly",
@ -2296,32 +2302,32 @@ jQuery.extend({
prop: function( elem, name, value ) {
var nType = elem.nodeType;
// don't get/set properties on text, comment and attribute nodes
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
return undefined;
}
var ret, hooks,
notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
// Try to normalize/fix the name
name = notxml && jQuery.propFix[ name ] || name;
hooks = jQuery.propHooks[ name ];
if ( value !== undefined ) {
if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
return ret;
} else {
return (elem[ name ] = value);
}
} else {
if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== undefined ) {
return ret;
} else {
return elem[ name ];
}
@ -2359,6 +2365,24 @@ boolHook = {
}
};
// Use the value property for back compat
// Use the formHook for button elements in IE6/7 (#1954)
jQuery.attrHooks.value = {
get: function( elem, name ) {
if ( formHook && jQuery.nodeName( elem, "button" ) ) {
return formHook.get( elem, name );
}
return elem.value;
},
set: function( elem, value, name ) {
if ( formHook && jQuery.nodeName( elem, "button" ) ) {
return formHook.set( elem, value, name );
}
// Does not return so that setAttribute is also used
elem.value = value;
}
};
// IE6/7 do not support getting/setting some attributes with get/setAttribute
if ( !jQuery.support.getSetAttribute ) {
@ -2366,12 +2390,9 @@ if ( !jQuery.support.getSetAttribute ) {
jQuery.attrFix = jQuery.propFix;
// Use this for any attribute on a form in IE6/7
formHook = jQuery.attrHooks.name = jQuery.attrHooks.value = jQuery.valHooks.button = {
formHook = jQuery.attrHooks.name = jQuery.valHooks.button = {
get: function( elem, name ) {
var ret;
if ( name === "value" && !jQuery.nodeName( elem, "button" ) ) {
return elem.getAttribute( name );
}
ret = elem.getAttributeNode( name );
// Return undefined if nodeValue is empty string
return ret && ret.nodeValue !== "" ?
@ -3126,6 +3147,9 @@ var withinElement = function( event ) {
// Check if mouse(over|out) are still within the same parent element
var parent = event.relatedTarget;
// set the correct event type
event.type = event.data;
// Firefox sometimes assigns relatedTarget a XUL element
// which we cannot access the parentNode property of
try {
@ -3135,15 +3159,13 @@ var withinElement = function( event ) {
if ( parent && parent !== document && !parent.parentNode ) {
return;
}
// Traverse up the tree
while ( parent && parent !== this ) {
parent = parent.parentNode;
}
if ( parent !== this ) {
// set the correct event type
event.type = event.data;
// handle event if we actually just moused on to a non sub-element
jQuery.event.handle.apply( this, arguments );
}
@ -4331,7 +4353,8 @@ var Expr = Sizzle.selectors = {
},
reset: function( elem ) {
return elem.nodeName.toLowerCase() === "input" && "reset" === elem.type;
var name = elem.nodeName.toLowerCase();
return (name === "input" || name === "button") && "reset" === elem.type;
},
button: function( elem ) {
@ -4597,6 +4620,16 @@ if ( document.documentElement.compareDocumentPosition ) {
} else {
sortOrder = function( a, b ) {
// The nodes are identical, we can exit early
if ( a === b ) {
hasDuplicate = true;
return 0;
// Fallback to using sourceIndex (in IE) if it's available on both nodes
} else if ( a.sourceIndex && b.sourceIndex ) {
return a.sourceIndex - b.sourceIndex;
}
var al, bl,
ap = [],
bp = [],
@ -4604,13 +4637,8 @@ if ( document.documentElement.compareDocumentPosition ) {
bup = b.parentNode,
cur = aup;
// The nodes are identical, we can exit early
if ( a === b ) {
hasDuplicate = true;
return 0;
// If the nodes are siblings (or identical) we can do a quick check
} else if ( aup === bup ) {
if ( aup === bup ) {
return siblingCheck( a, b );
// If no parents were found then the nodes are disconnected
@ -5434,6 +5462,7 @@ var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
// checked="checked" or checked
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
rscriptType = /\/(java|ecma)script/i,
rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
wrapMap = {
option: [ 1, "<select multiple='multiple'>", "</select>" ],
legend: [ 1, "<fieldset>", "</fieldset>" ],
@ -5924,7 +5953,7 @@ jQuery.each({
function getAll( elem ) {
if ( "getElementsByTagName" in elem ) {
return elem.getElementsByTagName( "*" );
} else if ( "querySelectorAll" in elem ) {
return elem.querySelectorAll( "*" );
@ -6162,7 +6191,7 @@ function evalScript( i, elem ) {
dataType: "script"
});
} else {
jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
}
if ( elem.parentNode ) {
@ -8039,6 +8068,9 @@ jQuery.fn.extend({
return this.each( optall.complete, [ false ] );
}
// Do not change referenced properties as per-property easing will be lost
prop = jQuery.extend( {}, prop );
return this[ optall.queue === false ? "each" : "queue" ](function() {
// XXX 'this' does not always have a nodeName when running the
// test suite
@ -8071,7 +8103,7 @@ jQuery.fn.extend({
// easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
if ( jQuery.isArray( val ) ) {
opt.animatedProperties[ name ] = val[ 1 ];
val = val[ 0 ];
val = prop[ name ] = val[ 0 ];
} else {
opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
}
@ -8168,7 +8200,6 @@ jQuery.fn.extend({
if ( !gotoEnd ) {
jQuery._unmark( true, this );
}
// go in reverse order so anything added to the queue during the loop is ignored
while ( i-- ) {
if ( timers[i].elem === this ) {
if (gotoEnd) {
@ -8432,11 +8463,9 @@ jQuery.fx.prototype = {
jQuery.extend( jQuery.fx, {
tick: function() {
var timers = jQuery.timers,
i = timers.length;
while ( i-- ) {
for ( var timers = jQuery.timers, i = 0 ; i < timers.length ; ++i ) {
if ( !timers[i]() ) {
timers.splice(i, 1);
timers.splice(i--, 1);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,365 @@
(function($) {
NEWSBLUR.MobileReader = function() {
// ===========
// = Globals =
// ===========
this.model = NEWSBLUR.AssetModel.reader();
this.story_view = 'page';
this.pages = {
'feeds' : $('#NB-page-feeds'),
'stories' : $('#NB-page-stories'),
'story' : $('#NB-page-story')
};
this.$s = {
$body: $('body'),
$feed_list: $('#NB-feed-list'),
$story_list: $('#NB-story-list'),
$story_detail: $('#NB-story-detail')
};
this.flags = {
'feeds_loaded' : false,
'active_view' : null
};
this.locks = {};
this.counts = {};
this.cache = {};
this.constants = {};
$(document).bind('mobileinit', function() {
$.mobile.ajaxEnabled = false;
});
this.runner();
};
NEWSBLUR.MobileReader.prototype = {
runner: function() {
this.load_feeds();
this.bind_clicks();
this.bind_scroll();
},
// =============
// = Feed List =
// =============
load_feeds: function() {
this.flags.active_view = 'feeds';
$.mobile.showPageLoadingMsg();
this.pages.feeds.unbind('pagebeforeshow').bind('pagebeforeshow', _.bind(function(e) {
$('ul', this.$s.$feed_list).listview('refresh');
}, this));
this.pages.feeds.unbind('pageshow').bind('pageshow', _.bind(function(e) {
$('ul', this.$s.$story_list).remove();
}, this));
this.model.load_feeds_flat($.rescope(this.build_feed_list, this));
},
build_feed_list: function() {
this.flags.active_view = 'feeds';
var self = this;
var folders = this.model.folders;
var feeds = this.model.feeds;
var $feed_list = this.$s.$feed_list;
var $feeds = '';
_.each(folders, function(items, folder_name) {
$feeds += '<ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">';
if (folder_name && folder_name != ' ') {
$feeds += _.template('\
<li data-role="list-divider"><%= folder_name %></li>', {
folder_name : folder_name
});
}
_.each(items, function(item) {
$feeds += self.make_feed_title(item);
});
$feeds += '</ul>';
});
this.flags.feeds_loaded = true;
$feed_list.html($feeds);
$('ul', $feed_list).listview();
$.mobile.hidePageLoadingMsg();
},
make_feed_title: function(feed_id) {
var feed = this.model.get_feed(feed_id);
var unread_class = '';
var exception_class = '';
if (feed.ps) unread_class += ' unread_positive';
if (feed.nt) unread_class += ' unread_neutral';
if (feed.ng) unread_class += ' unread_negative';
if (!feed.active) exception_class += ' NB-feed-inactive';
if (feed.has_exception && feed.exception_type == 'feed') {
exception_class += ' NB-feed-exception';
}
if (feed.not_yet_fetched && !feed.has_exception) {
exception_class += ' NB-feed-unfetched';
}
var $feed = _.template('\
<li class="<%= unread_class %> <%= exception_class %>">\
<a href="#" data-feed-id="<%= feed.id %>">\
<% if (feed.ps) { %>\
<span class="ui-li-count ui-li-count-positive"><%= feed.ps %></span>\
<% } %>\
<% if (feed.nt) { %>\
<span class="ui-li-count ui-li-count-neutral"><%= feed.nt %></span>\
<% } %>\
<% if (feed.ng) { %>\
<span class="ui-li-count ui-li-count-negative"><%= feed.ng %></span>\
<% } %>\
<img src="<%= $.favicon(feed.favicon) %>" class="ui-li-icon">\
<%= feed.feed_title %>\
</a>\
</li>', {
feed : feed,
unread_class : unread_class,
exception_class : exception_class
});
return $feed;
},
// ===========
// = Stories =
// ===========
reset_feed: function() {
this.page = 1;
},
load_stories: function(feed_id) {
this.flags.active_view = 'stories';
$.mobile.showPageLoadingMsg();
this.active_feed = feed_id;
this.model.load_feed(feed_id, this.page, this.page == 1, _.bind(this.build_stories, this));
},
build_stories: function(data, first_load) {
this.flags.active_view = 'stories';
var self = this;
var $story_list = this.$s.$story_list;
var $stories = "";
var feed_id = data.feed_id;
if (this.active_feed != feed_id) return;
_.each(data.stories, function(story) {
$stories += self.make_story_title(story);
});
if (first_load) {
$stories = '<ul data-role="listview" data-inset="false" data-theme="c" data-dividertheme="b">' +
$stories +
'</ul>';
$story_list.html($stories);
} else {
$('ul', $story_list).append($stories);
$('ul', $story_list).listview('refresh');
}
$('ul', $story_list).listview();
$.mobile.hidePageLoadingMsg();
},
load_next_page_of_stories: function() {
this.page += 1;
this.load_stories(this.active_feed);
},
make_story_title: function(story) {
var feed = this.model.get_feed(this.active_feed);
var score_color = this.story_color(story);
return _.template('<li class="NB-story <%= story.read_status?"NB-read":"" %> NB-score-<%= score_color %>">\
<div class="ui-li-icon NB-icon-score"></div>\
<a href="#" data-story-id="<%= story.id %>">\
<div class="NB-story-date"><%= story.long_parsed_date %></div>\
<% if (story.story_authors) { %>\
<div class="NB-story-author"><%= story.story_authors %></div>\
<% } %>\
<% if (story.story_tags && story.story_tags.length) { %>\
<div class="NB-story-tags">\
<% _.each(story.story_tags, function(tag) { %>\
<div class="NB-story-tag"><%= tag %></div>\
<% }); %>\
</div>\
<% } %>\
<div class="NB-story-title"><%= story.story_title %></div>\
<div class="NB-story-feed">\
<div class="NB-story-feed-icon"><img src="<%= $.favicon(feed.favicon) %>"></div>\
<div class="NB-story-feed-title"><%= feed.feed_title %></div>\
</div>\
</a>\
</li>', {
story : story,
feed : feed,
score_color : score_color
});
},
scroll_story_list: function() {
var window_height = $(window).height();
var window_offset = $(window).scrollTop();
var story_list_height = this.pages.stories.height();
var fudge_factor = 18;
if (window_height + window_offset > story_list_height - fudge_factor) {
this.load_next_page_of_stories();
}
},
// ================
// = Story Detail =
// ================
load_story_detail: function(story_id) {
this.flags.active_view = 'story_detail';
$.mobile.showPageLoadingMsg();
var $story_detail_view = this.$s.$story_detail;
var story = this.model.get_story(story_id);
var score_color = this.story_color(story);
var $story = this.make_story_detail(story);
this.colorize_story_title(story);
$('.ul-li-right', this.pages.story).jqmData('icon', 'NB-'+score_color);
$story_detail_view.html($story);
$.mobile.hidePageLoadingMsg();
this.mark_story_as_read(story);
},
make_story_detail: function(story) {
var feed = this.model.get_feed(this.active_feed);
var score_color = this.story_color(story);
var $story = _.template('<div class="NB-story <%= story.read_status?"NB-read":"" %> NB-score-<%= score_color %>">\
<div class="NB-story-header">\
<div class="NB-story-header-feed-gradient"></div>\
<div class="ui-li-icon NB-icon-score"></div>\
<div class="NB-story-date"><%= story.long_parsed_date %></div>\
<% if (story.story_authors) { %>\
<div class="NB-story-author"><%= story.story_authors %></div>\
<% } %>\
<% if (story.story_tags && story.story_tags.length) { %>\
<div class="NB-story-tags">\
<% _.each(story.story_tags, function(tag) { %>\
<div class="NB-story-tag"><%= tag %></div>\
<% }); %>\
</div>\
<% } %>\
<a href="<%= story.story_permalink %>" data-story-id="<%= story.id %>">\
<div class="NB-story-title"><%= story.story_title %></div>\
</a>\
</div>\
<div class="NB-story-content"><%= story.story_content %></div>\
</div>', {
story : story,
feed : feed,
score_color : score_color
});
return $story;
},
colorize_story_title: function() {
var feed = this.model.get_feed(this.active_feed);
$('.ui-header', this.pages.story)
.css('background-image', NEWSBLUR.utils.generate_gradient(feed, 'webkit'))
.css('background-image', NEWSBLUR.utils.generate_gradient(feed, 'moz'))
.css('borderBottom', NEWSBLUR.utils.generate_gradient(feed, 'border'))
.css('borderTop', NEWSBLUR.utils.generate_gradient(feed, 'border'))
.toggleClass('NB-inverse', NEWSBLUR.utils.is_feed_floater_gradient_light(feed));
var $feed = _.template('<div class="NB-story-feed-header">\
<img class="NB-favicon" src="<%= $.favicon(feed.favicon) %>" />\
<span class="feed_title">\
<%= feed.feed_title %>\
</span>\
</div>', {
feed : feed
});
$('.ui-title', this.pages.story).html($feed);
},
// =====================
// = General Utilities =
// =====================
story_color: function(story) {
var score = NEWSBLUR.utils.compute_story_score(story);
var score_color = 'neutral';
if (score > 0) score_color = 'positive';
if (score < 0) score_color = 'negative';
return score_color;
},
mark_story_as_read: function(story) {
var story_id = story.id;
var feed_id = story.story_feed_id;
this.model.mark_story_as_read(story_id, feed_id, _.bind(function(read) {
this.update_read_count(story_id, feed_id);
}, this));
},
update_read_count: function(story_id, feed_id) {
},
// ==========
// = Events =
// ==========
bind_clicks: function() {
var self = this;
this.$s.$feed_list.delegate('li a', 'tap', function(e) {
e.preventDefault();
var feed_id = $(e.currentTarget).jqmData('feed-id');
$.mobile.changePage(self.pages.stories);
self.reset_feed();
self.load_stories(feed_id);
});
this.$s.$story_list.delegate('li a', 'tap', function(e) {
e.preventDefault();
var story_id = $(e.currentTarget).jqmData('story-id');
$.mobile.showPageLoadingMsg();
$.mobile.changePage(self.pages.story);
self.load_story_detail(story_id);
});
this.$s.$story_detail.delegate('li a', 'tap', function(e) {
e.preventDefault();
var story_id = $(e.currentTarget).jqmData('story-id');
$.mobile.showPageLoadingMsg();
$.mobile.changePage(self.pages.story);
self.load_story_detail(story_id);
});
this.pages.story.delegate('.NB-next', 'tap', function(e) {
});
this.pages.story.delegate('.NB-previous', 'tap', function(e) {
});
},
bind_scroll: function() {
$(window).bind('scroll', _.throttle(_.bind(function(e) {
if (this.flags.active_view == 'stories') {
this.scroll_story_list();
}
}, this), 500));
}
};
})(jQuery);

View file

@ -230,6 +230,46 @@ NEWSBLUR.AssetModel.Reader.prototype = {
this.make_request('/reader/feeds', data, pre_callback, error_callback, {request_type: 'GET'});
},
load_feeds_flat: function(callback, error_callback) {
var self = this;
var data = {
flat: true,
include_favicons: true
};
var pre_callback = function(subscriptions) {
// NEWSBLUR.log(['subscriptions', subscriptions.flat_folders]);
var flat_feeds = function(feeds) {
var flattened = _.flatten(_.map(feeds, _.values));
return _.flatten(_.map(flattened, function(feed) {
if (!_.isNumber(feed) && feed) return flat_feeds(feed);
else return feed;
}));
};
var valid_feeds = flat_feeds({'root': subscriptions.flat_folders});
_.each(subscriptions.feeds, function(feed, feed_id) {
if (_.contains(valid_feeds, parseInt(feed_id, 10))) {
self.feeds[feed_id] = feed;
if (feed.favicon_fetching) self.flags['favicons_fetching'] = true;
}
});
self.folders = subscriptions.flat_folders;
self.starred_count = subscriptions.starred_count;
if (!_.isEqual(self.favicons, {})) {
_.each(self.feeds, function(feed) {
if (self.favicons[feed.id]) {
feed.favicon = self.favicons[feed.id];
}
});
}
callback();
};
this.make_request('/reader/feeds', data, pre_callback, error_callback, {request_type: 'GET'});
},
load_feed_favicons: function(callback, loaded_once, load_all) {
var pre_callback = _.bind(function(favicons) {
this.favicons = favicons;
@ -685,13 +725,29 @@ NEWSBLUR.AssetModel.Reader.prototype = {
this.make_request('/reader/features', {'page': page}, callback, callback, {request_type: 'GET'});
},
load_recommended_feed: function(page, refresh, callback, error_callback) {
load_recommended_feed: function(page, refresh, unmoderated, callback, error_callback) {
this.make_request('/recommendations/load_recommended_feed', {
'page': page,
'refresh': refresh
'page' : page,
'refresh' : refresh,
'unmoderated' : unmoderated
}, callback, error_callback, {request_type: 'GET'});
},
approve_feed_in_moderation_queue: function(feed_id, date, callback) {
this.make_request('/recommendations/approve_feed', {
'feed_id' : feed_id,
'date' : date,
'unmoderated' : true
}, callback, {request_type: 'GET'});
},
decline_feed_in_moderation_queue: function(feed_id, callback) {
this.make_request('/recommendations/decline_feed', {
'feed_id' : feed_id,
'unmoderated' : true
}, callback, {request_type: 'GET'});
},
load_dashboard_graphs: function(callback, error_callback) {
this.make_request('/statistics/dashboard_graphs', {}, callback, error_callback, {request_type: 'GET'});
},

View file

@ -367,6 +367,10 @@
},
find_feed_in_feed_list: function(feed_id) {
if (_.contains(this.cache.$feed_in_feed_list, feed_id)) {
return this.cache.$feed_in_feed_list[feed_id];
}
var $feed_list = this.$s.$feed_list;
var $feeds = $([]);
@ -376,6 +380,8 @@
}
});
this.cache.$feed_in_feed_list[feed_id] = $feeds;
return $feeds;
},
@ -1583,56 +1589,56 @@
this.active_feed = data.feed_id;
}
if (this.active_feed == feed_id) {
// NEWSBLUR.log(['post_open_feed', data.stories, this.flags]);
this.flags['opening_feed'] = false;
this.flags['feed_view_positions_calculated'] = false;
this.story_titles_clear_loading_endbar();
this.create_story_titles(stories);
this.make_story_feed_entries(stories, first_load);
this.show_feed_hidden_story_title_indicator(true);
this.show_story_titles_above_intelligence_level({'animate': false});
this.fill_out_story_titles();
$('.NB-feedbar-last-updated-date').text(data.last_update + ' ago');
if (this.counts['find_next_unread_on_page_of_feed_stories_load']) {
this.show_next_unread_story(true);
}
this.flags['story_titles_loaded'] = true;
if (!first_load) {
var stories_count = this.cache['iframe_story_positions_keys'].length;
this.flags.iframe_story_locations_fetched = false;
var $iframe = this.$s.$feed_iframe.contents();
this.fetch_story_locations_in_story_frame(stories_count, false, $iframe);
if (this.story_view == 'feed') {
this.prefetch_story_locations_in_feed_view();
}
} else {
if (this.story_view == 'page') {
if (this.flags['iframe_view_loaded']) {
// NEWSBLUR.log(['Titles loaded, iframe loaded']);
var $iframe = this.$s.$feed_iframe.contents();
this.fetch_story_locations_in_story_frame(0, true, $iframe);
} else {
// NEWSBLUR.log(['Titles loaded, iframe NOT loaded -- prefetching now']);
_.delay(_.bind(function() {
this.prefetch_story_locations_in_story_frame();
}, this), 500);
}
} else if (this.story_view == 'feed') {
this.prefetch_story_locations_in_feed_view();
} else if (this.story_view == 'story') {
this.show_next_story(1);
}
}
if (this.flags['open_unread_stories_in_tabs']) {
_.defer(_.bind(this.open_unread_stories_in_tabs, this));
}
this.hide_stories_progress_bar();
if (this.flags['showing_feed_in_tryfeed_view']) {
this.show_tryfeed_add_button();
}
this.make_content_pane_feed_counter(feed_id);
if (this.active_feed != feed_id) return;
// NEWSBLUR.log(['post_open_feed', data.stories, this.flags]);
this.flags['opening_feed'] = false;
this.flags['feed_view_positions_calculated'] = false;
this.story_titles_clear_loading_endbar();
this.create_story_titles(stories);
this.make_story_feed_entries(stories, first_load);
this.show_feed_hidden_story_title_indicator(true);
this.show_story_titles_above_intelligence_level({'animate': false});
this.fill_out_story_titles();
$('.NB-feedbar-last-updated-date').text(data.last_update + ' ago');
if (this.counts['find_next_unread_on_page_of_feed_stories_load']) {
this.show_next_unread_story(true);
}
this.flags['story_titles_loaded'] = true;
if (!first_load) {
var stories_count = this.cache['iframe_story_positions_keys'].length;
this.flags.iframe_story_locations_fetched = false;
var $iframe = this.$s.$feed_iframe.contents();
this.fetch_story_locations_in_story_frame(stories_count, false, $iframe);
if (this.story_view == 'feed' || this.flags['page_view_showing_feed_view']) {
this.prefetch_story_locations_in_feed_view();
}
} else {
if (this.story_view == 'page') {
if (this.flags['iframe_view_loaded']) {
// NEWSBLUR.log(['Titles loaded, iframe loaded']);
var $iframe = this.$s.$feed_iframe.contents();
this.fetch_story_locations_in_story_frame(0, true, $iframe);
} else {
// NEWSBLUR.log(['Titles loaded, iframe NOT loaded -- prefetching now']);
_.delay(_.bind(function() {
this.prefetch_story_locations_in_story_frame();
}, this), 250);
}
} else if (this.story_view == 'feed') {
this.prefetch_story_locations_in_feed_view();
} else if (this.story_view == 'story') {
this.show_next_story(1);
}
}
if (this.flags['open_unread_stories_in_tabs']) {
_.defer(_.bind(this.open_unread_stories_in_tabs, this));
}
this.hide_stories_progress_bar();
if (this.flags['showing_feed_in_tryfeed_view']) {
this.show_tryfeed_add_button();
}
this.make_content_pane_feed_counter(feed_id);
},
setup_mousemove_on_views: function() {
@ -1801,7 +1807,7 @@
var feeds = this.list_feeds_with_unreads_in_folder($folder, false, true);
this.cache['river_feeds_with_unreads'] = feeds;
this.show_stories_progress_bar(feeds.length);
this.model.fetch_river_stories(this.active_feed, feeds, 0,
this.model.fetch_river_stories(this.active_feed, feeds, 1,
_.bind(this.post_open_river_stories, this), true);
},
@ -1840,7 +1846,9 @@
var feeds = _.compact(_.map($('.feed:not(.NB-empty)', $folder), function(o) {
var feed_id = parseInt($(o).attr('data-id'), 10);
var feed = model.get_feed(feed_id);
if (counts_only && !visible_only) {
if (!feed) {
return;
} else if (counts_only && !visible_only) {
return feed.ps + feed.nt + feed.ng;
} else if (counts_only && visible_only) {
if (unread_view == 'positive') return feed.ps;
@ -2079,7 +2087,7 @@
!$story.length ||
this.flags['iframe_fetching_story_locations'] ||
this.flags['iframe_story_locations_fetched'] ||
parseInt($story.offset().top, 10) > this.cache['prefetch_iteration']*4000) {
parseInt($story.offset().top, 10) > this.cache['prefetch_iteration']*2000) {
if ($story && $story.length) {
// NEWSBLUR.log(['Prefetch break on position too far', parseInt($story.offset().top, 10), this.cache['prefetch_iteration']*4000]);
break;
@ -2100,7 +2108,7 @@
&& !self.flags['iframe_story_locations_fetched']) {
self.prefetch_story_locations_in_story_frame();
}
}, 2000);
}, 1000);
}
},
@ -2186,14 +2194,13 @@
var feed = this.model.get_feed(feed_id);
var $feed_list = this.$s.$feed_list;
var $feed = this.cache.$feed_in_feed_list[feed_id] || this.find_feed_in_feed_list(feed_id);
var $feed = this.find_feed_in_feed_list(feed_id);
var $feed_counts = this.cache.$feed_counts_in_feed_list[feed_id] || $('.feed_counts_floater', $feed);
var $story_title = this.find_story_in_story_titles(story_id);
var $content_pane = this.$s.$content_pane;
var $floater = $('.feed_counts_floater', $content_pane);
var unread_view = this.get_unread_view_name();
this.cache.$feed_in_feed_list[feed_id] = $feed;
this.cache.$feed_counts_in_feed_list[feed_id] = $feed_counts;
$story_title.toggleClass('read', !unread);
@ -2509,7 +2516,7 @@
var read = story.read_status
? ' read '
: '';
var score = this.compute_story_score(story);
var score = NEWSBLUR.utils.compute_story_score(story);
var score_color = 'neutral';
var starred = story.starred ? ' NB-story-starred ' : '';
if (options.river_stories) {
@ -2557,82 +2564,6 @@
return $story_title;
},
is_feed_floater_gradient_light: function(feed) {
if (!feed) return false;
var color = feed.favicon_color;
if (!color) return false;
var r = parseInt(color.substr(0, 2), 16) / 255.0;
var g = parseInt(color.substr(2, 2), 16) / 255.0;
var b = parseInt(color.substr(4, 2), 16) / 255.0;
return $.textColor({r: r, g: g, b: b}) != 'white';
},
generate_gradient: function(feed, type) {
if (!feed) return '';
var color = feed.favicon_color;
if (!color) return '';
var r = parseInt(color.substr(0, 2), 16);
var g = parseInt(color.substr(2, 2), 16);
var b = parseInt(color.substr(4, 2), 16);
if (type == 'border') {
return [
'1px solid rgb(',
[
parseInt(r*(6/8), 10),
parseInt(g*(6/8), 10),
parseInt(b*(6/8), 10)
].join(','),
')'
].join('');
} else if (type == 'webkit') {
return [
'-webkit-gradient(',
'linear,',
'left bottom,',
'left top,',
'color-stop(0.06, rgba(',
[
r,
g,
b,
255
].join(','),
')),',
'color-stop(0.84, rgba(',
[
r+35,
g+35,
b+35,
255
].join(','),
')))'
].join('');
} else if (type == 'moz') {
return [
'-moz-linear-gradient(',
'center bottom,',
'rgb(',
[
r,
g,
b
].join(','),
') 6%,',
'rgb(',
[
r+35,
g+35,
b+35
].join(','),
') 84%)'
].join('');
}
},
story_titles_clear_loading_endbar: function() {
var $story_titles = this.$s.$story_titles;
@ -2643,22 +2574,6 @@
}
},
compute_story_score: function(story) {
var score = 0;
var score_max = Math.max(story.intelligence['title'],
story.intelligence['author'],
story.intelligence['tags']);
var score_min = Math.min(story.intelligence['title'],
story.intelligence['author'],
story.intelligence['tags']);
if (score_max > 0) score = score_max;
else if (score_min < 0) score = score_min;
if (score == 0) score = story.intelligence['feed'];
return score;
},
recalculate_story_scores: function(feed_id) {
feed_id = feed_id || this.active_feed;
@ -2667,7 +2582,7 @@
var replace_stories = _.bind(function($story, story_id) {
var story = this.model.get_story(story_id);
if (story.story_feed_id != feed_id) return;
var score = this.compute_story_score(story);
var score = NEWSBLUR.utils.compute_story_score(story);
$story.removeClass('NB-story-positive')
.removeClass('NB-story-neutral')
.removeClass('NB-story-negative');
@ -2972,7 +2887,7 @@
var unread_view_name = this.get_unread_view_name();
var $indicator = $('.NB-story-title-indicator', $story_titles);
var hidden_stories = _.any(this.model.stories, _.bind(function(story) {
var score = this.compute_story_score(story);
var score = NEWSBLUR.utils.compute_story_score(story);
if (unread_view_name == 'positive') return score <= 0;
else if (unread_view_name == 'neutral') return score < 0;
@ -2982,6 +2897,7 @@
if ($indicator.length) {
var $counts = this.make_feed_counts_floater(feed.ps, feed.nt, feed.ng);
$('.feed_counts_floater', $indicator).replaceWith($counts);
this.cache.$feed_counts_in_feed_list[feed_id] = null;
$indicator.css({'opacity': 1});
} else if (feed) {
$indicator = $.make('div', { className: 'NB-story-title-indicator' }, [
@ -3011,14 +2927,14 @@
'positive' :
'neutral';
var hidden_stories_at_threshold = _.any(this.model.stories, _.bind(function(story) {
var score = this.compute_story_score(story);
var score = NEWSBLUR.utils.compute_story_score(story);
if (unread_view_name == 'positive') return score == 0;
else if (unread_view_name == 'neutral') return score < 0;
}, this));
var hidden_stories_below_threshold = unread_view_name == 'positive' &&
_.any(this.model.stories, _.bind(function(story) {
var score = this.compute_story_score(story);
var score = NEWSBLUR.utils.compute_story_score(story);
return score < 0;
}, this));
@ -3164,7 +3080,7 @@
var read = story.read_status
? ' read '
: '';
var score = this.compute_story_score(story);
var score = NEWSBLUR.utils.compute_story_score(story);
var score_color = 'neutral';
var river_stories = options['river_stories']
? ' NB-river-story '
@ -3189,11 +3105,11 @@
$.make('span', { className: 'feed_title' }, feed.feed_title)
])
)
]).css('background-image', this.generate_gradient(feed, 'webkit'))
.css('background-image', this.generate_gradient(feed, 'moz'))
.css('borderBottom', this.generate_gradient(feed, 'border'))
.css('borderTop', this.generate_gradient(feed, 'border'))
.toggleClass('NB-inverse', this.is_feed_floater_gradient_light(feed)),
]).css('background-image', NEWSBLUR.utils.generate_gradient(feed, 'webkit'))
.css('background-image', NEWSBLUR.utils.generate_gradient(feed, 'moz'))
.css('borderBottom', NEWSBLUR.utils.generate_gradient(feed, 'border'))
.css('borderTop', NEWSBLUR.utils.generate_gradient(feed, 'border'))
.toggleClass('NB-inverse', NEWSBLUR.utils.is_feed_floater_gradient_light(feed)),
$.make('div', { className: 'NB-feed-story-header-info' }, [
(story.story_authors &&
this.make_story_feed_author(story)),
@ -4599,6 +4515,7 @@
var $new_feed = $(self.make_feed_title_template(feeds[feed_id], 'feed'));
if ($feed.hasClass('NB-toplevel')) $new_feed.addClass('NB-toplevel');
$feed.replaceWith($new_feed);
self.cache.$feed_in_feed_list[feed_id] = null;
self.hover_over_feed_titles($new_feed);
if (self.active_feed == feed_id) {
self.open_feed(feed_id, true, $new_feed);
@ -4633,8 +4550,7 @@
var feed = this.model.get_feed(feed_id);
if (!feed) continue;
var $feed = $(this.make_feed_title_template(feed, 'feed'));
var $feed_on_page = this.cache.$feed_in_feed_list[feed_id] ||
this.find_feed_in_feed_list(feed_id);
var $feed_on_page = this.find_feed_in_feed_list(feed_id);
if (feed_id == this.active_feed) {
NEWSBLUR.log(['UPDATING INLINE', feed.feed_title, $feed, $feed_on_page, replace_active_feed]);
@ -4648,6 +4564,7 @@
} else {
if ($feed_on_page.hasClass('NB-toplevel')) $feed.addClass('NB-toplevel');
$feed_on_page.replaceWith($feed);
this.cache.$feed_in_feed_list[feed_id] = null;
this.mark_feed_as_selected(this.active_feed, $feed);
if (!single_feed_id) this.recalculate_story_scores(feed_id);
this.show_feed_hidden_story_title_indicator();
@ -4659,6 +4576,7 @@
}
if ($feed_on_page.hasClass('NB-toplevel')) $feed.addClass('NB-toplevel');
$feed_on_page.replaceWith($feed);
this.cache.$feed_in_feed_list[feed_id] = null;
(function($feed) {
$feed.css({'backgroundColor': '#D7DDE6'});
$feed.animate({
@ -4701,7 +4619,7 @@
if ($story && $story.length) {
// Just update intelligence
var score = this.compute_story_score(story);
var score = NEWSBLUR.utils.compute_story_score(story);
$story.removeClass('NB-story-neutral')
.removeClass('NB-story-negative')
.removeClass('NB-story-positive');
@ -5134,13 +5052,44 @@
this.open_add_feed_modal({url: feed_address});
},
load_recommended_feed: function(direction, refresh) {
approve_feed_in_moderation_queue: function(feed_id) {
var self = this;
var $module = $('.NB-module-recommended');
var $module = $('.NB-module-recommended.NB-recommended-unmoderated');
$module.addClass('NB-loading');
var date = $('.NB-recommended-moderation-date').val();
this.model.approve_feed_in_moderation_queue(feed_id, date, function(resp) {
if (!resp) return;
$module.removeClass('NB-loading');
$module.replaceWith(resp);
self.load_javascript_elements_on_page();
});
},
decline_feed_in_moderation_queue: function(feed_id) {
var self = this;
var $module = $('.NB-module-recommended.NB-recommended-unmoderated');
$module.addClass('NB-loading');
this.model.decline_feed_in_moderation_queue(feed_id, function(resp) {
if (!resp) return;
$module.removeClass('NB-loading');
$module.replaceWith(resp);
self.load_javascript_elements_on_page();
});
},
load_recommended_feed: function(direction, refresh, unmoderated) {
var self = this;
var $module = unmoderated ?
$('.NB-module-recommended.NB-recommended-unmoderated') :
$('.NB-module-recommended:not(.NB-recommended-unmoderated)');
$module.addClass('NB-loading');
direction = direction || 0;
this.model.load_recommended_feed(this.counts['recommended_feed_page']+direction, !!refresh, function(resp) {
this.model.load_recommended_feed(this.counts['recommended_feed_page']+direction,
!!refresh, unmoderated, function(resp) {
if (!resp) return;
self.counts['recommended_feed_page'] += direction;
@ -5653,17 +5602,33 @@
});
});
$.targetIs(e, { tagSelector: '.NB-recommended-decline' }, function($t, $p){
e.preventDefault();
var feed_id = $t.closest('.NB-recommended').attr('data-feed-id');
self.decline_feed_in_moderation_queue(feed_id);
});
$.targetIs(e, { tagSelector: '.NB-recommended-approve' }, function($t, $p){
e.preventDefault();
var feed_id = $t.closest('.NB-recommended').attr('data-feed-id');
self.approve_feed_in_moderation_queue(feed_id);
});
$.targetIs(e, { tagSelector: '.NB-module-recommended .NB-module-next-page' }, function($t, $p){
e.preventDefault();
if (!$t.hasClass('NB-disabled')) {
self.load_recommended_feed(1);
console.log(['parent', $t.closest('.NB-module-recommended'), $t.closest('.NB-module-recommended').hasClass('NB-recommended-unmoderated')]);
var unmoderated = $t.closest('.NB-module-recommended').hasClass('NB-recommended-unmoderated');
self.load_recommended_feed(1, false, unmoderated);
}
});
$.targetIs(e, { tagSelector: '.NB-module-recommended .NB-module-previous-page' }, function($t, $p){
e.preventDefault();
if (!$t.hasClass('NB-disabled')) {
self.load_recommended_feed(-1);
var unmoderated = $t.closest('.NB-module-recommended').hasClass('NB-recommended-unmoderated');
console.log(['parent', $t.closest('.NB-module-recommended')]);
self.load_recommended_feed(-1, false, unmoderated);
}
});

View file

@ -0,0 +1,95 @@
NEWSBLUR.utils = {
compute_story_score: function(story) {
var score = 0;
var score_max = Math.max(story.intelligence['title'],
story.intelligence['author'],
story.intelligence['tags']);
var score_min = Math.min(story.intelligence['title'],
story.intelligence['author'],
story.intelligence['tags']);
if (score_max > 0) score = score_max;
else if (score_min < 0) score = score_min;
if (score == 0) score = story.intelligence['feed'];
return score;
},
generate_gradient: function(feed, type) {
if (!feed) return '';
var color = feed.favicon_color;
if (!color) return '';
var r = parseInt(color.substr(0, 2), 16);
var g = parseInt(color.substr(2, 2), 16);
var b = parseInt(color.substr(4, 2), 16);
if (type == 'border') {
return [
'1px solid rgb(',
[
parseInt(r*(6/8), 10),
parseInt(g*(6/8), 10),
parseInt(b*(6/8), 10)
].join(','),
')'
].join('');
} else if (type == 'webkit') {
return [
'-webkit-gradient(',
'linear,',
'left bottom,',
'left top,',
'color-stop(0, rgba(',
[
r,
g,
b,
255
].join(','),
')),',
'color-stop(1, rgba(',
[
r+35,
g+35,
b+35,
255
].join(','),
')))'
].join('');
} else if (type == 'moz') {
return [
'-moz-linear-gradient(',
'center bottom,',
'rgb(',
[
r,
g,
b
].join(','),
') 0%,',
'rgb(',
[
r+35,
g+35,
b+35
].join(','),
') 100%)'
].join('');
}
},
is_feed_floater_gradient_light: function(feed) {
if (!feed) return false;
var color = feed.favicon_color;
if (!color) return false;
var r = parseInt(color.substr(0, 2), 16) / 255.0;
var g = parseInt(color.substr(2, 2), 16) / 255.0;
var b = parseInt(color.substr(4, 2), 16) / 255.0;
return $.textColor({r: r, g: g, b: b}) != 'white';
}
};

View file

@ -155,7 +155,7 @@ LOGGING = {
COMPRESS_JS = {
'all': {
'source_filenames': (
'js/jquery-1.6.js',
'js/jquery-1.6.1.js',
'js/inflector.js',
'js/jquery.json.js',
'js/jquery.easing.js',
@ -183,6 +183,7 @@ COMPRESS_JS = {
'js/jquery.flot.js',
'js/jquery.tipsy.js',
'js/underscore.js',
'js/newsblur/reader_utils.js',
'js/newsblur/assetmodel.js',
'js/newsblur/reader.js',
'js/newsblur/generate_bookmarklet.js',
@ -204,6 +205,22 @@ COMPRESS_JS = {
),
'output_filename': 'js/all-compressed-?.js'
},
'mobile': {
'source_filenames': (
'js/jquery-1.6.1.js',
'js/mobile/jquery.mobile-1.0b1.js',
'js/jquery.ajaxmanager.3.js',
'js/underscore.js',
'js/inflector.js',
'js/jquery.json.js',
'js/jquery.easing.js',
'js/jquery.newsblur.js',
'js/newsblur/reader_utils.js',
'js/newsblur/assetmodel.js',
'js/mobile/newsblur/mobile_workspace.js',
),
'output_filename': 'js/mobile-compressed-?.js',
},
'paypal': {
'source_filenames': (
'js/newsblur/paypal_return.js',
@ -234,6 +251,13 @@ COMPRESS_CSS = {
),
'output_filename': 'css/all-compressed-?.css'
},
'mobile': {
'source_filenames': (
'css/mobile/jquery.mobile-1.0b1.css',
'css/mobile/mobile.css',
),
'output_filename': 'css/mobile/mobile-compressed-?.css',
},
'paypal': {
'source_filenames': (
'css/paypal_return.css',
@ -305,6 +329,8 @@ INSTALLED_APPS = (
'apps.profile',
'apps.recommendations',
'apps.statistics',
'apps.static',
'apps.mobile',
'south',
'utils',
'vendor',

View file

@ -0,0 +1,116 @@
{% load compressed utils_tags %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}NewsBlur{% endblock %}</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<link rel="shortcut icon" HREF="/media/img/favicon.png">
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="apple-touch-icon" href="/media/img/logo_128.png"/>
<script type="text/javascript" charset="utf-8">
var NEWSBLUR = {};
NEWSBLUR.Globals = {
'is_authenticated' : {{ user.is_authenticated|yesno:"true,false" }},
'is_anonymous' : {{ user.is_anonymous|yesno:"true,false" }},
'is_premium' : {{ user.profile.is_premium|yesno:"true,false" }},
'secret_token' : "{{ user.profile.secret_token }}",
'username' : "{{ user.username|safe }}",
'email' : "{{ user.email|safe }}",
'google_favicon_url' : 'http://www.google.com/s2/favicons?domain_url=',
'MEDIA_URL' : "{{ MEDIA_URL }}"
};
NEWSBLUR.Flags = {
'start_import_from_google_reader': {{ start_import_from_google_reader|yesno:"true,false" }}
};
NEWSBLUR.URLs = {
'domain' : "{% current_domain %}"
};
</script>
{% compressed_css 'mobile' %}
{% block head_js %}
{% compressed_js 'mobile' %}
{% endblock head_js %}
{% block extra_head_js %}
{% endblock extra_head_js %}
{% if not debug %}
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-8371683-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
{% endif %}
</head>
<body>
<div data-role="page" data-url="feeds" id="NB-page-feeds">
<div data-role="header" data-backbtn="false">
<h1>{{ request.user.username }}</h1>
</div>
<div data-role="content">
<div id="NB-feed-list"></div>
</div>
<div data-role="footer">
<div class="NB-footer-site-count ui-li-count">12 sites</div>
</div>
</div>
<div data-role="page" data-url="stories" id="NB-page-stories">
<div data-role="header">
<a href="#" data-icon="arrow-l" data-back-btn-text="Sites" data-rel="back">Sites</a>
<h1>Stories</h1>
</div>
<div data-role="content">
<div id="NB-story-list"></div>
</div>
<div data-role="footer" class="ui-header"> 1
<div class="NB-footer-site-count ui-li-count">12 sites</div>
</div>
</div>
<div data-role="page" data-url="story" id="NB-page-story">
<div data-role="header">
<a href="#" data-icon="arrow-l" data-back-btn-text="Stories" data-rel="back">Stories</a>
<h1>Story</h1>
<a href="#" data-icon="arrow-d" class="ui-btn-right NB-next">Next Unread</a>
</div>
<div data-role="content">
<div id="NB-story-detail">
</div>
</div>
<div data-role="footer" class="ui-header">
<a href="#" data-icon="arrow-u" class="NB-previous">Previous</a>
<h1>...</h1>
<a href="#" data-icon="arrow-d" class="ui-btn-right NB-next">Next Unread</a>
</div>
</div>
<script>
$(document).bind('mobileinit', function() {
$.mobile.ajaxEnabled = false;
});
$(document).bind('ready', function() {
NEWSBLUR.mobile_reader = new NEWSBLUR.MobileReader();
});
</script>
</body>
</html>

View file

@ -83,6 +83,11 @@ $(document).ready(function() {
{% if recommended_feeds %}
{% render_recommended_feed recommended_feeds %}
{% endif %}
{% if user.is_staff and unmoderated_feeds %}
{% render_recommended_feed unmoderated_feeds 1 %}
{% endif %}
</div>

View file

@ -1,6 +1,10 @@
<div class="NB-module-recommended NB-module">
<div class="NB-module-recommended NB-module {% if unmoderated %}NB-recommended-unmoderated{% endif %}">
<h5 class="NB-module-header">
Recommended Site
{% if unmoderated %}
Moderation Queue
{% else %}
Recommended Site
{% endif %}
<div class="NB-module-header-right">
{% if has_next_page or has_previous_page %}
<a href="#" class="NB-module-direction NB-module-next-page NB-javascript {% if not has_next_page %}NB-disabled{% endif %}"></a>
@ -8,9 +12,15 @@
{% endif %}
<div class="NB-spinner NB-left"></div>
<div class="NB-module-recommended-date">
{{ recommended_feed.approved_date|date:"M j" }}
<span>{{ recommended_feed.approved_date|date:"S" }}</span>,
{{ recommended_feed.approved_date|date:"Y" }}
{% if unmoderated %}
{{ recommended_feed.created_date|date:"M j" }}
<span>{{ recommended_feed.created_date|date:"S" }}</span>,
{{ recommended_feed.created_date|date:"Y" }}
{% else %}
{{ recommended_feed.approved_date|date:"M j" }}
<span>{{ recommended_feed.approved_date|date:"S" }}</span>,
{{ recommended_feed.approved_date|date:"Y" }}
{% endif %}
</div>
</div>
</h5>
@ -34,6 +44,11 @@
<div class="NB-recommended-try NB-modal-submit-green NB-modal-submit-button NB-javascript">Try</div>
<div class="NB-recommended-add NB-modal-submit-close NB-modal-submit-button NB-javascript">Add</div>
{% endif %}
{% if unmoderated %}
<input type="text" class="NB-recommended-moderation-date" name="date" value="{{ today|date:"Y-m-d" }}" />
<div class="NB-recommended-approve NB-modal-submit-green NB-modal-submit-button NB-javascript">Approve</div>
<div class="NB-recommended-decline NB-modal-submit-close NB-modal-submit-button NB-javascript">Decline</div>
{% endif %}
</div>
</div>

View file

@ -13,6 +13,8 @@ urlpatterns = patterns('',
(r'^api/', include('apps.api.urls')),
(r'^recommendations/', include('apps.recommendations.urls')),
(r'^statistics/', include('apps.statistics.urls')),
(r'^mobile/', include('apps.mobile.urls')),
(r'^m/', include('apps.mobile.urls')),
url(r'^about/?', static_views.about, name='about'),
url(r'^faq/?', static_views.faq, name='faq'),
url(r'^api/?', static_views.api, name='api'),

View file

@ -212,11 +212,11 @@ class ProcessFeed:
# if story.get('published') > end_date:
# end_date = story.get('published')
story_guids.append(story.get('guid') or story.get('link'))
existing_stories = MStory.objects(
existing_stories = list(MStory.objects(
# story_guid__in=story_guids,
story_date__gte=start_date,
story_feed_id=self.feed.pk
).limit(len(story_guids))
).limit(len(story_guids)))
# MStory.objects(
# (Q(story_date__gte=start_date) & Q(story_date__lte=end_date))

File diff suppressed because it is too large Load diff

View file

@ -27,6 +27,25 @@ def ajax_login_required(function=None):
else:
return _dec(function)
def admin_only(function=None):
def _dec(view_func):
def _view(request, *args, **kwargs):
if not request.user.is_staff:
return HttpResponseForbidden()
else:
return view_func(request, *args, **kwargs)
_view.__name__ = view_func.__name__
_view.__dict__ = view_func.__dict__
_view.__doc__ = view_func.__doc__
return _view
if function is None:
return _dec
else:
return _dec(function)
def get_user(request):
if not hasattr(request, 'user'):
user = request

View file

@ -59,8 +59,8 @@ class Opml(object):
return self._outlines[index]
def from_string(opml_text):
return Opml(lxml.etree.fromstring(opml_text))
parser = lxml.etree.XMLParser(recover=True)
return Opml(lxml.etree.fromstring(opml_text, parser))
def parse(opml_url):