mirror of
https://github.com/viq/NewsBlur.git
synced 2025-09-18 21:43:31 +00:00
Adding a classifier to all stories. Classifies on:
- Story title (highlighted phrase) - Story author - Feed
This commit is contained in:
parent
838a57baa8
commit
ef70c288ed
6 changed files with 314 additions and 22 deletions
|
@ -2,6 +2,7 @@ from django.db import models
|
|||
from django.db import IntegrityError
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core import serializers
|
||||
from django.core.cache import cache
|
||||
from utils import feedparser, object_manager
|
||||
from utils.dateutil.parser import parse as dateutil_parse
|
||||
|
@ -157,16 +158,16 @@ class Feed(models.Model):
|
|||
story.save()
|
||||
|
||||
def get_stories(self, offset=0, limit=25):
|
||||
stories = cache.get('feed_stories:%s-%s-%s' % (self.id, offset, limit))
|
||||
stories = cache.get('feed_stories:%s-%s-%s' % (self.id, offset, limit), [])
|
||||
|
||||
if stories is None:
|
||||
stories = Story.objects.filter(story_feed=self).values()[offset:offset+limit]
|
||||
for story in stories:
|
||||
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['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_feed_title'] = self.feed_title
|
||||
story['story_feed_link'] = mark_safe(self.feed_link)
|
||||
story['story_permalink'] = mark_safe(story['story_permalink'])
|
||||
story['story_authors'] = story_db.story_author.author_name
|
||||
stories.append(story)
|
||||
cache.set('feed_stories:%s-%s-%s' % (self.id, offset, limit), stories)
|
||||
|
||||
return stories
|
||||
|
@ -238,6 +239,9 @@ class StoryAuthor(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return '%s - %s' % (self.feed, self.author_name)
|
||||
|
||||
def natural_keys(self):
|
||||
return (self.author_name,)
|
||||
|
||||
class Story(models.Model):
|
||||
'''A feed item'''
|
||||
|
|
|
@ -664,4 +664,77 @@ form.opml_import_form input {
|
|||
height: 376px;
|
||||
width: 600px;
|
||||
background: transparent url('../img/reader/newsblur_splash_image.png') no-repeat 0 0;
|
||||
}
|
||||
|
||||
/* ============== */
|
||||
/* = Classifier = */
|
||||
/* ============== */
|
||||
|
||||
.NB-classifier {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.NB-classifier h2 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.NB-classifier h2.NB-like {
|
||||
color: #007000;
|
||||
}
|
||||
|
||||
.NB-classifier h2.NB-dislike {
|
||||
color: #700000;
|
||||
}
|
||||
|
||||
.NB-classifier h5 {
|
||||
border-bottom: 1px solid #A0A0A0;
|
||||
padding: 16px 0 0px;
|
||||
margin: 0 0 6px -24px;
|
||||
color: #808080;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
.NB-classifier .NB-classifier-field {
|
||||
margin: 0 0 0 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.NB-classifier .NB-classifier-field input[type=text] {
|
||||
width: 446px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.NB-classifier .NB-classifier-field input[type=checkbox] {
|
||||
margin: 5px 0 0 -20px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.NB-classifier .NB-classifier-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.NB-classifier .NB-classifier-facet-disabled {
|
||||
color: #A0A0A0;
|
||||
}
|
||||
|
||||
.NB-disabled {
|
||||
color: #A0A0A0;
|
||||
}
|
||||
|
||||
.NB-classifier .NB-classifier-submit {
|
||||
margin: 16px 0 0 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.NB-classifier .NB-classifier-submit input[type=submit] {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.NB-classifier img.feed_favicon {
|
||||
margin: 0 6px 0 0;
|
||||
vertical-align: middle;
|
||||
}
|
83
media/js/jquery.fieldselection.js
Normal file
83
media/js/jquery.fieldselection.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* jQuery plugin: fieldSelection - v0.1.0 - last change: 2006-12-16
|
||||
* (c) 2006 Alex Brem <alex@0xab.cd> - http://blog.0xab.cd
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var fieldSelection = {
|
||||
|
||||
getSelection: function() {
|
||||
|
||||
var e = this.jquery ? this[0] : this;
|
||||
|
||||
return (
|
||||
|
||||
/* mozilla / dom 3.0 */
|
||||
('selectionStart' in e && function() {
|
||||
var l = e.selectionEnd - e.selectionStart;
|
||||
return { start: e.selectionStart, end: e.selectionEnd, length: l, text: e.value.substr(e.selectionStart, l) };
|
||||
}) ||
|
||||
|
||||
/* exploder */
|
||||
(document.selection && function() {
|
||||
|
||||
e.focus();
|
||||
|
||||
var r = document.selection.createRange();
|
||||
if (r == null) {
|
||||
return { start: 0, end: e.value.length, length: 0 }
|
||||
}
|
||||
|
||||
var re = e.createTextRange();
|
||||
var rc = re.duplicate();
|
||||
re.moveToBookmark(r.getBookmark());
|
||||
rc.setEndPoint('EndToStart', re);
|
||||
|
||||
return { start: rc.text.length, end: rc.text.length + r.text.length, length: r.text.length, text: r.text };
|
||||
}) ||
|
||||
|
||||
/* browser not supported */
|
||||
function() {
|
||||
return { start: 0, end: e.value.length, length: 0 };
|
||||
}
|
||||
|
||||
)();
|
||||
|
||||
},
|
||||
|
||||
replaceSelection: function() {
|
||||
|
||||
var e = this.jquery ? this[0] : this;
|
||||
var text = arguments[0] || '';
|
||||
|
||||
return (
|
||||
|
||||
/* mozilla / dom 3.0 */
|
||||
('selectionStart' in e && function() {
|
||||
e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd, e.value.length);
|
||||
return this;
|
||||
}) ||
|
||||
|
||||
/* exploder */
|
||||
(document.selection && function() {
|
||||
e.focus();
|
||||
document.selection.createRange().text = text;
|
||||
return this;
|
||||
}) ||
|
||||
|
||||
/* browser not supported */
|
||||
function() {
|
||||
e.value += text;
|
||||
return this;
|
||||
}
|
||||
|
||||
)();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
jQuery.each(fieldSelection, function(i) { jQuery.fn[i] = this; });
|
||||
|
||||
})();
|
|
@ -42,7 +42,7 @@
|
|||
var $story_title = $.make('div', { className: 'story' + read }, [
|
||||
$.make('a', { href: story.story_permalink, className: 'story_title' }, [
|
||||
story.story_title,
|
||||
$.make('span', { className: 'NB-storytitles-author'}, story.story_author)
|
||||
$.make('span', { className: 'NB-storytitles-author'}, story.story_authors)
|
||||
]),
|
||||
$.make('span', { className: 'story_date' }, story.short_parsed_date),
|
||||
$.make('span', { className: 'story_id' }, ''+story.id),
|
||||
|
@ -388,8 +388,8 @@
|
|||
|
||||
var $story = $.make('li', { className: 'NB-feed-story' }, [
|
||||
$.make('div', { className: 'NB-feed-story-header' }, [
|
||||
( story.story_author &&
|
||||
$.make('div', { className: 'NB-feed-story-author' }, story.story_author)),
|
||||
( story.story_authors &&
|
||||
$.make('div', { className: 'NB-feed-story-author' }, story.story_authors)),
|
||||
$.make('a', { className: 'NB-feed-story-title', href: unescape(story.story_permalink) }, story.story_title),
|
||||
( story.long_parsed_date &&
|
||||
$.make('span', { className: 'NB-feed-story-date' }, story.long_parsed_date))
|
||||
|
@ -695,7 +695,7 @@
|
|||
if (!$stories.length) {
|
||||
// Try slicing words off the title, from the beginning.
|
||||
title_words = title.match(/[^ ]+/g);
|
||||
NEWSBLUR.log(['Words', title_words.length, title_words, title_words.slice(1).join(' '), title_words.slice(0, -1).join(' '), title_words.slice(1, -1).join(' ')])
|
||||
NEWSBLUR.log(['Words', title_words.length, title_words, title_words.slice(1).join(' '), title_words.slice(0, -1).join(' '), title_words.slice(1, -1).join(' ')]);
|
||||
if (title_words.length > 2) {
|
||||
for (var i=0; i < 3; i++) {
|
||||
if (i==0) shortened_title = title_words.slice(1).join(' ');
|
||||
|
@ -814,16 +814,9 @@
|
|||
},
|
||||
|
||||
mark_story_as_like: function(story_id, $button) {
|
||||
var self = this;
|
||||
|
||||
var callback = function() {
|
||||
return;
|
||||
};
|
||||
|
||||
$button.addClass('liked');
|
||||
if (NEWSBLUR.Globals.is_authenticated) {
|
||||
this.model.mark_story_as_like(story_id, callback);
|
||||
}
|
||||
var feed_id = this.active_feed;
|
||||
|
||||
var classifier = new NEWSBLUR.ReaderClassifier(story_id, feed_id);
|
||||
},
|
||||
|
||||
mark_story_as_dislike: function(story_id, $button) {
|
||||
|
|
137
media/js/newsblur/reader_classifier.js
Normal file
137
media/js/newsblur/reader_classifier.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
NEWSBLUR.ReaderClassifier = function(story_id, feed_id, options) {
|
||||
var defaults = {};
|
||||
|
||||
this.story_id = story_id;
|
||||
this.feed_id = feed_id;
|
||||
this.options = $.extend({}, defaults, options);
|
||||
this.model = NEWSBLUR.AssetModel.reader();
|
||||
this.google_favicon_url = 'http://www.google.com/s2/favicons?domain_url=';
|
||||
this.runner();
|
||||
};
|
||||
|
||||
NEWSBLUR.ReaderClassifier.prototype = {
|
||||
|
||||
runner: function() {
|
||||
this.find_story_and_feed();
|
||||
this.make_modal();
|
||||
this.handle_text_highlight();
|
||||
this.handle_select_checkboxes();
|
||||
this.handle_cancel();
|
||||
this.open_modal();
|
||||
},
|
||||
|
||||
find_story_and_feed: function() {
|
||||
this.story = this.model.get_story(this.story_id);
|
||||
this.feed = this.model.get_feed(this.feed_id);
|
||||
},
|
||||
|
||||
make_modal: function() {
|
||||
var self = this;
|
||||
var story = this.story;
|
||||
var feed = this.feed;
|
||||
|
||||
this.$classifier = $.make('div', { className: 'NB-classifier' }, [
|
||||
$.make('h2', 'What do you like about this story?'),
|
||||
$.make('form', { method: 'post' }, [
|
||||
(story.story_title && $.make('div', { className: 'NB-classifier-field' }, [
|
||||
$.make('h5', 'Story Title'),
|
||||
$.make('input', { type: 'checkbox', name: 'facet', value: 'title', id: 'classifier_title' }),
|
||||
$.make('input', { type: 'text', value: story.story_title, className: 'NB-classifier-title-highlight' }),
|
||||
$.make('label', { 'for': 'classifier_title' }, [
|
||||
$.make('div', [
|
||||
'Look for: ',
|
||||
$.make('span', { className: 'NB-classifier-title NB-classifier-facet-disabled' }, 'Highlight phrases to look for in future stories')
|
||||
])
|
||||
])
|
||||
])),
|
||||
(story.story_authors && $.make('div', { className: 'NB-classifier-field' }, [
|
||||
$.make('h5', 'Story Author'),
|
||||
$.make('input', { type: 'checkbox', name: 'facet', value: 'author', id: 'classifier_author' }),
|
||||
$.make('label', { 'for': 'classifier_author' }, [
|
||||
$.make('b', story.story_authors)
|
||||
])
|
||||
])),
|
||||
$.make('div', { className: 'NB-classifier-field' }, [
|
||||
$.make('h5', 'The Publisher'),
|
||||
$.make('input', { type: 'checkbox', name: 'facet', value: 'publisher', id: 'classifier_publisher' }),
|
||||
$.make('label', { 'for': 'classifier_publisher' }, [
|
||||
$.make('img', { className: 'feed_favicon', src: this.google_favicon_url + feed.feed_link }),
|
||||
$.make('span', { className: 'feed_title' }, feed.feed_title)
|
||||
])
|
||||
]),
|
||||
$.make('div', { className: 'NB-classifier-submit' }, [
|
||||
$.make('input', { type: 'submit', disabled: 'true', className: 'NB-disabled', value: 'Check what you like above...' }),
|
||||
' or ',
|
||||
$.make('a', { href: '#', className: 'NB-classifier-cancel' }, 'cancel')
|
||||
])
|
||||
]).bind('submit', function(e) {
|
||||
e.preventDefault();
|
||||
self.save();
|
||||
return false;
|
||||
})
|
||||
]);
|
||||
},
|
||||
|
||||
open_modal: function() {
|
||||
this.$classifier.modal({
|
||||
'minWidth': 550,
|
||||
'overlayClose': true,
|
||||
'onOpen': function (dialog) {
|
||||
dialog.overlay.fadeIn(200, function () {
|
||||
dialog.container.fadeIn(400);
|
||||
dialog.data.fadeIn(400);
|
||||
});
|
||||
},
|
||||
'onShow': function() {
|
||||
$('#simplemodal-container').corners('4px')
|
||||
.css({'height': 'auto', 'width': '550px'});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handle_text_highlight: function() {
|
||||
var $title_highlight = $('.NB-classifier-title-highlight', this.$classifier);
|
||||
var $title = $('.NB-classifier-title', this.$classifier);
|
||||
var $title_checkbox = $('#classifier_title', this.$classifier);
|
||||
|
||||
var update = function() {
|
||||
var text = $.trim($(this).getSelection().text);
|
||||
|
||||
if ($title.text() != text && text.length) {
|
||||
$title_checkbox.attr('checked', 'checked').change();
|
||||
$title.text(text).removeClass('NB-classifier-facet-disabled');
|
||||
}
|
||||
};
|
||||
|
||||
$title_highlight.keydown(update).keyup(update).mousedown(update).mouseup(update).mousemove(update);
|
||||
},
|
||||
|
||||
handle_select_checkboxes: function() {
|
||||
var self = this;
|
||||
var $submit = $('input[type=submit]', this.$classifier);
|
||||
|
||||
$('input', this.$classifier).change(function() {
|
||||
var count = $('input:checked', self.$classifier).length;
|
||||
|
||||
if (count) {
|
||||
$submit.removeClass("NB-disabled").removeAttr('disabled').attr('value', 'Save');
|
||||
} else {
|
||||
$submit.addClass("NB-disabled").attr('disabled', 'true').attr('value', 'Check what you like above...');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handle_cancel: function() {
|
||||
var $cancel = $('.NB-classifier-cancel', this.$classifier);
|
||||
|
||||
$cancel.click(function(e) {
|
||||
e.preventDefault();
|
||||
$.modal.close();
|
||||
});
|
||||
},
|
||||
|
||||
save: function() {
|
||||
|
||||
}
|
||||
|
||||
};
|
|
@ -175,9 +175,11 @@ COMPRESS_JS = {
|
|||
'js/jquery.color.js',
|
||||
'js/jquery-ui-1.7.2.custom.min.js',
|
||||
'js/jquery.layout.js',
|
||||
'js/jquery.fieldselection.js',
|
||||
|
||||
'js/newsblur/assetmodel.js',
|
||||
'js/newsblur/reader.js'
|
||||
'js/newsblur/reader.js',
|
||||
'js/newsblur/reader_classifier.js'
|
||||
),
|
||||
'output_filename': 'release/all-compressed-?.js'
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue