mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Huge update: Hooking up slider to hide/show stories based on their score. Also added new story counts, score calculations, and real-time updating of unread counts as you read.
This commit is contained in:
parent
340ff31228
commit
b1c72c5c9f
12 changed files with 479 additions and 69 deletions
|
@ -2,7 +2,6 @@ from django.db import models
|
|||
from django.contrib.auth.models import User
|
||||
import datetime
|
||||
from apps.rss_feeds.models import Feed, Story, StoryAuthor, Tag
|
||||
from apps.reader.models import UserSubscription, UserStory
|
||||
|
||||
class FeatureCategory(models.Model):
|
||||
|
||||
|
@ -37,6 +36,7 @@ class ClassifierTitle(models.Model):
|
|||
def __unicode__(self):
|
||||
return '%s: %s (%s)' % (self.user, self.title, self.feed)
|
||||
|
||||
|
||||
class ClassifierAuthor(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
score = models.SmallIntegerField()
|
||||
|
@ -47,7 +47,13 @@ class ClassifierAuthor(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return '%s: %s (%s)' % (self.user, self.author.author_name, self.feed)
|
||||
|
||||
|
||||
def apply_classifier(self, story):
|
||||
if story['author'] == self.author:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ClassifierFeed(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
score = models.SmallIntegerField()
|
||||
|
@ -58,6 +64,12 @@ class ClassifierFeed(models.Model):
|
|||
def __unicode__(self):
|
||||
return '%s: %s' % (self.user, self.feed)
|
||||
|
||||
def apply_classifier(self, story):
|
||||
if self.feed == story.feed:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ClassifierTag(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
score = models.SmallIntegerField()
|
||||
|
@ -67,4 +79,32 @@ class ClassifierTag(models.Model):
|
|||
creation_date = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s: %s (%s)' % (self.user, self.tag.name, self.feed)
|
||||
return '%s: %s (%s)' % (self.user, self.tag.name, self.feed)
|
||||
|
||||
def apply_classifier_titles(classifiers, story):
|
||||
for classifier in classifiers:
|
||||
if classifier.title in story['story_title']:
|
||||
# print 'Titles: (%s) %s -- %s' % (classifier.title in story['story_title'], classifier.title, story['story_title'])
|
||||
return classifier.score
|
||||
return 0
|
||||
|
||||
def apply_classifier_feeds(classifiers, feed):
|
||||
for classifier in classifiers:
|
||||
if classifier.feed == feed:
|
||||
# print 'Feeds: %s -- %s' % (classifier.feed, feed)
|
||||
return classifier.score
|
||||
return 0
|
||||
|
||||
def apply_classifier_authors(classifiers, story):
|
||||
for classifier in classifiers:
|
||||
if classifier.author.id == story['story_author_id']:
|
||||
# print 'Authors: %s -- %s' % (classifier.author.id, story['story_author_id'])
|
||||
return classifier.score
|
||||
return 0
|
||||
|
||||
def apply_classifier_tags(classifiers, story):
|
||||
for classifier in classifiers:
|
||||
if classifier.tag.name in story['story_tags']:
|
||||
# print 'Tags: (%s) %s -- %s' % (classifier.tag.name in story['story_tags'], classifier.tag.name, story['story_tags'])
|
||||
return classifier.score
|
||||
return 0
|
|
@ -23,6 +23,7 @@ def index(requst):
|
|||
pass
|
||||
|
||||
@require_POST
|
||||
@json.json_view
|
||||
def save_classifier_story(request):
|
||||
post = request.POST
|
||||
facets = post.getlist('facet')
|
||||
|
@ -65,9 +66,10 @@ def save_classifier_story(request):
|
|||
original_story=story)
|
||||
|
||||
response = dict(code=code, message=message, payload=payload)
|
||||
return HttpResponse(response)
|
||||
return response
|
||||
|
||||
@require_POST
|
||||
@json.json_view
|
||||
def save_classifier_publisher(request):
|
||||
post = request.POST
|
||||
facets = post.getlist('facet')
|
||||
|
@ -101,4 +103,4 @@ def save_classifier_publisher(request):
|
|||
feed=feed)
|
||||
|
||||
response = dict(code=code, message=message, payload=payload)
|
||||
return HttpResponse(response)
|
||||
return response
|
|
@ -5,17 +5,22 @@ import random
|
|||
from django.core.cache import cache
|
||||
from apps.rss_feeds.models import Feed, Story
|
||||
from utils import feedparser, object_manager, json
|
||||
from apps.analyzer.models import ClassifierFeed, ClassifierAuthor, ClassifierTag, ClassifierTitle
|
||||
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds, apply_classifier_authors, apply_classifier_tags
|
||||
|
||||
DAYS_OF_UNREAD = 14
|
||||
|
||||
class UserSubscription(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
feed = models.ForeignKey(Feed)
|
||||
last_read_date = models.DateTimeField(default=datetime.datetime.now()-datetime.timedelta(days=7))
|
||||
mark_read_date = models.DateTimeField(default=datetime.datetime.now()-datetime.timedelta(days=7))
|
||||
unread_count = models.IntegerField(default=0)
|
||||
unread_count_updated = models.DateTimeField(
|
||||
default=datetime.datetime(2000,1,1)
|
||||
)
|
||||
scores = models.CharField(max_length=256)
|
||||
last_read_date = models.DateTimeField(default=datetime.datetime.now()
|
||||
- datetime.timedelta(days=DAYS_OF_UNREAD))
|
||||
mark_read_date = models.DateTimeField(default=datetime.datetime.now()
|
||||
- datetime.timedelta(days=DAYS_OF_UNREAD))
|
||||
unread_count_neutral = models.IntegerField(default=0)
|
||||
unread_count_positive = models.IntegerField(default=0)
|
||||
unread_count_negative = models.IntegerField(default=0)
|
||||
unread_count_updated = models.DateTimeField(default=datetime.datetime(2000,1,1))
|
||||
|
||||
def __unicode__(self):
|
||||
return '[' + self.feed.feed_title + '] '
|
||||
|
@ -61,7 +66,7 @@ class UserSubscription(models.Model):
|
|||
# readstories.delete()
|
||||
|
||||
def stories_newer_lastread(self):
|
||||
return self.feed.new_stories_since_date(self.last_read_date)
|
||||
return self.feed.new_stories_since_date(self.last_read_date).count()
|
||||
|
||||
def stories_between_lastread_allread(self):
|
||||
story_count = Story.objects.filter(
|
||||
|
@ -82,15 +87,51 @@ class UserSubscription(models.Model):
|
|||
new_subscription.save()
|
||||
|
||||
def calculate_feed_scores(self):
|
||||
scores = []
|
||||
for i in range(20):
|
||||
# [0, 0, 2, 4, 5 ..]
|
||||
scores.append(random.randint(0, 20))
|
||||
feed_scores = dict(negative=0, neutral=0, positive=0)
|
||||
date_delta = datetime.datetime.now()-datetime.timedelta(days=DAYS_OF_UNREAD)
|
||||
if date_delta < self.mark_read_date:
|
||||
date_delta = self.mark_read_date
|
||||
read_stories = UserStory.objects.select_related('story')\
|
||||
.filter(user=self.user,
|
||||
feed=self.feed,
|
||||
story__story_date__gte=date_delta)
|
||||
stories_db = Story.objects.filter(story_date__gte=date_delta,
|
||||
story_feed=self.feed)\
|
||||
.exclude(id__in=[rs.story.id for rs in read_stories])
|
||||
|
||||
stories = self.feed.format_stories(stories_db)
|
||||
classifier_feeds = ClassifierFeed.objects.filter(user=self.user, feed=self.feed)
|
||||
classifier_authors = ClassifierAuthor.objects.filter(user=self.user, feed=self.feed)
|
||||
classifier_titles = ClassifierTitle.objects.filter(user=self.user, feed=self.feed)
|
||||
classifier_tags = ClassifierTag.objects.filter(user=self.user, feed=self.feed)
|
||||
|
||||
scores = {
|
||||
'feed': apply_classifier_feeds(classifier_feeds, self.feed),
|
||||
}
|
||||
|
||||
for story in stories:
|
||||
scores.update({
|
||||
'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['feed'], scores['author'], scores['tags'], scores['title'])
|
||||
min_score = min(scores['feed'], scores['author'], scores['tags'], scores['title'])
|
||||
if max_score > 0:
|
||||
feed_scores['positive'] += 1
|
||||
if min_score < 0:
|
||||
feed_scores['negative'] += 1
|
||||
if max_score == 0 and min_score == 0:
|
||||
feed_scores['neutral'] += 1
|
||||
|
||||
self.unread_count_positive = feed_scores['positive']
|
||||
self.unread_count_neutral = feed_scores['neutral']
|
||||
self.unread_count_negative = feed_scores['negative']
|
||||
|
||||
self.scores = json.encode(scores)
|
||||
self.save()
|
||||
|
||||
return scores
|
||||
return
|
||||
|
||||
def get_scores(self):
|
||||
scores = json.decode(self.scores)
|
||||
|
|
|
@ -19,6 +19,8 @@ from django.core import serializers
|
|||
from django.utils.safestring import mark_safe
|
||||
from django.views.decorators.cache import cache_page
|
||||
from djangologging.decorators import suppress_logging_output
|
||||
from apps.analyzer.models import ClassifierFeed, ClassifierAuthor, ClassifierTag, ClassifierTitle
|
||||
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds, apply_classifier_authors, apply_classifier_tags
|
||||
import logging
|
||||
import datetime
|
||||
import threading
|
||||
|
@ -61,11 +63,10 @@ def load_feeds(request):
|
|||
feeds = []
|
||||
folders = []
|
||||
for sub in us:
|
||||
# logging.info("UserSub Scores: %s" % sub.user_sub.scores)
|
||||
feed_scores = sub.user_sub.scores or "[]"
|
||||
sub.feed.scores = json.decode(feed_scores)
|
||||
try:
|
||||
sub.feed.unread_count = sub.user_sub.unread_count
|
||||
sub.feed.unread_count_positive = sub.user_sub.unread_count_positive
|
||||
sub.feed.unread_count_neutral = sub.user_sub.unread_count_positive + sub.user_sub.unread_count_neutral
|
||||
sub.feed.unread_count_negative = sub.user_sub.unread_count_positive + sub.user_sub.unread_count_neutral + sub.user_sub.unread_count_negative
|
||||
except:
|
||||
logging.warn("Subscription %s does not exist outside of Folder." % (sub.feed))
|
||||
sub.delete()
|
||||
|
@ -107,13 +108,20 @@ def load_single_feed(request):
|
|||
if force_update:
|
||||
feed.update(force_update)
|
||||
|
||||
usersub = UserSubscription.objects.get(user=user, feed=feed.id)
|
||||
# Get intelligence classifier for user
|
||||
classifier_feeds = ClassifierFeed.objects.filter(user=user, feed=feed)
|
||||
classifier_authors = ClassifierAuthor.objects.filter(user=user, feed=feed)
|
||||
classifier_titles = ClassifierTitle.objects.filter(user=user, feed=feed)
|
||||
classifier_tags = ClassifierTag.objects.filter(user=user, feed=feed)
|
||||
|
||||
usersub = UserSubscription.objects.get(user=user, feed=feed)
|
||||
|
||||
# print "Feed: %s %s" % (feed, usersub)
|
||||
logging.debug("Feed: " + feed.feed_title)
|
||||
userstory = UserStory.objects.filter(
|
||||
user=user,
|
||||
feed=feed.id
|
||||
feed=feed.id,
|
||||
read_date__gt=usersub.mark_read_date
|
||||
).values()
|
||||
for story in stories:
|
||||
for o in userstory:
|
||||
|
@ -125,8 +133,16 @@ def load_single_feed(request):
|
|||
story['read_status'] = 1
|
||||
elif not story.get('read_status') and story['story_date'] > usersub.last_read_date:
|
||||
story['read_status'] = 0
|
||||
story['intelligence'] = {
|
||||
'feed': apply_classifier_feeds(classifier_feeds, feed),
|
||||
'author': apply_classifier_authors(classifier_authors, story),
|
||||
'tags': apply_classifier_tags(classifier_tags, story),
|
||||
'title': apply_classifier_titles(classifier_titles, story),
|
||||
}
|
||||
# logging.debug("Story: %s" % story)
|
||||
|
||||
# Intelligence
|
||||
|
||||
all_tags = Tag.objects.filter(feed=feed)\
|
||||
.annotate(stories_count=Count('story'))\
|
||||
.order_by('-stories_count')[:20]
|
||||
|
@ -138,7 +154,7 @@ def load_single_feed(request):
|
|||
feed_authors = [(author.author_name, author.stories_count) for author in all_authors\
|
||||
if author.stories_count > 1]
|
||||
|
||||
context = dict(stories=stories, feed_tags=feed_tags, feed_authors=feed_authors, intelligence={})
|
||||
context = dict(stories=stories, feed_tags=feed_tags, feed_authors=feed_authors)
|
||||
data = json.encode(context)
|
||||
return HttpResponse(data, mimetype='application/json')
|
||||
|
||||
|
|
|
@ -46,9 +46,9 @@ class Feed(models.Model):
|
|||
return time.time() - time.mktime(self.last_update.timetuple())
|
||||
|
||||
def new_stories_since_date(self, date):
|
||||
story_count = Story.objects.filter(story_date__gte=date,
|
||||
story_feed=self).count()
|
||||
return story_count
|
||||
stories = Story.objects.filter(story_date__gte=date,
|
||||
story_feed=self)
|
||||
return stories
|
||||
|
||||
def add_feed(self, feed_address, feed_link, feed_title):
|
||||
print locals()
|
||||
|
@ -165,21 +165,29 @@ class Feed(models.Model):
|
|||
|
||||
def get_stories(self, offset=0, limit=25):
|
||||
stories = cache.get('feed_stories:%s-%s-%s' % (self.id, offset, limit), [])
|
||||
|
||||
|
||||
if not stories:
|
||||
stories_db = Story.objects.filter(story_feed=self).select_related('story_author')[offset:offset+limit]
|
||||
for story_db in stories_db:
|
||||
story = story_db.__dict__
|
||||
story_tags = story_db.tags.all()
|
||||
story['story_tags'] = [tag.name for tag in story_tags]
|
||||
story['short_parsed_date'] = format_story_link_date__short(story['story_date'])
|
||||
story['long_parsed_date'] = format_story_link_date__long(story['story_date'])
|
||||
story['story_authors'] = story_db.story_author.author_name
|
||||
stories.append(story)
|
||||
stories_db = Story.objects.filter(story_feed=self)\
|
||||
.select_related('story_author')[offset:offset+limit]
|
||||
stories = self.format_stories(stories_db)
|
||||
cache.set('feed_stories:%s-%s-%s' % (self.id, offset, limit), stories)
|
||||
|
||||
return stories
|
||||
|
||||
def format_stories(self, stories_db):
|
||||
stories = []
|
||||
|
||||
for story_db in stories_db:
|
||||
story = story_db.__dict__
|
||||
story_tags = story_db.tags.all()
|
||||
story['story_tags'] = [tag.name for tag in story_tags]
|
||||
story['short_parsed_date'] = format_story_link_date__short(story['story_date'])
|
||||
story['long_parsed_date'] = format_story_link_date__long(story['story_date'])
|
||||
story['story_authors'] = story_db.story_author.author_name
|
||||
stories.append(story)
|
||||
|
||||
return stories
|
||||
|
||||
def get_tags(self, entry):
|
||||
fcat = []
|
||||
if entry.has_key('tags'):
|
||||
|
|
|
@ -152,7 +152,6 @@ a img {
|
|||
}
|
||||
#feed_list .feed_title {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
padding: 4px 42px 2px 45px;
|
||||
text-decoration: none;
|
||||
color: #272727;
|
||||
|
@ -161,10 +160,6 @@ a img {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
#feed_list .feed.no_unread_items .feed_title {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#feed_list .feed.selected {
|
||||
background: #f6a828 url('../theme/images/ui-bg_highlight-hard_35_f6a828_1x100.png') 0 50% repeat-x;
|
||||
border-top: 1px solid #A8A8A8;
|
||||
|
@ -178,8 +173,45 @@ a img {
|
|||
color: #FFF;
|
||||
padding: 0 6px;
|
||||
background-color: #8eb6e8;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#feed_list .unread_count_positive {
|
||||
background-color: #559F4D;
|
||||
}
|
||||
|
||||
#feed_list .unread_count_neutral {
|
||||
color: #303030;
|
||||
background-color: #F9C72A;
|
||||
}
|
||||
|
||||
#feed_list .unread_count_negative {
|
||||
background-color: #CC2A2E;
|
||||
}
|
||||
|
||||
#feed_list.unread_view_positive .feed.unread_positive .unread_count_positive {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#feed_list.unread_view_neutral .feed.unread_neutral .unread_count_neutral {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#feed_list.unread_view_negative .feed.unread_negative .unread_count_negative {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#feed_list.unread_view_positive .feed.unread_positive {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#feed_list.unread_view_neutral .feed.unread_neutral {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#feed_list.unread_view_negative .feed.unread_negative {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* ================ */
|
||||
/* = Story Titles = */
|
||||
|
@ -272,6 +304,18 @@ a img {
|
|||
background: transparent url('../img/icons/silk/bullet_orange.png') no-repeat 6px 50%;
|
||||
}
|
||||
|
||||
#story_titles .story.NB-story-positive {
|
||||
background: transparent url('../img/icons/silk/bullet_green.png') no-repeat 6px 50%;
|
||||
}
|
||||
|
||||
#story_titles .story.NB-story-neutral {
|
||||
background: transparent url('../img/icons/silk/bullet_yellow.png') no-repeat 6px 50%;
|
||||
}
|
||||
|
||||
#story_titles .story.NB-story-negative {
|
||||
background: transparent url('../img/icons/silk/bullet_red.png') no-repeat 6px 50%;
|
||||
}
|
||||
|
||||
#story_titles .story.NB-story-hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
@ -547,7 +591,7 @@ a img {
|
|||
.NB-taskbar .NB-task-view-page-to-feed-arrow {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 64px;
|
||||
right: -8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: transparent url('../img/icons/silk/arrow_switch.png') no-repeat center 0px;
|
||||
|
@ -645,6 +689,7 @@ a img {
|
|||
cursor: pointer;
|
||||
}
|
||||
.NB-taskbar .task_button.task_view_page {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background: transparent url('../img/icons/silk/application_view_tile.png') no-repeat 8px 8px;
|
||||
}
|
||||
|
@ -931,3 +976,40 @@ form.opml_import_form input {
|
|||
.simplemodal-wrap {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* ======================= */
|
||||
/* = Intelligence Slider = */
|
||||
/* ======================= */
|
||||
|
||||
.NB-taskbar-intelligence {
|
||||
width: 100px;
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
top: 16px;
|
||||
font-size: .5em;
|
||||
}
|
||||
|
||||
.NB-taskbar-intelligence .NB-taskbar-intelligence-indicator {
|
||||
position: absolute;
|
||||
top: -16px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.NB-taskbar-intelligence .NB-taskbar-intelligence-negative {
|
||||
right: 94px;
|
||||
background: transparent url(../img/icons/silk/bullet_red.png) no-repeat 0 0;
|
||||
}
|
||||
|
||||
.NB-taskbar-intelligence .NB-taskbar-intelligence-neutral {
|
||||
right: 45px;
|
||||
background: transparent url(../img/icons/silk/bullet_yellow.png) no-repeat 0 0;
|
||||
}
|
||||
|
||||
.NB-taskbar-intelligence .NB-taskbar-intelligence-positive {
|
||||
right: -4px;
|
||||
background: transparent url(../img/icons/silk/bullet_green.png) no-repeat 0 0;
|
||||
}
|
||||
|
||||
.NB-intelligence-slider {
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
this.$account_menu = $('.menu_button');
|
||||
this.$feed_view = $('.NB-feed-story-view');
|
||||
this.$story_iframe = $('.NB-feed-frame');
|
||||
this.$intelligence_slider = $('.NB-intelligence-slider');
|
||||
|
||||
this.model = NEWSBLUR.AssetModel.reader();
|
||||
this.options = {};
|
||||
|
@ -37,6 +38,7 @@
|
|||
this.handle_keystrokes();
|
||||
// this.setup_taskbar_nav_left();
|
||||
this.setup_feed_page_iframe_load();
|
||||
this.load_intelligence_slider();
|
||||
};
|
||||
|
||||
NEWSBLUR.Reader.prototype = {
|
||||
|
@ -49,14 +51,19 @@
|
|||
var read = story.read_status
|
||||
? 'read'
|
||||
: '';
|
||||
var score = this.compute_story_score(story);
|
||||
var score_color = 'neutral';
|
||||
if (score > 0) score_color = 'positive';
|
||||
if (score < 0) score_color = 'negative';
|
||||
var $story_tags = $.make('span', { className: 'NB-storytitles-tags'});
|
||||
|
||||
for (var t in story.story_tags) {
|
||||
var tag = story.story_tags[t];
|
||||
var $tag = $.make('span', { className: 'NB-storytitles-tag'}, tag).corners('4px');
|
||||
$story_tags.append($tag);
|
||||
break;
|
||||
}
|
||||
var $story_title = $.make('div', { className: 'story ' + read }, [
|
||||
var $story_title = $.make('div', { className: 'story ' + read + ' NB-story-' + score_color }, [
|
||||
$.make('a', { href: story.story_permalink, className: 'story_title' }, [
|
||||
$.make('span', { className: 'NB-storytitles-title' }, story.story_title),
|
||||
$.make('span', { className: 'NB-storytitles-author' }, story.story_authors),
|
||||
|
@ -71,6 +78,22 @@
|
|||
return $story_title;
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
// ========
|
||||
// = Page =
|
||||
// ========
|
||||
|
@ -211,9 +234,9 @@
|
|||
$current_story = $('.story:first', this.$story_titles);
|
||||
$next_story = $current_story;
|
||||
} else if (direction == 1) {
|
||||
$next_story = $current_story.next('.story');
|
||||
$next_story = $current_story.nextAll('.story:visible').eq(0);
|
||||
} else if (direction == -1) {
|
||||
$next_story = $current_story.prev('.story');
|
||||
$next_story = $current_story.prevAll('.story:visible').eq(0);
|
||||
}
|
||||
|
||||
var story_id = $('.story_id', $next_story).text();
|
||||
|
@ -307,7 +330,7 @@
|
|||
var callback = function() {
|
||||
var $feed_list = self.$feed_list.empty();
|
||||
var folders = self.model.folders;
|
||||
|
||||
|
||||
$('#story_taskbar').css({'display': 'block'});
|
||||
// NEWSBLUR.log(['Subscriptions', {'folders':folders}]);
|
||||
for (fo in folders) {
|
||||
|
@ -317,15 +340,38 @@
|
|||
$.make('div', { className: 'feeds' })
|
||||
]);
|
||||
for (f in feeds) {
|
||||
var $feed = $.make('div', { className: 'feed' }, [
|
||||
$.make('span', { className: 'unread_count' }, ''+feeds[f].unread_count),
|
||||
var unread_class = '';
|
||||
if (feeds[f].unread_count_positive) {
|
||||
unread_class += ' unread_positive';
|
||||
}
|
||||
if (feeds[f].unread_count_neutral) {
|
||||
unread_class += ' unread_neutral';
|
||||
}
|
||||
if (feeds[f].unread_count_negative) {
|
||||
unread_class += ' unread_negative';
|
||||
}
|
||||
var $feed = $.make('div', { className: 'feed ' + unread_class }, [
|
||||
$.make('span', {
|
||||
className: 'unread_count unread_count_positive '
|
||||
+ (feeds[f].unread_count_positive
|
||||
? "unread_count_full"
|
||||
: "unread_count_empty")
|
||||
}, ''+feeds[f].unread_count_positive),
|
||||
$.make('span', {
|
||||
className: 'unread_count unread_count_neutral '
|
||||
+ (feeds[f].unread_count_neutral
|
||||
? "unread_count_full"
|
||||
: "unread_count_empty")
|
||||
}, ''+feeds[f].unread_count_neutral),
|
||||
$.make('span', {
|
||||
className: 'unread_count unread_count_negative '
|
||||
+ (feeds[f].unread_count_negative
|
||||
? "unread_count_full"
|
||||
: "unread_count_empty")
|
||||
}, ''+feeds[f].unread_count_negative),
|
||||
$.make('img', { className: 'feed_favicon', src: self.google_favicon_url + feeds[f].feed_link }),
|
||||
$.make('span', { className: 'feed_title' }, feeds[f].feed_title)
|
||||
]).data('feed_id', feeds[f].id);
|
||||
if (feeds[f].unread_count <= 0) {
|
||||
$('.unread_count', $feed).css('display', 'none');
|
||||
$feed.addClass('no_unread_items');
|
||||
}
|
||||
$('.feeds', $folder).append($feed);
|
||||
}
|
||||
$feed_list.append($folder);
|
||||
|
@ -778,7 +824,7 @@
|
|||
var $feed_view = this.$feed_view;
|
||||
var $story = this.find_story_in_feed_view(story);
|
||||
|
||||
NEWSBLUR.log(['scroll_to_story_in_story_feed', story, $story]);
|
||||
// NEWSBLUR.log(['scroll_to_story_in_story_feed', story, $story]);
|
||||
|
||||
if ($story && $story.length) {
|
||||
if (this.story_view == 'feed' || this.page_view_showing_feed_view) {
|
||||
|
@ -835,7 +881,7 @@
|
|||
if (story) {
|
||||
var self = this;
|
||||
var $story = this.find_story_in_story_frame(story);
|
||||
NEWSBLUR.log(['Prefetching story', s, story, $story]);
|
||||
// NEWSBLUR.log(['Prefetching story', s, story, $story]);
|
||||
|
||||
setTimeout(function() {
|
||||
// NEWSBLUR.log(['Fetching next story', s]);
|
||||
|
@ -864,10 +910,10 @@
|
|||
var $story, $stories = [], title_words, shortened_title, $reduced_stories = [];
|
||||
|
||||
if (story.id in this.cache.iframe_stories || this.flags.story_frame_prefetched) {
|
||||
NEWSBLUR.log(['Cached story frame', this.cache.iframe_stories[story.id], story]);
|
||||
// NEWSBLUR.log(['Cached story frame', this.cache.iframe_stories[story.id], story]);
|
||||
return this.cache.iframe_stories[story.id];
|
||||
} else {
|
||||
NEWSBLUR.log(['Cache story frame miss', this.cache.iframe_stories, story]);
|
||||
// NEWSBLUR.log(['Cache story frame miss', this.cache.iframe_stories, story]);
|
||||
}
|
||||
|
||||
var $iframe_contents = $iframe.contents();
|
||||
|
@ -1021,12 +1067,49 @@
|
|||
mark_story_as_read: function(story_id, $story_title) {
|
||||
var self = this;
|
||||
var feed_id = this.active_feed;
|
||||
|
||||
var $feed_list = this.$feed_list;
|
||||
var $feed = $('.feed.selected', $feed_list);
|
||||
|
||||
var callback = function() {
|
||||
var unread_count_positive = parseInt($('.unread_count_positive', $feed).text(), 10);
|
||||
var unread_count_neutral = parseInt($('.unread_count_neutral', $feed).text(), 10);
|
||||
var unread_count_negative = parseInt($('.unread_count_negative', $feed).text(), 10);
|
||||
NEWSBLUR.log(['marked read', unread_count_positive, unread_count_neutral, unread_count_negative, $story_title.is('.NB-story-positive'), $story_title.is('.NB-story-neutral'), $story_title.is('.NB-story-negative')]);
|
||||
|
||||
if ($story_title.is('.NB-story-positive')) {
|
||||
$('.unread_count_positive', $feed).text(unread_count_positive-1);
|
||||
$('.unread_count_neutral', $feed).text(unread_count_neutral-1);
|
||||
$('.unread_count_negative', $feed).text(unread_count_negative-1);
|
||||
if (unread_count_positive == 1) {
|
||||
$feed.removeClass('unread_positive');
|
||||
} else {
|
||||
$feed.addClass('unread_positive');
|
||||
}
|
||||
} else if ($story_title.is('.NB-story-neutral')) {
|
||||
$('.unread_count_neutral', $feed).text(unread_count_neutral-1);
|
||||
$('.unread_count_negative', $feed).text(unread_count_negative-1);
|
||||
if (unread_count_neutral == 1) {
|
||||
$feed.removeClass('unread_positive');
|
||||
$feed.removeClass('unread_neutral');
|
||||
} else {
|
||||
$feed.addClass('unread_neutral');
|
||||
}
|
||||
} else if ($story_title.is('.NB-story-negative')) {
|
||||
$('.unread_count_negative', $feed).text(unread_count_negative-1);
|
||||
if (unread_count_negative == 1) {
|
||||
$feed.removeClass('unread_positive');
|
||||
$feed.removeClass('unread_neutral');
|
||||
$feed.removeClass('unread_negative');
|
||||
} else {
|
||||
$feed.addClass('unread_negative');
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
$story_title.addClass('read');
|
||||
|
||||
if (NEWSBLUR.Globals.is_authenticated) {
|
||||
this.model.mark_story_as_read(story_id, feed_id, callback);
|
||||
}
|
||||
|
@ -1373,7 +1456,83 @@
|
|||
$taskbar_button.removeClass('active');
|
||||
$('.taskbar_menu', $taskbar_button).removeShadow();
|
||||
$(document).unbind('click.taskbar_menu');
|
||||
},
|
||||
|
||||
// ================
|
||||
// = Intelligence =
|
||||
// ================
|
||||
|
||||
load_intelligence_slider: function() {
|
||||
var self = this;
|
||||
var $slider = this.$intelligence_slider;
|
||||
var unread_view = NEWSBLUR.Globals.unread_view;
|
||||
|
||||
this.switch_feed_view_unread_view(unread_view);
|
||||
|
||||
$slider.slider({
|
||||
range: 'max',
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 1,
|
||||
value: NEWSBLUR.Globals.unread_view,
|
||||
slide: function(e, ui) {
|
||||
self.switch_feed_view_unread_view(ui.value);
|
||||
},
|
||||
stop: function(e, ui) {
|
||||
self.save_profile('unread_view', ui.value);
|
||||
self.show_correct_story_titles_in_unread_view();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
switch_feed_view_unread_view: function(unread_view) {
|
||||
var $feed_list = this.$feed_list;
|
||||
var unread_view_name = this.get_unread_view_name(unread_view);
|
||||
|
||||
$feed_list.removeClass('unread_view_positive')
|
||||
.removeClass('unread_view_neutral')
|
||||
.removeClass('unread_view_negative')
|
||||
.addClass('unread_view_'+unread_view_name);
|
||||
},
|
||||
|
||||
get_unread_view_name: function(unread_view) {
|
||||
return (unread_view > 0
|
||||
? 'positive'
|
||||
: unread_view < 0
|
||||
? 'negative'
|
||||
: 'neutral');
|
||||
},
|
||||
|
||||
show_correct_story_titles_in_unread_view: function() {
|
||||
var unread_view = NEWSBLUR.Globals.unread_view;
|
||||
var $story_titles = this.$story_titles;
|
||||
var unread_view_name = this.get_unread_view_name(unread_view);
|
||||
var $stories_show, $stories_hide;
|
||||
|
||||
if (unread_view_name == 'positive') {
|
||||
$stories_show = $('.story').filter('.NB-story-positive');
|
||||
$stories_hide = $('.story').filter('.NB-story-neutral,.NB-story-negative');
|
||||
} else if (unread_view_name == 'neutral') {
|
||||
$stories_show = $('.story').filter('.NB-story-positive,.NB-story-neutral');
|
||||
$stories_hide = $('.story').filter('.NB-story-negative');
|
||||
} else if (unread_view_name == 'negative') {
|
||||
$stories_show = $('.story').filter('.NB-story-positive,.NB-story-neutral,.NB-story-negative');
|
||||
$stories_hide = $();
|
||||
}
|
||||
|
||||
// NEWSBLUR.log(['showing correct stories', unread_view_name, $stories_show, $stories_hide]);
|
||||
$stories_show.slideDown(500);
|
||||
$stories_hide.slideUp(500);
|
||||
},
|
||||
|
||||
// ===========
|
||||
// = Profile =
|
||||
// ===========
|
||||
|
||||
save_profile: function(key, value) {
|
||||
NEWSBLUR.Globals[key] = value;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
|
|
@ -173,7 +173,8 @@ COMPRESS_JS = {
|
|||
'js/jquery.ajaxupload.js',
|
||||
'js/jquery.simplemodal-1.3.js',
|
||||
'js/jquery.color.js',
|
||||
'js/jquery-ui-1.7.2.custom.min.js',
|
||||
'js/jquery.ui.core.js',
|
||||
'js/jquery.ui.slider.js',
|
||||
'js/jquery.layout.js',
|
||||
'js/jquery.fieldselection.js',
|
||||
|
||||
|
@ -190,6 +191,7 @@ COMPRESS_CSS = {
|
|||
'all': {
|
||||
'source_filenames': (
|
||||
'css/reader.css',
|
||||
'css/jquery-ui/jquery.theme.css',
|
||||
),
|
||||
'output_filename': 'release/all-compressed-?.css'
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
NEWSBLUR.Globals = {
|
||||
'is_authenticated': {% if user.is_authenticated %}true{% else %}false{% endif %},
|
||||
'is_anonymous': {% if user.is_anonymous %}true{% else %}false{% endif %},
|
||||
'username': "{{ user.username|safe }}"
|
||||
'username': "{{ user.username|safe }}",
|
||||
'unread_view': 0{# user.profile.unread_view|safe #}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
|
|
@ -10,6 +10,12 @@
|
|||
<li class="task_button task_button_menu task_add NB-task-add">
|
||||
</li>
|
||||
</ul>
|
||||
<div class="NB-taskbar-intelligence">
|
||||
<div class="NB-taskbar-intelligence-indicator NB-taskbar-intelligence-negative"></div>
|
||||
<div class="NB-taskbar-intelligence-indicator NB-taskbar-intelligence-neutral"></div>
|
||||
<div class="NB-taskbar-intelligence-indicator NB-taskbar-intelligence-positive"></div>
|
||||
<div class="NB-intelligence-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -56,10 +62,11 @@
|
|||
|
||||
<div class="content-pane">
|
||||
<div id="story_taskbar" class="NB-taskbar content-north">
|
||||
<div class="NB-task-view-page-to-feed-arrow"></div>
|
||||
<ul class="taskbar_nav taskbar_nav_left">
|
||||
<li class="task_button task_button_view task_view_page NB-active"><span class="NB-task-title">Page</span></li>
|
||||
<li class="task_button task_button_view task_view_feed"><span class="NB-task-title">Feed</span></li>
|
||||
<li class="task_button task_button_view task_view_page NB-active"><span class="NB-task-title">Native</span>
|
||||
<div class="NB-task-view-page-to-feed-arrow"></div>
|
||||
</li>
|
||||
<li class="task_button task_button_view task_view_feed"><span class="NB-task-title">Remote</span></li>
|
||||
</ul>
|
||||
|
||||
<ul class="taskbar_nav taskbar_nav_return">
|
||||
|
|
|
@ -190,6 +190,7 @@ class ProcessFeed:
|
|||
|
||||
return FEED_OK, ret_values
|
||||
|
||||
|
||||
class Dispatcher:
|
||||
def __init__(self, options, num_threads):
|
||||
self.options = options
|
||||
|
@ -254,10 +255,11 @@ class Dispatcher:
|
|||
|
||||
fpage = FetchPage(feed, self.options)
|
||||
fpage.fetch()
|
||||
|
||||
del ffeed
|
||||
del pfeed
|
||||
del fpage
|
||||
|
||||
if ENTRY_NEW in ret_entries and ret_entries[ENTRY_NEW]:
|
||||
user_subs = UserSubscription.objects.filter(feed=feed)
|
||||
for sub in user_subs:
|
||||
sub.calculate_feed_scores()
|
||||
except:
|
||||
(etype, eobj, etb) = sys.exc_info()
|
||||
print '[%d] ! -------------------------' % (feed.id,)
|
||||
|
@ -266,6 +268,12 @@ class Dispatcher:
|
|||
print '[%d] ! -------------------------' % (feed.id,)
|
||||
ret_feed = FEED_ERREXC
|
||||
ret_entries = {}
|
||||
finally:
|
||||
del ffeed
|
||||
del pfeed
|
||||
del fpage
|
||||
if ENTRY_NEW in ret_entries and ret_entries[ENTRY_NEW]:
|
||||
del user_subs
|
||||
|
||||
delta = datetime.datetime.now() - start_time
|
||||
if delta.seconds > SLOWFEED_WARNING:
|
||||
|
|
|
@ -5,6 +5,11 @@ from django.utils.encoding import force_unicode
|
|||
from django.utils import simplejson as json
|
||||
from decimal import Decimal
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.core.mail import mail_admins
|
||||
from django.utils.translation import ugettext as _
|
||||
import sys
|
||||
|
||||
def decode(data):
|
||||
return json.loads(data)
|
||||
|
||||
|
@ -73,3 +78,42 @@ def json_encode(data):
|
|||
ret = _any(data)
|
||||
|
||||
return json.dumps(ret, cls=DateTimeAwareJSONEncoder, ensure_ascii=False)
|
||||
|
||||
def json_view(func):
|
||||
def wrap(request, *a, **kw):
|
||||
response = None
|
||||
try:
|
||||
response = dict(func(request, *a, **kw))
|
||||
assert isinstance(response, dict)
|
||||
if 'result' not in response:
|
||||
response['result'] = 'ok'
|
||||
except KeyboardInterrupt:
|
||||
# Allow keyboard interrupts through for debugging.
|
||||
raise
|
||||
except Exception, e:
|
||||
# Mail the admins with the error
|
||||
exc_info = sys.exc_info()
|
||||
subject = 'JSON view error: %s' % request.path
|
||||
try:
|
||||
request_repr = repr(request)
|
||||
except:
|
||||
request_repr = 'Request repr() unavailable'
|
||||
import traceback
|
||||
message = 'Traceback:\n%s\n\nRequest:\n%s' % (
|
||||
'\n'.join(traceback.format_exception(*exc_info)),
|
||||
request_repr,
|
||||
)
|
||||
print message
|
||||
# mail_admins(subject, message, fail_silently=True)
|
||||
|
||||
# Come what may, we're returning JSON.
|
||||
if hasattr(e, 'message'):
|
||||
msg = e.message
|
||||
else:
|
||||
msg = _('Internal error')+': '+str(e)
|
||||
response = {'result': 'error',
|
||||
'text': msg}
|
||||
|
||||
json = json_encode(response)
|
||||
return HttpResponse(json, mimetype='application/json')
|
||||
return wrap
|
||||
|
|
Loading…
Add table
Reference in a new issue