Text view. Needs a few tweaks and a premium only view.

This commit is contained in:
Samuel Clay 2013-01-08 18:33:30 -08:00
parent 9303e3d6fb
commit 36028cc719
14 changed files with 255 additions and 46 deletions

View file

@ -22,6 +22,7 @@ from mongoengine.queryset import OperationError, Q
from mongoengine.base import ValidationError
from vendor.timezones.utilities import localtime_for_timezone
from apps.rss_feeds.tasks import UpdateFeeds, PushFeeds
from apps.rss_feeds.text_importer import TextImporter
from apps.search.models import SearchStarredStory, SearchFeed
from utils import json_functions as json
from utils import feedfinder, feedparser
@ -1410,6 +1411,7 @@ class MStory(mongo.Document):
story_original_content_z = mongo.BinaryField()
story_latest_content = mongo.StringField()
story_latest_content_z = mongo.BinaryField()
original_text_z = mongo.BinaryField()
story_content_type = mongo.StringField(max_length=255)
story_author_name = mongo.StringField()
story_permalink = mongo.StringField()
@ -1465,17 +1467,19 @@ class MStory(mongo.Document):
super(MStory, self).delete(*args, **kwargs)
@classmethod
def find_story(cls, story_feed_id, story_id):
def find_story(cls, story_feed_id, story_id, original_only=False):
from apps.social.models import MSharedStory
original_found = True
story = cls.objects(story_feed_id=story_feed_id,
story_guid=story_id).limit(1).first()
if not story:
original_found = False
if not story and not original_only:
story = MSharedStory.objects.filter(story_feed_id=story_feed_id,
story_guid=story_id).limit(1).first()
if not story:
if not story and not original_only:
story = MStarredStory.objects.filter(story_feed_id=story_feed_id,
story_guid=story_id).limit(1).first()
@ -1543,6 +1547,18 @@ class MStory(mongo.Document):
self.share_count = shares.count()
self.share_user_ids = [s['user_id'] for s in shares]
self.save()
def fetch_original_text(self, force=False, request=None):
original_text_z = self.original_text_z
if not original_text_z or force:
ti = TextImporter(self, request=request)
original_text = ti.fetch()
else:
logging.user(request, "~FYFetching ~FGoriginal~FY story text, ~SBfound.")
original_text = zlib.decompress(original_text_z)
return original_text
class MStarredStory(mongo.Document):

View file

@ -0,0 +1,42 @@
import requests
import zlib
from django.conf import settings
from vendor.readability import readability
from utils import log as logging
class TextImporter:
def __init__(self, story, request=None):
self.story = story
self.request = request
@property
def headers(self):
return {
'User-Agent': 'NewsBlur Content Fetcher - %s '
'(Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) '
'AppleWebKit/534.48.3 (KHTML, like Gecko) Version/5.1 '
'Safari/534.48.3)' % (
settings.NEWSBLUR_URL
),
'Connection': 'close',
}
def fetch(self):
html = requests.get(self.story.story_permalink, headers=self.headers)
original_text_doc = readability.Document(html.text)
content = original_text_doc.summary()
if content:
self.story.original_text_z = zlib.compress(content)
self.story.save()
logging.user(self.request, "~SN~FYFetched ~FGoriginal text~FY: now ~SB%s bytes~SN vs. was ~SB%s bytes" % (
len(unicode(content)),
len(zlib.decompress(self.story.story_content_z))
))
else:
logging.user(self.request, "~SN~FRFailed~FY to fetch ~FGoriginal text~FY: was ~SB%s bytes" % (
len(zlib.decompress(self.story.story_content_z))
))
return content

View file

@ -13,4 +13,5 @@ urlpatterns = patterns('',
url(r'^exception_change_feed_link', views.exception_change_feed_link, name='exception-change-feed-link'),
url(r'^status', views.status, name='status'),
url(r'^load_single_feed', views.load_single_feed, name='feed-canonical'),
url(r'^original_text', views.original_text, name='original-text'),
)

View file

@ -13,11 +13,13 @@ from apps.rss_feeds.models import MFeedFetchHistory, MPageFetchHistory, MFeedPus
from apps.rss_feeds.models import MFeedIcon
from apps.analyzer.models import get_classifiers_for_user
from apps.reader.models import UserSubscription
from apps.rss_feeds.models import MStory
from utils.user_functions import ajax_login_required
from utils import json_functions as json, feedfinder
from utils.feed_functions import relative_timeuntil, relative_timesince
from utils.user_functions import get_user
from utils.view_functions import get_argument_or_404
from utils.view_functions import required_params
@json.json_view
@ -399,4 +401,25 @@ def status(request):
feeds = Feed.objects.filter(last_update__gte=hour_ago).order_by('-last_update')
return render_to_response('rss_feeds/status.xhtml', {
'feeds': feeds
}, context_instance=RequestContext(request))
}, context_instance=RequestContext(request))
@required_params('story_id', feed_id=int)
@json.json_view
def original_text(request):
story_id = request.GET['story_id']
feed_id = request.GET['feed_id']
force = request.GET.get('force', False)
story, _ = MStory.find_story(story_id=story_id, story_feed_id=feed_id)
if not story:
logging.user(request, "~FYFetching ~FGoriginal~FY story text: ~FRstory not found")
return {'code': -1, 'message': 'Story not found.'}
original_text = story.fetch_original_text(force=force, request=request)
return {
'feed_id': feed_id,
'story_id': story_id,
'original_text': original_text,
}

View file

@ -22,11 +22,11 @@ from apps.reader.models import UserSubscription, MUserStory
from apps.analyzer.models import MClassifierFeed, MClassifierAuthor, MClassifierTag, MClassifierTitle
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds, apply_classifier_authors, apply_classifier_tags
from apps.rss_feeds.models import Feed, MStory
from apps.rss_feeds.text_importer import TextImporter
from apps.profile.models import Profile, MSentEmail
from vendor import facebook
from vendor import tweepy
from vendor import pynliner
from vendor.readability import readability
from utils import log as logging
from utils import json_functions as json
from utils.feed_functions import relative_timesince
@ -1186,7 +1186,7 @@ class MSharedStory(mongo.Document):
story_content_z = mongo.BinaryField()
story_original_content = mongo.StringField()
story_original_content_z = mongo.BinaryField()
original_article_z = mongo.BinaryField()
original_text_z = mongo.BinaryField()
story_content_type = mongo.StringField(max_length=255)
story_author_name = mongo.StringField()
story_permalink = mongo.StringField()
@ -1900,25 +1900,8 @@ class MSharedStory(mongo.Document):
return image_sizes
def fetch_original_text(self):
headers = {
'User-Agent': 'NewsBlur Content Fetcher - %s '
'(Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) '
'AppleWebKit/534.48.3 (KHTML, like Gecko) Version/5.1 '
'Safari/534.48.3)' % (
settings.NEWSBLUR_URL
),
'Connection': 'close',
}
html = requests.get(self.story_permalink, headers=headers).text
original_text_doc = readability.Document(html)
content = original_text_doc.content()
self.original_article_z = content and zlib.compress(content)
self.save()
logging.debug(" ---> ~SN~FGFetched original text on shared story: now ~SB%s bytes~SN vs. was ~SB%s bytes" % (
len(unicode(content)),
len(zlib.decompress(self.story_content_z))
))
ti = TextImporter(self)
original_text_doc = ti.fetch()
return original_text_doc

View file

@ -1683,7 +1683,8 @@ background: transparent;
}
#story_pane .NB-feed-iframe,
#story_pane .NB-story-iframe {
#story_pane .NB-story-iframe,
#story_pane .NB-text-view {
width: 100%;
height: 100%;
border: 0;
@ -1698,6 +1699,10 @@ background: transparent;
left: 200%;
top: 0;
}
#story_pane .NB-text-view {
left: 300%;
top: 0;
}
/* ================================ */
/* = Feed View Feed Title Floater = */
@ -1881,10 +1886,14 @@ background: transparent;
/* text-shadow: 0 1px 0 #E0E0E0;*/
}
#story_pane .NB-feed-stories .NB-feed-story .NB-feed-story-content div {
.NB-feed-story .NB-feed-story-content div {
max-width: 100%;
}
#story_pane .NB-feed-stories .NB-feed-story .NB-feed-story-content img {
.NB-feed-story .NB-feed-story-content pre,
.NB-feed-story .NB-feed-story-content code {
white-space: normal;
}
.NB-feed-story .NB-feed-story-content img {
max-width: 100% !important;
width: auto;
height: auto !important;
@ -2543,7 +2552,7 @@ background: transparent;
background: -moz-linear-gradient(center bottom, #bababa 0%, #dadada 100%);
}
#story_pane .NB-feed-stories .NB-feed-story {
.NB-feed-story {
margin: 0;
clear: both;
overflow: hidden;
@ -3342,6 +3351,9 @@ background: transparent;
.NB-taskbar .task_button.task_view_story .NB-task-image {
background: transparent url('/media/embed/icons/silk/application_view_gallery.png') no-repeat 0 0;
}
.NB-taskbar .task_button.task_view_text .NB-task-image {
background: transparent url('/media/embed/icons/silk/application_view_columns.png') no-repeat 0 0;
}
.NB-taskbar .task_button.task_view_story.NB-disabled-page .NB-task-image {
background-image: url('/media/embed/icons/silk/error.png');
}

View file

@ -257,7 +257,7 @@ body.NB-iphone {
.NB-story code {
overflow: auto;
clear: both;
white-space: normal;
}
.NB-story h1,

View file

@ -1359,6 +1359,16 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
this.make_request('/oauth/unfollow_twitter_account', {'username': username}, callback);
},
fetch_original_text: function(story_id, feed_id, callback, error_callback) {
this.make_request('/rss_feeds/original_text', {
story_id: story_id,
feed_id: feed_id
}, callback, error_callback, {
request_type: 'GET',
ajax_group: 'feed'
});
},
recalculate_story_scores: function(feed_id, options) {
options = options || {};
this.stories.each(_.bind(function(story, i) {

View file

@ -29,6 +29,7 @@
$feed_stories: $('.NB-feed-stories'),
$feed_iframe: $('.NB-feed-iframe'),
$story_iframe: $('.NB-story-iframe'),
$text_view: $('.NB-text-view'),
$intelligence_slider: $('.NB-intelligence-slider'),
$mouse_indicator: $('#mouse-indicator'),
$feed_link_loader: $('#NB-feeds-list-loader'),
@ -113,6 +114,7 @@
NEWSBLUR.app.story_list = new NEWSBLUR.Views.StoryListView({collection: NEWSBLUR.assets.stories});
NEWSBLUR.app.original_tab_view = new NEWSBLUR.Views.OriginalTabView({collection: NEWSBLUR.assets.stories});
NEWSBLUR.app.story_tab_view = new NEWSBLUR.Views.StoryTabView({collection: NEWSBLUR.assets.stories});
NEWSBLUR.app.text_tab_view = new NEWSBLUR.Views.TextTabView({collection: NEWSBLUR.assets.stories});
NEWSBLUR.app.feed_selector = new NEWSBLUR.Views.FeedSelector();
NEWSBLUR.app.follow_requests_module = new NEWSBLUR.Views.FollowRequestsModule();
@ -487,7 +489,7 @@
var story = NEWSBLUR.assets.stories.get_next_story(direction, {
score: this.get_unread_view_score()
});
console.log(["show_next_story", direction, story]);
if (story) {
story.set('selected', true);
}
@ -1190,7 +1192,7 @@
this.make_story_titles_pane_counter();
this.find_story_with_action_preference_on_open_feed();
if (this.story_view == 'story' &&
if (_.contains(['story', 'text'], this.story_view) &&
!this.active_story &&
!this.counts['find_next_unread_on_page_of_feed_stories_load']) {
this.show_next_story(1);
@ -1683,7 +1685,8 @@
// = Story loading =
// =================
show_stories_progress_bar: function(feeds_loading) {
show_stories_progress_bar: function(feeds_loading, message) {
message = message || "Fetching stories";
if (NEWSBLUR.app.story_unread_counter) {
NEWSBLUR.app.story_unread_counter.remove();
}
@ -1703,7 +1706,7 @@
else unreads = this.get_unread_count(false) / 10;
this.animate_progress_bar($bar, unreads / 10);
$('.NB-river-progress-text', $progress).text('Fetching stories');
$('.NB-river-progress-text', $progress).text(message);
// Center the progress bar
var i_width = $progress.width();
var o_width = this.$s.$story_taskbar.width();
@ -1722,14 +1725,14 @@
});
},
show_stories_error: function(data) {
show_stories_error: function(data, message) {
NEWSBLUR.log(["show_stories_error", data]);
this.hide_stories_progress_bar();
NEWSBLUR.app.original_tab_view.iframe_not_busting();
this.model.flags['no_more_stories'] = true;
var message = "Oh no! <br> There was an error!";
message = message || "Oh no! <br> There was an error!";
if (data && data.status) {
if (data.status == 502) {
message = "NewsBlur is down right now. <br> Try again soon.";
@ -2371,6 +2374,18 @@
if (!this.active_story) {
this.show_next_story(1);
}
} else if (view == 'text') {
$story_pane.animate({
'left': -3 * $feed_iframe.width()
}, {
'easing': 'easeInOutQuint',
'duration': this.model.preference('animations') ? 550 : 0,
'queue': false
});
NEWSBLUR.app.text_tab_view.load_story();
if (!this.active_story) {
this.show_next_story(1);
}
}
this.setup_mousemove_on_views();
@ -2387,6 +2402,8 @@
view = 'page';
} else if ($active.hasClass('task_view_story')) {
view = 'feed';
} else if ($active.hasClass('task_view_text')) {
view = 'story';
}
} else if (direction == 1) {
if ($active.hasClass('task_view_page')) {
@ -2394,6 +2411,8 @@
} else if ($active.hasClass('task_view_feed')) {
view = 'story';
} else if ($active.hasClass('task_view_story')) {
view = 'text';
} else if ($active.hasClass('task_view_text')) {
// view = 'story';
}
}
@ -5042,6 +5061,10 @@
e.preventDefault();
self.switch_taskbar_view('story');
});
$.targetIs(e, { tagSelector: '.task_view_text' }, function($t, $p){
e.preventDefault();
self.switch_taskbar_view('text');
});
$.targetIs(e, { tagSelector: '.NB-task-return' }, function($t, $p){
e.preventDefault();
NEWSBLUR.app.original_tab_view.load_feed_iframe();

View file

@ -7,7 +7,6 @@ NEWSBLUR.ReaderSocialProfile = function(user_id, options) {
this.model = NEWSBLUR.assets;
this.profiles = new NEWSBLUR.Collections.Users();
user_id = parseInt(_.string.ltrim(user_id, 'social:'), 10);
NEWSBLUR.log(["user_id", user_id]);
this.runner(user_id);
};

View file

@ -35,7 +35,7 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
// this.$el.bind('mouseenter', this.mouseenter);
// this.$el.bind('mouseleave', this.mouseleave);
if (!this.options.feed_floater) {
if (!this.options.feed_floater && !this.options.text_view) {
this.model.story_view = this;
}
},
@ -56,6 +56,9 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
profile: NEWSBLUR.assets.user_profile
});
this.$el.html(this.template(params));
if (this.feed) {
this.$el.toggleClass('NB-inverse', this.feed.is_light());
}
this.toggle_classes();
this.toggle_read_status();
this.toggle_score();
@ -75,10 +78,10 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
get_render_params: function() {
this.feed = NEWSBLUR.assets.get_feed(this.model.get('story_feed_id'));
this.classifiers = NEWSBLUR.assets.classifiers[this.model.get('story_feed_id')];
var show_feed_title = NEWSBLUR.reader.flags.river_view || this.options.show_feed_title;
return {
story : this.model,
feed : NEWSBLUR.reader.flags.river_view && this.feed,
feed : show_feed_title && this.feed,
tag : _.first(this.model.get("story_tags")),
title : this.make_story_title(),
authors_score : this.classifiers && this.classifiers.authors[this.model.get('story_authors')],
@ -139,7 +142,9 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
<%= story_header %>\
<div class="NB-feed-story-shares-container"></div>\
<div class="NB-feed-story-content">\
<%= story.get("story_content") %>\
<% if (!options.skip_content) { %>\
<%= story.get("story_content") %>\
<% } %>\
</div>\
<div class="NB-feed-story-comments-container"></div>\
<div class="NB-feed-story-sideoptions-container">\
@ -176,9 +181,10 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
$header.css('textShadow', NEWSBLUR.utils.generate_shadow(this.feed));
},
make_story_title: function() {
var title = this.model.get('story_title');
var classifiers = NEWSBLUR.assets.classifiers[this.model.get('story_feed_id')];
make_story_title: function(story) {
story = story || this.model;
var title = story.get('story_title');
var classifiers = NEWSBLUR.assets.classifiers[story.get('story_feed_id')];
var feed_titles = classifiers && classifiers.titles || [];
_.each(feed_titles, function(score, title_classifier) {
@ -229,9 +235,6 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({
var unread_view = NEWSBLUR.reader.get_unread_view_score();
var score = story.score();
if (this.feed) {
this.$el.toggleClass('NB-inverse', this.feed.is_light());
}
this.$el.toggleClass('NB-river-story', NEWSBLUR.reader.flags.river_view);
this.$el.toggleClass('NB-story-starred', !!story.get('starred'));
this.$el.toggleClass('NB-story-shared', !!story.get('shared'));

View file

@ -0,0 +1,83 @@
NEWSBLUR.Views.TextTabView = Backbone.View.extend({
initialize: function() {
this.setElement(NEWSBLUR.reader.$s.$text_view);
this.collection.bind('change:selected', this.select_story, this);
this.$story = this.$('.NB-text-view-detail');
},
// ===========
// = Actions =
// ===========
load_story: function(story, is_temporary) {
if (!story) story = NEWSBLUR.reader.active_story;
if (!story) return;
this.story = story;
this.$story.html(new NEWSBLUR.Views.StoryDetailView({
model: this.story,
show_feed_title: true,
skip_content: true,
text_view: true
}).render().el);
// NEWSBLUR.reader.switch_taskbar_view('text', {
// skip_save_type: is_temporary ? 'text' : false
// });
this.show_loading();
NEWSBLUR.assets.fetch_original_text(story.get('id'), story.get('story_feed_id'),
_.bind(this.render, this),
_.bind(this.error, this));
},
render: function(data) {
if (data.story_id != this.story.get('id') ||
data.feed_id != this.story.get('story_feed_id')) {
return;
}
var original_text = data.original_text;
this.hide_loading();
var $content = this.$('.NB-feed-story-content');
if (original_text.length < this.story.get('story_content').length) {
$content.html(this.story.get('story_content'));
} else {
$content.html(original_text);
}
$content.css('opacity', 0);
$content.show();
$content.animate({
'opacity': 1
}, {
duration: 250,
queue: false
});
},
show_loading: function() {
NEWSBLUR.reader.hide_stories_error();
NEWSBLUR.reader.show_stories_progress_bar(10, "Fetching text");
},
hide_loading: function() {
NEWSBLUR.reader.hide_stories_progress_bar();
},
error: function() {
this.hide_loading();
NEWSBLUR.reader.show_stories_error({}, "Sorry, the story\'s text<br />could not be extracted.");
},
// ==========
// = Events =
// ==========
select_story: function(story, selected) {
if (selected && NEWSBLUR.reader.story_view == 'text') {
this.load_story(story);
}
}
});

View file

@ -176,6 +176,13 @@
<span class="NB-task-title">Story</span>
</div>
</li>
<li class="task_button task_button_view task_view_text">
<div class="NB-task-button-wrapper">
<div class="task_button_background"></div>
<div class="NB-task-image"></div>
<span class="NB-task-title">Text</span>
</div>
</li>
</ul>
<ul class="taskbar_nav taskbar_nav_story first">
<li class="task_button task_button_story task_story_previous">
@ -226,6 +233,9 @@
<ul class="NB-feed-stories"></ul>
</div>
<iframe id="story_iframe" class="NB-story-iframe"></iframe>
<div class="NB-text-view">
<div class="NB-text-view-detail"></div>
</div>
</div>
</div>

View file

@ -16,6 +16,10 @@ def getlogger():
def user(u, msg, request=None):
from apps.statistics.models import MAnalyticsPageLoad
if not u:
return debug(msg)
platform = '------'
time_elapsed = ""
if isinstance(u, WSGIRequest) or request: