From 66eae41e2d821e6cd6f8a384683d15f3a2b3af02 Mon Sep 17 00:00:00 2001 From: Samuel Clay Date: Sun, 1 Aug 2010 19:12:42 -0400 Subject: [PATCH] Intelligence Trainer. A whopper of a commit. --- apps/analyzer/views.py | 7 +- apps/reader/migrations/0004_is_trained.py | 149 ++++++++++ apps/reader/models.py | 1 + apps/reader/urls.py | 1 + apps/reader/views.py | 19 ++ .../migrations/0010_stories_per_month.py | 2 +- media/css/reader.css | 132 +++++++-- media/js/jquery.newsblur.js | 8 + media/js/jquery.simplemodal-1.3.js | 44 ++- media/js/newsblur/assetmodel.js | 7 + media/js/newsblur/reader.js | 14 +- media/js/newsblur/reader_add_feed.js | 26 +- media/js/newsblur/reader_classifier.js | 280 ++++++++++++++---- media/js/newsblur/reader_manage_feed.js | 10 - media/js/newsblur/reader_mark_read.js | 14 +- media/js/newsblur/reader_statistics.js | 8 +- utils/munin/newsblur_users.py | 5 +- 17 files changed, 574 insertions(+), 153 deletions(-) create mode 100644 apps/reader/migrations/0004_is_trained.py diff --git a/apps/analyzer/views.py b/apps/analyzer/views.py index 644e634c3..91d947992 100644 --- a/apps/analyzer/views.py +++ b/apps/analyzer/views.py @@ -21,10 +21,12 @@ def save_classifier(request): # Make subscription as dirty, so unread counts can be recalculated usersub = UserSubscription.objects.get(user=request.user, feed=feed) - if not usersub.needs_unread_recalc: + if not usersub.needs_unread_recalc or not usersub.is_trained: usersub.needs_unread_recalc = True + usersub.is_trained = True usersub.save() + def _save_classifier(ClassifierCls, content_type, ContentCls=None, post_content_field=None): classifiers = { 'like_'+content_type: 1, @@ -87,4 +89,5 @@ def get_classifiers_feed(request): response = dict(code=code, payload=payload) - return response \ No newline at end of file + return response + \ No newline at end of file diff --git a/apps/reader/migrations/0004_is_trained.py b/apps/reader/migrations/0004_is_trained.py new file mode 100644 index 000000000..b4c240d27 --- /dev/null +++ b/apps/reader/migrations/0004_is_trained.py @@ -0,0 +1,149 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'UserSubscription.is_trained' + db.add_column('reader_usersubscription', 'is_trained', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'UserSubscription.is_trained' + db.delete_column('reader_usersubscription', 'is_trained') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'reader.feature': { + 'Meta': {'object_name': 'Feature'}, + 'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'reader.userstory': { + 'Meta': {'unique_together': "(('user', 'feed', 'story'),)", 'object_name': 'UserStory'}, + 'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'opinion': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'read_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'story': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Story']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'reader.usersubscription': { + 'Meta': {'unique_together': "(('user', 'feed'),)", 'object_name': 'UserSubscription'}, + 'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}), + 'feed_opens': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_trained': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'last_read_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 7, 17, 13, 2, 23, 54046)'}), + 'mark_read_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 7, 17, 13, 2, 23, 54155)'}), + 'needs_unread_recalc': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'unread_count_negative': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'unread_count_neutral': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'unread_count_positive': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'unread_count_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2000, 1, 1, 0, 0)'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'reader.usersubscriptionfolders': { + 'Meta': {'object_name': 'UserSubscriptionFolders'}, + 'folders': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'rss_feeds.feed': { + 'Meta': {'object_name': 'Feed', 'db_table': "'feeds'"}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'average_stories_per_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'days_to_trim': ('django.db.models.fields.IntegerField', [], {'default': '90'}), + 'etag': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'feed_address': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '255'}), + 'feed_link': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '1000', 'null': 'True', 'blank': 'True'}), + 'feed_tagline': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'feed_title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_load_time': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'default': '0', 'auto_now': 'True', 'blank': 'True'}), + 'min_to_decay': ('django.db.models.fields.IntegerField', [], {'default': '15'}), + 'next_scheduled_update': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'num_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'popular_authors': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'popular_tags': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'stories_last_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'stories_last_year': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + 'rss_feeds.story': { + 'Meta': {'unique_together': "(('story_feed', 'story_guid_hash'),)", 'object_name': 'Story', 'db_table': "'stories'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'story_author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.StoryAuthor']"}), + 'story_author_name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), + 'story_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}), + 'story_content_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'story_date': ('django.db.models.fields.DateTimeField', [], {}), + 'story_feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'stories'", 'to': "orm['rss_feeds.Feed']"}), + 'story_guid': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), + 'story_guid_hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'story_original_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}), + 'story_past_trim_date': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'story_permalink': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), + 'story_tags': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}), + 'story_title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['rss_feeds.Tag']", 'symmetrical': 'False'}) + }, + 'rss_feeds.storyauthor': { + 'Meta': {'object_name': 'StoryAuthor'}, + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'rss_feeds.tag': { + 'Meta': {'object_name': 'Tag'}, + 'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['reader'] diff --git a/apps/reader/models.py b/apps/reader/models.py index 8144ea7f8..4c20c23ca 100644 --- a/apps/reader/models.py +++ b/apps/reader/models.py @@ -29,6 +29,7 @@ class UserSubscription(models.Model): unread_count_updated = models.DateTimeField(default=datetime.datetime(2000,1,1)) needs_unread_recalc = models.BooleanField(default=False) feed_opens = models.IntegerField(default=0) + is_trained = models.BooleanField(default=False) def __unicode__(self): return '[' + self.feed.feed_title + '] ' diff --git a/apps/reader/urls.py b/apps/reader/urls.py index 780e20127..908784601 100644 --- a/apps/reader/urls.py +++ b/apps/reader/urls.py @@ -23,4 +23,5 @@ urlpatterns = patterns('', url(r'^add_feature', views.add_feature, name='add-feature'), url(r'^load_features', views.load_features, name='load-features'), url(r'^save_feed_order', views.save_feed_order, name='save-feed-order'), + url(r'^get_feeds_trainer', views.get_feeds_trainer, name='get-feeds-trainer'), ) diff --git a/apps/reader/views.py b/apps/reader/views.py index e86319dcd..1476d7455 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -534,6 +534,25 @@ def save_feed_order(request): return {} +@json.json_view +def get_feeds_trainer(request): + classifiers = [] + + usersubs = UserSubscription.objects.filter(user=request.user).select_related('feed')\ + .order_by('-feed__stories_last_month') + + for us in usersubs: + if not us.is_trained and us.feed.stories_last_month > 0: + classifier = dict() + classifier['classifiers'] = get_classifiers_for_user(request.user, us.feed) + classifier['feed_id'] = us.feed.pk + classifier['stories_last_month'] = us.feed.stories_last_month + classifier['feed_tags'] = json.decode(us.feed.popular_tags) if us.feed.popular_tags else [] + classifier['feed_authors'] = json.decode(us.feed.popular_authors) if us.feed.popular_authors else [] + classifiers.append(classifier) + + return classifiers + @login_required def login_as(request): if not request.user.is_staff: diff --git a/apps/rss_feeds/migrations/0010_stories_per_month.py b/apps/rss_feeds/migrations/0010_stories_per_month.py index 36f7924e5..ad5458f26 100644 --- a/apps/rss_feeds/migrations/0010_stories_per_month.py +++ b/apps/rss_feeds/migrations/0010_stories_per_month.py @@ -35,7 +35,7 @@ class Migration(SchemaMigration): db.alter_column('stories', 'story_tags', self.gf('django.db.models.fields.CharField')(max_length=2000, null=True, blank=True)) # Adding unique constraint on 'Story', fields ['story_feed', 'story_guid_hash'] - db.create_unique('stories', ['story_feed_id', 'story_guid_hash']) + # db.create_unique('stories', ['story_feed_id', 'story_guid_hash']) def backwards(self, orm): diff --git a/media/css/reader.css b/media/css/reader.css index a493190ea..8eb381a3a 100644 --- a/media/css/reader.css +++ b/media/css/reader.css @@ -1474,17 +1474,72 @@ a.NB-splash-link:hover { text-shadow: 1px 1px 0px #e0e0e0; } -.NB-disabled { - color: #A0A0A0; +.NB-modal .NB-modal-subtitle { + margin:10px 0 0; + padding:8px 12px; + font-size: 14px; + position: relative; + background-color: #F6F6F6; +} +.NB-modal .NB-modal-feed-title { + display: block; + margin: 0 40px 0 23px; + text-decoration: none; + color: #272727; + overflow: hidden; + text-shadow: 0 1px 0 #EBF3FA; +} + +.NB-modal .NB-modal-feed-image { + float: left; +} + +.NB-modal .NB-modal-submit input[type=submit], +.NB-modal .NB-modal-submit .NB-modal-submit-button { + border: 1px solid #606060; + font-size: 12px; + padding: 4px 8px; + text-transform: uppercase; + margin: 2px 4px 2px; + border: 1px solid #606060; + -moz-box-shadow:2px 2px 0 #D0D0D0; + -webkit-box-shadow:2px 2px 0 #D0D0D0; + box-shadow:2px 2px 0 #D0D0D0; + border-radius: 4px; + -moz-border-radius: 4px; + cursor: pointer; + text-decoration: none; +} + +.NB-modal .NB-modal-submit .NB-modal-submit-back { + background-color: #d5d4dB; + color: #909090; + +} +.NB-modal .NB-modal-submit .NB-modal-submit-close { + background-color: #d5d4dB; + color: #909090; + font-weight: bold; +} +.NB-modal .NB-modal-submit .NB-modal-submit-save { + background-color: #217412; + font-weight: bold; + color: #FCFCFC; +} + +.NB-modal .NB-modal-submit .NB-disabled { + background-color: #d5d4dB; + color: #909090; + border: 1px solid #A0A0A0; + -moz-box-shadow:2px 2px 0 #E6E6E6; + -webkit-box-shadow:2px 2px 0 #E6E6E6; + box-shadow:2px 2px 0 #E6E6E6; } .NB-modal .NB-modal-submit { margin: 16px 0 0 0; font-size: 13px; -} - -.NB-modal .NB-modal-submit input[type=submit] { - font-size: 14px; + overflow: hidden; } .NB-modal img.feed_favicon { @@ -1518,6 +1573,11 @@ a.NB-splash-link:hover { /* = Classifier = */ /* ============== */ +.NB-classifier { + border-radius: 14px; + -moz-border-radius: 14px; +} + .NB-classifier h2.NB-like { color: #007000; } @@ -1592,6 +1652,27 @@ a.NB-splash-link:hover { .NB-classifiers { } +.NB-classifier .NB-modal-submit .NB-modal-submit-back { + float: left; + color: #FFF; + background-color: #b5b4bB; +} +.NB-classifier .NB-modal-submit .NB-modal-submit-close { + float: right; + color: #FFF; + background-color: #b5b4bB; +} +.NB-classifier .NB-modal-submit .NB-modal-submit-save { + float: right; + padding-left: 12px !important; + padding-right: 12px !important; +} +.NB-classifier-trainer-counts { + float: right; + color: #606060; + font-size: 17px; +} + /* ======================= */ /* = Intelligence Slider = */ /* ======================= */ @@ -1644,10 +1725,12 @@ a.NB-splash-link:hover { .NB-add .NB-add-danger { display: block; + clear: both; font-size: 12px; color: #535558; font-weight: bold; margin: 8px 0 0; + float: left; } .NB-add .NB-add-danger img { @@ -1714,6 +1797,12 @@ a.NB-splash-link:hover { margin: 0 4px; } +.NB-add .NB-opml-reader-oauth { + margin: 4px 0 6px 0; + text-decoration: none; + float: left; + display: block; +} /* ================ */ /* = Manage Feeds = */ /* ================ */ @@ -1847,20 +1936,23 @@ background: transparent; display: block; margin: 2px 6px 6px 0; cursor: pointer; - padding: 2px 9px 2px 4px; + padding: 0 9px 0 4px; font-size: 12px; text-transform: uppercase; } .NB-classifiers .NB-classifier input[type=checkbox] { - margin: 0 6px 0 4px; - float: none; + margin: 3px 6px 2px 4px; cursor: pointer; + float: left; } .NB-classifiers .NB-classifier label { cursor: pointer; color: black; + float: left; + display: block; + padding: 2px 0; } .NB-classifiers .NB-classifier label b { @@ -2243,7 +2335,7 @@ background: transparent; .NB-menu-manage .NB-menu-manage-subtitle { font-size: 12px; - color: #617C4B; + color: #718C7B; } .NB-menu-manage li:hover .NB-menu-manage-subtitle { @@ -2342,26 +2434,6 @@ background: transparent; .NB-modal-statistics { } -.NB-modal-statistics .NB-modal-subtitle { - border-bottom: 3px solid #E0E0E0; - font-size: 14px; - margin-bottom: 6px; - padding-bottom: 6px; - position: relative; -} -.NB-modal-statistics .NB-modal-statistics-feed-title { - display: block; - margin: 4px 40px 2px 23px; - text-decoration: none; - color: #272727; - overflow: hidden; - text-shadow: 0 1px 0 #EBF3FA; -} - -.NB-modal-statistics .NB-modal-statistics-feed-image { - float: left; -} - .NB-modal-statistics .NB-statistics-stat { background-color: #E0FFE0; clear: both; diff --git a/media/js/jquery.newsblur.js b/media/js/jquery.newsblur.js index e2974aaa1..54c48fccd 100644 --- a/media/js/jquery.newsblur.js +++ b/media/js/jquery.newsblur.js @@ -129,6 +129,14 @@ NEWSBLUR.log = function(msg) { return true; } }, + + entity: function(str) { + var e = document.createElement('div'); + + e.innerHTML = String(str); + + return e.innerHTML; + }, make: function(){ var $elem, text, children, type, name, props; diff --git a/media/js/jquery.simplemodal-1.3.js b/media/js/jquery.simplemodal-1.3.js index c977e4511..9206e31ec 100644 --- a/media/js/jquery.simplemodal-1.3.js +++ b/media/js/jquery.simplemodal-1.3.js @@ -141,7 +141,7 @@ minWidth: null, maxHeight: null, maxWidth: null, - autoResize: false, + autoResize: true, autoPosition: true, zIndex: 1000, close: true, @@ -446,6 +446,13 @@ return [h, el.width()]; }, + resize: function(data) { + if (data) { + this.d.data = data; + } + w = this.getDimensions(); + this.setContainerDimensions(); + }, getVal: function (v) { return v == 'auto' ? 0 : v.indexOf('%') > 0 ? v @@ -459,34 +466,21 @@ var ch = $.browser.opera ? s.d.container.height() : s.getVal(s.d.container.css('height')), cw = $.browser.opera ? s.d.container.width() : s.getVal(s.d.container.css('width')), dh = s.d.data.outerHeight(true), dw = s.d.data.outerWidth(true); - - var mh = s.o.maxHeight && s.o.maxHeight < w[0] ? s.o.maxHeight : w[0], + w = s.getDimensions(); + var mh = (s.o.maxHeight && s.o.maxHeight < w[0] ? s.o.maxHeight : w[0]) - 80, mw = s.o.maxWidth && s.o.maxWidth < w[1] ? s.o.maxWidth : w[1]; + + // NEWSBLUR.log(['heights', ch, dh, mh, w]); + // height - if (!ch) { - if (!dh) {ch = s.o.minHeight;} - else { - if (dh > mh) {ch = mh;} - else if (dh < s.o.minHeight) {ch = s.o.minHeight;} - else {ch = dh;} - } - } - else { - ch = ch > mh ? mh : ch; - } + if (dh > mh) {ch = mh;} + else if (dh < s.o.minHeight) {ch = s.o.minHeight;} + else {ch = dh;} // width - if (!cw) { - if (!dw) {cw = s.o.minWidth;} - else { - if (dw > mw) {cw = mw;} - else if (dw < s.o.minWidth) {cw = s.o.minWidth;} - else {cw = dw;} - } - } - else { - cw = cw > mw ? mw : cw; - } + if (dw > mw) {cw = mw;} + else if (dw < s.o.minWidth) {cw = s.o.minWidth;} + else {cw = dw;} s.d.container.css({height: ch, width: cw}); if (dh > ch || dw > cw) { diff --git a/media/js/newsblur/assetmodel.js b/media/js/newsblur/assetmodel.js index 6d7e8f0df..6606089d2 100644 --- a/media/js/newsblur/assetmodel.js +++ b/media/js/newsblur/assetmodel.js @@ -244,6 +244,13 @@ NEWSBLUR.AssetModel.Reader.prototype = { $.isFunction(callback) && callback(data, first_load); }, + get_feeds_trainer: function(callback) { + var self = this; + + this.make_request('/reader/get_feeds_trainer', {}, callback, + null, {'ajax_group': 'feed'}); + }, + refresh_feeds: function(callback) { var self = this; diff --git a/media/js/newsblur/reader.js b/media/js/newsblur/reader.js index 6d93d4a18..3a7a8edae 100644 --- a/media/js/newsblur/reader.js +++ b/media/js/newsblur/reader.js @@ -1149,13 +1149,13 @@ mark_story_as_like: function(story_id, $button) { var feed_id = this.active_feed; - NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifierStory(story_id, feed_id, 1); + NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifierStory(story_id, feed_id, {'score': 1}); }, mark_story_as_dislike: function(story_id, $button) { var feed_id = this.active_feed; - NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifierStory(story_id, feed_id, -1); + NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifierStory(story_id, feed_id, {'score': -1}); }, // ===================== @@ -1518,7 +1518,8 @@ open_feed_intelligence_modal: function(score) { var feed_id = this.active_feed; - NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifierFeed(feed_id, score); + // NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifierFeed(feed_id, {'score': score}); + NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifierTrainer({'score': score}); }, // ========================== @@ -1855,6 +1856,11 @@ $.make('div', { className: 'NB-menu-manage-title' }, 'Mark all feeds as read'), $.make('div', { className: 'NB-menu-manage-subtitle' }, 'Choose how many days back.') ]), + $.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-feed-train' }, [ + $.make('div', { className: 'NB-menu-manage-image' }), + $.make('div', { className: 'NB-menu-manage-title' }, 'Train intelligence'), + $.make('div', { className: 'NB-menu-manage-subtitle' }, 'Accurate filters are happy filters.') + ]), $.make('li', { className: 'NB-menu-manage-preferences' }, [ $.make('div', { className: 'NB-menu-manage-image' }), $.make('div', { className: 'NB-menu-manage-title' }, 'Preferences'), @@ -1883,7 +1889,7 @@ $.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-feed-train' }, [ $.make('div', { className: 'NB-menu-manage-image' }), $.make('div', { className: 'NB-menu-manage-title' }, 'Train intelligence'), - $.make('div', { className: 'NB-menu-manage-subtitle' }, 'Accurate filters are happy filters.') + $.make('div', { className: 'NB-menu-manage-subtitle' }, 'Choose classifiers for this site.') ]), $.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-feed-stats' }, [ $.make('div', { className: 'NB-menu-manage-image' }), diff --git a/media/js/newsblur/reader_add_feed.js b/media/js/newsblur/reader_add_feed.js index c45afcaf9..13d8eb81c 100644 --- a/media/js/newsblur/reader_add_feed.js +++ b/media/js/newsblur/reader_add_feed.js @@ -24,7 +24,7 @@ NEWSBLUR.ReaderAddFeed.prototype = { this.$add = $.make('div', { className: 'NB-add NB-modal' }, [ $.make('h2', { className: 'NB-modal-title' }, 'Add feeds and folders'), $.make('div', { className: 'NB-add-form' }, [ - $.make('div', { className: 'NB-fieldset NB-add-add-url' }, [ + $.make('div', { className: 'NB-fieldset NB-add-add-url NB-modal-submit' }, [ $.make('h5', [ $.make('div', { className: 'NB-add-folders' }, this.make_folders()), 'Add a new feed' @@ -34,12 +34,12 @@ NEWSBLUR.ReaderAddFeed.prototype = { $.make('div', { className: 'NB-loading' }), $.make('label', { 'for': 'NB-add-url' }, 'RSS or URL: '), $.make('input', { type: 'text', id: 'NB-add-url', className: 'NB-add-url', name: 'url' }), - $.make('input', { type: 'submit', value: 'Add it', className: 'NB-add-url-submit' }), + $.make('input', { type: 'submit', value: 'Add it', className: 'NB-modal-submit-save NB-add-url-submit' }), $.make('div', { className: 'NB-error' }) ]) ]) ]), - $.make('div', { className: 'NB-fieldset NB-add-add-folder' }, [ + $.make('div', { className: 'NB-fieldset NB-add-add-folder NB-modal-submit' }, [ $.make('h5', [ $.make('div', { className: 'NB-add-folders' }, this.make_folders()), 'Add a new folder' @@ -51,18 +51,18 @@ NEWSBLUR.ReaderAddFeed.prototype = { $.make('div', { className: 'NB-folder-icon' }) ]), $.make('input', { type: 'text', id: 'NB-add-folder', className: 'NB-add-folder', name: 'url' }), - $.make('input', { type: 'submit', value: 'Add folder', className: 'NB-add-folder-submit' }), + $.make('input', { type: 'submit', value: 'Add folder', className: 'NB-add-folder-submit NB-modal-submit-save' }), $.make('div', { className: 'NB-error' }) ]) ]) ]), - $.make('div', { className: 'NB-fieldset NB-anonymous-ok' }, [ + $.make('div', { className: 'NB-fieldset NB-anonymous-ok NB-modal-submit' }, [ $.make('h5', [ - 'Import from Google Reader' + 'Import feeds' ]), $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('a', { href: NEWSBLUR.URLs['opml-reader-authorize'], className: 'NB-opml-reader-oauth NB-splash-link' }, [ - 'Import everything from Google Reader (OAuth)', + $.make('a', { href: NEWSBLUR.URLs['opml-reader-authorize'], className: 'NB-opml-reader-oauth NB-modal-submit-save NB-modal-submit-button' }, [ + 'Import from Google Reader', $.make('img', { className: 'NB-add-google-reader-arrow', src: NEWSBLUR.Globals['MEDIA_URL']+'img/icons/silk/arrow_right.png' }) ]), $.make('div', { className: 'NB-add-danger' }, [ @@ -71,13 +71,13 @@ NEWSBLUR.ReaderAddFeed.prototype = { ]) ]) ]), - $.make('div', { className: 'NB-fieldset NB-add-opml' }, [ + $.make('div', { className: 'NB-fieldset NB-add-opml NB-modal-submit' }, [ $.make('h5', 'Upload OPML'), $.make('div', { className: 'NB-fieldset-fields' }, [ $.make('form', { method: 'post', enctype: 'multipart/form-data', className: 'NB-add-form' }, [ $.make('div', { className: 'NB-loading' }), $.make('input', { type: 'file', name: 'file', id: 'opml_file_input' }), - $.make('input', { type: 'submit', className: 'NB-add-opml-button', value: 'Upload OPML File' }).click(function(e) { + $.make('input', { type: 'submit', className: 'NB-add-opml-button NB-modal-submit-save', value: 'Upload OPML File' }).click(function(e) { e.preventDefault(); self.handle_opml_upload(); return false; @@ -142,17 +142,17 @@ NEWSBLUR.ReaderAddFeed.prototype = { this.$add.modal({ 'minWidth': 600, - 'maxHeight': height, 'overlayClose': true, 'onOpen': function (dialog) { dialog.overlay.fadeIn(200, function () { dialog.container.fadeIn(200); - dialog.data.fadeIn(200); + dialog.data.fadeIn(200, function() { + $.modal.impl.setContainerDimensions(); + }); }); }, 'onShow': function(dialog) { $('#simplemodal-container').corner('6px').css({'width': 600, 'height': height}); - $.modal.impl.setPosition(); }, 'onClose': function(dialog) { dialog.data.hide().empty().remove(); diff --git a/media/js/newsblur/reader_classifier.js b/media/js/newsblur/reader_classifier.js index 4fcc47cd8..bc0cb9d78 100644 --- a/media/js/newsblur/reader_classifier.js +++ b/media/js/newsblur/reader_classifier.js @@ -1,21 +1,46 @@ -NEWSBLUR.ReaderClassifierFeed = function(feed_id, score, options) { - var defaults = {}; +NEWSBLUR.ReaderClassifierTrainer = function(options) { + var defaults = { + 'score': 1, + 'training': true + }; + + this.flags = { + 'publisher': true, + 'story': false + }; + this.cache = {}; + this.trainer_iterator = 0; + this.feed_id = null; + this.options = $.extend({}, defaults, options); + this.score = this.options['score']; + this.model = NEWSBLUR.AssetModel.reader(); + this.google_favicon_url = 'http://www.google.com/s2/favicons?domain_url='; + this.runner_trainer(); +}; + +NEWSBLUR.ReaderClassifierFeed = function(feed_id, options) { + var defaults = { + 'score': 1, + 'training': false + }; this.flags = { 'publisher': true, 'story': false }; this.feed_id = feed_id; - this.score = score; this.options = $.extend({}, defaults, options); + this.score = this.options['score']; this.model = NEWSBLUR.AssetModel.reader(); this.google_favicon_url = 'http://www.google.com/s2/favicons?domain_url='; this.runner_feed(); }; -NEWSBLUR.ReaderClassifierStory = function(story_id, feed_id, score, options) { - var defaults = {}; +NEWSBLUR.ReaderClassifierStory = function(story_id, feed_id, options) { + var defaults = { + 'score': 1 + }; this.flags = { 'publisher': false, @@ -23,8 +48,8 @@ NEWSBLUR.ReaderClassifierStory = function(story_id, feed_id, score, options) { }; this.story_id = story_id; this.feed_id = feed_id; - this.score = score; this.options = $.extend({}, defaults, options); + this.score = this.options['score']; this.model = NEWSBLUR.AssetModel.reader(); this.google_favicon_url = 'http://www.google.com/s2/favicons?domain_url='; this.runner_story(); @@ -32,19 +57,37 @@ NEWSBLUR.ReaderClassifierStory = function(story_id, feed_id, score, options) { var classifier = { - runner_feed: function() { - this.find_story_and_feed(); - this.make_modal_feed(); - this.make_modal_title(); - this.make_modal_intelligence_slider(); - this.handle_text_highlight(); + runner_trainer: function() { + this.user_classifiers = {}; + + this.make_trainer_intro(); + this.get_feeds_trainer(); this.handle_select_checkboxes(); this.handle_cancel(); this.handle_select_title(); this.open_modal(); + + this.$modal.parent().bind('click.reader_classifer', $.rescope(this.handle_clicks, this)); + }, + + runner_feed: function() { + this.user_classifiers = this.model.classifiers; + + this.find_story_and_feed(); + this.make_modal_feed(); + this.make_modal_title(); + this.make_modal_intelligence_slider(); + this.handle_select_checkboxes(); + this.handle_cancel(); + this.handle_select_title(); + this.open_modal(); + + this.$modal.bind('submit.reader_classifer', $.rescope(this.handle_submit, this)); }, runner_story: function() { + this.user_classifiers = this.model.classifiers; + this.find_story_and_feed(); this.make_modal_story(); this.make_modal_title(); @@ -55,6 +98,49 @@ var classifier = { this.open_modal(); }, + load_next_feed_in_trainer: function(backwards) { + if (backwards) { + this.trainer_iterator = Math.max(1, this.trainer_iterator - 1); + } else { + this.trainer_iterator = Math.min(this.trainer_data.length, this.trainer_iterator + 1); + } + var trainer_data = this.trainer_data[this.trainer_iterator-1]; + this.feed_id = trainer_data['feed_id']; + this.feed = this.model.get_feed(this.feed_id); + this.feed_tags = trainer_data['feed_tags']; + this.feed_authors = trainer_data['feed_authors']; + this.user_classifiers = trainer_data['classifiers']; + + this.make_modal_feed(); + this.make_modal_title(); + this.make_modal_trainer_count(); + + if (backwards || this.feed_id in this.cache) { + this.$modal = this.cache[this.feed_id]; + } + $('.NB-modal').replaceWith(this.$modal); + + + + // var height = this.$modal.outerHeight(true); + $.modal.impl.resize(this.$modal); + }, + + get_feeds_trainer: function() { + this.model.get_feeds_trainer($.rescope(this.load_feeds_trainer, this)); + }, + + load_feeds_trainer: function(e, data) { + var $begin = $('.NB-modal-submit-begin', this.$modal); + + NEWSBLUR.log(['data', data]); + this.trainer_data = data; + + $begin.text('Begin Training') + .addClass('NB-modal-submit-save') + .removeClass('NB-disabled'); + }, + find_story_and_feed: function() { if (this.story_id) { this.story = this.model.get_story(this.story_id); @@ -62,6 +148,22 @@ var classifier = { this.feed = this.model.get_feed(this.feed_id); this.feed_tags = this.model.get_feed_tags(); this.feed_authors = this.model.get_feed_authors(); + + $('.NB-modal-subtitle .NB-modal-feed-image', this.$modal).attr('src', this.google_favicon_url + this.feed['feed_link']); + $('.NB-modal-subtitle .NB-modal-feed-title', this.$modal).html(this.feed['feed_title']); + }, + + make_trainer_intro: function() { + var self = this; + + this.$modal = $.make('div', { className: 'NB-classifier NB-modal NB-trainer'}, [ + $.make('h2', { className: 'NB-modal-title' }, 'Intelligence Trainer'), + $.make('h2', { className: 'NB-modal-title' }, 'Trained feed are happy feeds'), + $.make('div', { className: 'NB-modal-submit' }, [ + $.make('a', { href: '#', className: 'NB-modal-submit-save NB-modal-submit-begin NB-modal-submit-button NB-disabled' }, 'Loading Training...') + ]) + ]); + }, make_modal_feed: function() { @@ -69,11 +171,17 @@ var classifier = { var feed = this.feed; var opinion = (this.score == 1 ? 'like_' : 'dislike_'); - NEWSBLUR.log(['Make feed', feed, this.feed_authors, this.feed_tags]); + // NEWSBLUR.log(['Make feed', feed, this.feed_authors, this.feed_tags]); - this.$classifier = $.make('div', { className: 'NB-classifier NB-modal' }, [ - this.make_modal_intelligence_slider(), + this.$modal = $.make('div', { className: 'NB-classifier NB-modal' }, [ + $.make('div', { className: 'NB-modal-loading' }), + (!this.options['training'] && this.make_modal_intelligence_slider()), + (this.options['training'] && $.make('div', { className: 'NB-classifier-trainer-counts' })), $.make('h2', { className: 'NB-modal-title' }), + $.make('h2', { className: 'NB-modal-subtitle' }, [ + $.make('img', { className: 'NB-modal-feed-image feed_favicon', src: this.google_favicon_url + this.feed.feed_link }), + $.make('span', { className: 'NB-modal-feed-title' }, this.feed.feed_title) + ]), $.make('form', { method: 'post', className: 'NB-publisher' }, [ (this.feed_authors.length && $.make('div', { className: 'NB-modal-field NB-fieldset NB-classifiers' }, [ $.make('h5', 'Authors'), @@ -93,19 +201,22 @@ var classifier = { this.make_publisher(feed, opinion) ) ]), - $.make('div', { className: 'NB-modal-submit' }, [ + (this.options['training'] && $.make('div', { className: 'NB-modal-submit' }, [ $.make('input', { name: 'score', value: this.score, type: 'hidden' }), $.make('input', { name: 'feed_id', value: this.feed_id, type: 'hidden' }), + $.make('a', { href: '#', className: 'NB-modal-submit-button NB-modal-submit-back' }, $.entity('«') + ' Back'), + $.make('a', { href: '#', className: 'NB-modal-submit-button NB-modal-submit-save' }, 'Save & Next '+$.entity('»')), + $.make('a', { href: '#', className: 'NB-modal-submit-button NB-modal-submit-close' }, 'Close') + ])), + (!this.options['training'] && $.make('div', { className: 'NB-modal-submit' }, [ + $.make('input', { name: 'score', value: this.score, type: 'hidden' }), $.make('input', { name: 'story_id', value: this.story_id, type: 'hidden' }), - $.make('input', { type: 'submit', disabled: 'true', className: 'NB-disabled', value: 'Check what you like above...' }), + $.make('input', { name: 'feed_id', value: this.feed_id, type: 'hidden' }), + $.make('input', { type: 'submit', disabled: 'true', className: 'NB-modal-submit-save NB-disabled', value: 'Check what you like above...' }), ' or ', $.make('a', { href: '#', className: 'NB-modal-cancel' }, 'cancel') - ]) - ]).bind('submit', function(e) { - e.preventDefault(); - self.save_publisher(); - return false; - }) + ])) + ]) ]); }, @@ -120,7 +231,7 @@ var classifier = { // HTML entities decoding. story.story_title = $('
').html(story.story_title).text(); - this.$classifier = $.make('div', { className: 'NB-classifier NB-modal' }, [ + this.$modal = $.make('div', { className: 'NB-classifier NB-modal' }, [ this.make_modal_intelligence_slider(), $.make('h2', { className: 'NB-modal-title' }), $.make('form', { method: 'post' }, [ @@ -157,9 +268,9 @@ var classifier = { ]), $.make('div', { className: 'NB-modal-submit' }, [ $.make('input', { name: 'score', value: this.score, type: 'hidden' }), - $.make('input', { name: 'feed_id', value: this.feed_id, type: 'hidden' }), $.make('input', { name: 'story_id', value: this.story_id, type: 'hidden' }), - $.make('input', { type: 'submit', disabled: 'true', className: 'NB-disabled', value: 'Check what you like above...' }), + $.make('input', { name: 'feed_id', value: this.feed_id, type: 'hidden' }), + $.make('input', { type: 'submit', disabled: 'true', className: 'NB-modal-submit-save NB-disabled', value: 'Check what you like above...' }), ' or ', $.make('a', { href: '#', className: 'NB-modal-cancel' }, 'cancel') ]) @@ -172,7 +283,7 @@ var classifier = { }, make_modal_title: function() { - var $modal_title = $('.NB-modal-title', this.$classifier); + var $modal_title = $('.NB-modal-title', this.$modal); if (this.flags['publisher']) { if (this.score == 1) { @@ -189,6 +300,13 @@ var classifier = { } }, + make_modal_trainer_count: function() { + var $count = $('.NB-classifier-trainer-counts', this.$modal); + var count = this.trainer_iterator; + var total = this.trainer_data.length; + $count.html(count + '/' + total); + }, + make_modal_intelligence_slider: function() { var self = this; var $slider = $.make('div', { className: 'NB-taskbar-intelligence NB-modal-slider' }, [ @@ -208,14 +326,14 @@ var classifier = { // self.switch_feed_view_unread_view(ui.value); self.score = ui.value - 1; self.make_modal_title(); - $('input[name^=like],input[name^=dislike]', self.$classifier).attr('name', function(i, current_name) { + $('input[name^=like],input[name^=dislike]', self.$modal).attr('name', function(i, current_name) { if (self.score == -1) { return 'dis' + current_name.substr(current_name.indexOf('like_')); } else if (self.score == 1) { return current_name.substr(current_name.indexOf('like_')); } }); - var $submit = $('input[type=submit]', self.$classifier); + var $submit = $('input[type=submit]', self.$modal); $submit.removeClass("NB-disabled").removeAttr('disabled').attr('value', 'Save'); } }); @@ -245,8 +363,8 @@ var classifier = { id: 'classifier_author_'+a }; - if (author in this.model.classifiers.authors - && this.model.classifiers.authors[author] == this.score) { + if (author in this.user_classifiers.authors + && this.user_classifiers.authors[author] == this.score) { input_attrs['checked'] = 'checked'; } @@ -290,7 +408,7 @@ var classifier = { id: 'classifier_tag_'+t }; - if (tag in this.model.classifiers.tags && this.model.classifiers.tags[tag] == this.score) { + if (tag in this.user_classifiers.tags && this.user_classifiers.tags[tag] == this.score) { input_attrs['checked'] = 'checked'; } @@ -321,8 +439,8 @@ var classifier = { id: 'classifier_publisher', checked: false }; - if (this.feed.feed_link in this.model.classifiers.feeds - && this.model.classifiers.feeds[this.feed.feed_link].score == this.score) { + if (this.feed.feed_link in this.user_classifiers.feeds + && this.user_classifiers.feeds[this.feed.feed_link].score == this.score) { input_attrs['checked'] = true; } @@ -354,7 +472,7 @@ var classifier = { var self = this; var $holder = $.make('div', { className: 'NB-modal-holder' }) - .append(this.$classifier) + .append(this.$modal) .appendTo('body') .css({'visibility': 'hidden', 'display': 'block', 'width': 600}); var height = $('.NB-classifier', $holder).outerHeight(true); @@ -364,11 +482,11 @@ var classifier = { height = w[0] - 70; } - this.$classifier.modal({ + this.$modal.modal({ 'minWidth': 600, - 'maxHeight': height, 'overlayClose': true, 'autoResize': true, + 'position': [40, 0], 'onOpen': function (dialog) { dialog.overlay.fadeIn(200, function () { dialog.container.fadeIn(200); @@ -377,7 +495,7 @@ var classifier = { }, 'onShow': function(dialog) { $('#simplemodal-container').corner('6px').css({'width': 600, 'height': height}); - $('.NB-classifier', self.$classifier).corner('14px'); + $('.NB-classifier', self.$modal).corner('14px'); $.modal.impl.setPosition(); }, 'onClose': function(dialog) { @@ -393,9 +511,9 @@ var classifier = { }, handle_text_highlight: function() { - var $title_highlight = $('.NB-classifier-title-highlight', this.$classifier); - var $title = $('.NB-classifier-title-text', this.$classifier); - var $title_checkbox = $('#classifier_title', this.$classifier); + var $title_highlight = $('.NB-classifier-title-highlight', this.$modal); + var $title = $('.NB-classifier-title-text', this.$modal); + var $title_checkbox = $('#classifier_title', this.$modal); var update = function() { var text = $.trim($(this).getSelection().text); @@ -415,9 +533,9 @@ var classifier = { }, handle_select_title: function() { - var $title_checkbox = $('#classifier_title', this.$classifier); - var $title = $('.NB-classifier-title-text', this.$classifier); - var $title_highlight = $('.NB-classifier-title-highlight', this.$classifier); + var $title_checkbox = $('#classifier_title', this.$modal); + var $title = $('.NB-classifier-title-text', this.$modal); + var $title_highlight = $('.NB-classifier-title-highlight', this.$modal); $title_checkbox.change(function() {; if ($title.parents('.NB-classifier-facet-disabled').length) { @@ -432,16 +550,22 @@ var classifier = { handle_select_checkboxes: function() { var self = this; - var $submit = $('input[type=submit]', this.$classifier); + var $save = $('.NB-modal-submit-save', this.$modal); + var $close = $('.NB-modal-submit-close', this.$modal); + var $back = $('.NB-modal-submit-back', this.$modal); - $('input', this.$classifier).change(function() { - // var count = $('input:checked', self.$classifier).length; - $submit.removeClass("NB-disabled").removeAttr('disabled').attr('value', 'Save'); + $('input', this.$modal).change(function() { + // var count = $('input:checked', self.$modal).length; + if (self.options['training']) { + $close.val('Save & Close'); + } else { + $submit.removeClass("NB-disabled").removeAttr('disabled').attr('value', 'Save'); + } }); }, handle_cancel: function() { - var $cancel = $('.NB-modal-cancel', this.$classifier); + var $cancel = $('.NB-modal-cancel', this.$modal); $cancel.click(function(e) { e.preventDefault(); @@ -449,10 +573,44 @@ var classifier = { }); }, + handle_submit: function(elem, e) { + var self = this; + + $.targetIs(e, { tagSelector: '.NB-modal-submit-save' }, function($t, $p){ + e.preventDefault(); + self.save_publisher(); + }); + }, + + handle_clicks: function(elem, e) { + var self = this; + + $.targetIs(e, { tagSelector: '.NB-modal-submit-begin' }, function($t, $p){ + e.preventDefault(); + self.load_next_feed_in_trainer(); + }); + + $.targetIs(e, { tagSelector: '.NB-modal-submit-save:not(.NB-modal-submit-begin)' }, function($t, $p){ + e.preventDefault(); + self.save_publisher(true); + self.load_next_feed_in_trainer(); + }); + + $.targetIs(e, { tagSelector: '.NB-modal-submit-back' }, function($t, $p){ + e.preventDefault(); + self.load_next_feed_in_trainer(true); + }); + + $.targetIs(e, { tagSelector: '.NB-modal-submit-close' }, function($t, $p){ + e.preventDefault(); + self.save_publisher(); + }); + }, + serialize_classifier: function() { - var checked_data = $('input', this.$classifier).serialize(); + var checked_data = $('input', this.$modal).serialize(); - var $unchecked = $('input[type=checkbox]:not(:checked)', this.$classifier); + var $unchecked = $('input[type=checkbox]:not(:checked)', this.$modal); $unchecked.attr('checked', true); $unchecked.each(function() { $(this).attr('name', 'remove_' + $(this).attr('name')); @@ -468,26 +626,31 @@ var classifier = { return data; }, - save_publisher: function() { - var $save = $('.NB-classifier input[type=submit]'); + save_publisher: function(keep_modal_open) { + var $save = $('.NB-modal-submit-save', this.$modal); var story_id = this.story_id; var data = this.serialize_classifier(); - NEWSBLUR.reader.update_opinions(this.$classifier, this.feed_id); + NEWSBLUR.reader.update_opinions(this.$modal, this.feed_id); + if (this.options['training']) { + this.cache[this.feed_id] = this.$modal.clone(); + } $save.text('Saving...').addClass('NB-disabled').attr('disabled', true); this.model.save_classifier_publisher(data, function() { - NEWSBLUR.reader.force_feed_refresh(); - $.modal.close(); + if (!keep_modal_open) { + NEWSBLUR.reader.force_feed_refresh(); + $.modal.close(); + } }); }, save_story: function() { - var $save = $('.NB-classifier input[type=submit]'); + var $save = $('.NB-modal-submit-save', this.$modal); var story_id = this.story_id; var data = this.serialize_classifier(); - NEWSBLUR.reader.update_opinions(this.$classifier, this.feed_id); + NEWSBLUR.reader.update_opinions(this.$modal, this.feed_id); $save.text('Saving...').addClass('NB-disabled').attr('disabled', true); this.model.save_classifier_story(story_id, data, function() { @@ -500,3 +663,4 @@ var classifier = { NEWSBLUR.ReaderClassifierStory.prototype = classifier; NEWSBLUR.ReaderClassifierFeed.prototype = classifier; +NEWSBLUR.ReaderClassifierTrainer.prototype = classifier; diff --git a/media/js/newsblur/reader_manage_feed.js b/media/js/newsblur/reader_manage_feed.js index 021993318..bd6223bfb 100644 --- a/media/js/newsblur/reader_manage_feed.js +++ b/media/js/newsblur/reader_manage_feed.js @@ -255,19 +255,9 @@ NEWSBLUR.ReaderManageFeed.prototype = { open_modal: function() { var self = this; - - var $holder = $.make('div', { className: 'NB-modal-holder' }).append(this.$manage).appendTo('body').css({'visibility': 'hidden', 'display': 'block', 'width': 600}); - var height = $('.NB-manage', $holder).outerHeight(true); - $holder.css({'visibility': 'visible', 'display': 'none'}); - - var w = $.modal.impl.getDimensions(); - if (height > w[0] - 70) { - height = w[0] - 70; - } this.$manage.modal({ 'minWidth': 600, - 'maxHeight': height, 'overlayClose': true, 'onOpen': function (dialog) { dialog.overlay.fadeIn(200, function () { diff --git a/media/js/newsblur/reader_mark_read.js b/media/js/newsblur/reader_mark_read.js index e1aa4a990..b90fb463d 100644 --- a/media/js/newsblur/reader_mark_read.js +++ b/media/js/newsblur/reader_mark_read.js @@ -30,7 +30,7 @@ NEWSBLUR.ReaderMarkRead.prototype = { $.make('div', { className: 'NB-markread-slider'}), $.make('div', { className: 'NB-markread-explanation'}), $.make('div', { className: 'NB-modal-submit' }, [ - $.make('input', { type: 'submit', className: '', value: 'Do it' }), + $.make('input', { type: 'submit', className: 'NB-modal-submit-save', value: 'Do it' }), ' or ', $.make('a', { href: '#', className: 'NB-modal-cancel' }, 'cancel') ]) @@ -125,11 +125,15 @@ NEWSBLUR.ReaderMarkRead.prototype = { var days = $slider.slider('option', 'value'); $save.attr('value', 'Marking as read...').addClass('NB-disabled').attr('disabled', true); - this.model.save_mark_read(days, function() { - NEWSBLUR.reader.force_feed_refresh(function() { - $.modal.close(); + if (NEWSBLUR.Globals.is_authenticated) { + this.model.save_mark_read(days, function() { + NEWSBLUR.reader.force_feed_refresh(function() { + $.modal.close(); + }); }); - }); + } else { + $.modal.close(); + } }, // =========== diff --git a/media/js/newsblur/reader_statistics.js b/media/js/newsblur/reader_statistics.js index bfb1ac8a7..cc4eb1c7b 100644 --- a/media/js/newsblur/reader_statistics.js +++ b/media/js/newsblur/reader_statistics.js @@ -28,8 +28,8 @@ NEWSBLUR.ReaderStatistics.prototype = { $.make('div', { className: 'NB-modal-loading' }), $.make('h2', { className: 'NB-modal-title' }, 'Statistics & History'), $.make('h2', { className: 'NB-modal-subtitle' }, [ - $.make('img', { className: 'NB-modal-statistics-feed-image feed_favicon', src: this.google_favicon_url + this.feed.feed_link }), - $.make('span', { className: 'NB-modal-statistics-feed-title' }, this.feed.feed_title) + $.make('img', { className: 'NB-modal-feed-image feed_favicon', src: this.google_favicon_url + this.feed.feed_link }), + $.make('span', { className: 'NB-modal-feed-title' }, this.feed.feed_title) ]), $.make('div', { className: 'NB-modal-statistics-info' }), $.make('div', { className: 'NB-modal-feed-chooser-container'}, [ @@ -42,8 +42,8 @@ NEWSBLUR.ReaderStatistics.prototype = { this.feed_id = feed_id; this.feed = this.model.get_feed(feed_id); - $('.NB-modal-subtitle .NB-modal-statistics-feed-image', this.$manage).attr('src', this.google_favicon_url + this.feed['feed_link']); - $('.NB-modal-subtitle .NB-modal-statistics-feed-title', this.$manage).html(this.feed['feed_title']); + $('.NB-modal-subtitle .NB-modal-feed-image', this.$modal).attr('src', this.google_favicon_url + this.feed['feed_link']); + $('.NB-modal-subtitle .NB-modal-feed-title', this.$modal).html(this.feed['feed_title']); }, open_modal: function() { diff --git a/utils/munin/newsblur_users.py b/utils/munin/newsblur_users.py index a14e8b6ac..8a89e9b9f 100755 --- a/utils/munin/newsblur_users.py +++ b/utils/munin/newsblur_users.py @@ -11,13 +11,16 @@ graph_config = { 'graph_vlabel' : 'users', 'all.label': 'all', 'monthly.label': 'monthly', + 'daily.label': 'daily', } last_month = datetime.datetime.now() - datetime.timedelta(days=30) +last_day = datetime.datetime.now() - datetime.timedelta(minutes=60*24) metrics = { 'all': User.objects.count(), - 'monthly': Profile.objects.filter(last_seen_on__gte=last_month).count() + 'monthly': Profile.objects.filter(last_seen_on__gte=last_month).count(), + 'daily': Profile.objects.filter(last_seen_on__gte=last_day).count(), } if __name__ == '__main__':