mirror of
https://github.com/viq/NewsBlur.git
synced 2025-09-18 21:43:31 +00:00
Merge branch 'master' into river
This commit is contained in:
commit
f96b2d2308
10 changed files with 555 additions and 97 deletions
151
apps/reader/migrations/0007_user_title.py
Normal file
151
apps/reader/migrations/0007_user_title.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
# 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.user_title'
|
||||
db.add_column('reader_usersubscription', 'user_title', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting field 'UserSubscription.user_title'
|
||||
db.delete_column('reader_usersubscription', 'user_title')
|
||||
|
||||
|
||||
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': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", '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'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'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': {'ordering': "('name',)", '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': {'ordering': "['-date']", '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'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subscribers'", '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'}),
|
||||
'last_read_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 11, 27, 20, 26, 0, 567540)'}),
|
||||
'mark_read_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2010, 11, 27, 20, 26, 0, 567540)'}),
|
||||
'needs_unread_recalc': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'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.now'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subscriptions'", 'to': "orm['auth.User']"}),
|
||||
'user_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'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']", 'unique': 'True'})
|
||||
},
|
||||
'rss_feeds.feed': {
|
||||
'Meta': {'ordering': "['feed_title']", 'object_name': 'Feed', 'db_table': "'feeds'"},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'active_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
|
||||
'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': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'exception_code': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'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'}),
|
||||
'fetched_once': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'has_feed_exception': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
|
||||
'has_page_exception': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': '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', [], {'db_index': 'True'}),
|
||||
'min_to_decay': ('django.db.models.fields.IntegerField', [], {'default': '15'}),
|
||||
'next_scheduled_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||
'num_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
|
||||
'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'}),
|
||||
'premium_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
|
||||
'queued_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||
'stories_last_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'story_count_history': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.story': {
|
||||
'Meta': {'ordering': "['-story_date']", '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'}),
|
||||
'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'})
|
||||
},
|
||||
'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'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['reader']
|
|
@ -21,6 +21,7 @@ class UserSubscription(models.Model):
|
|||
UNREAD_CUTOFF = datetime.datetime.utcnow() - datetime.timedelta(days=settings.DAYS_OF_UNREAD)
|
||||
user = models.ForeignKey(User, related_name='subscriptions')
|
||||
feed = models.ForeignKey(Feed, related_name='subscribers')
|
||||
user_title = models.CharField(max_length=255, null=True, blank=True)
|
||||
active = models.BooleanField(default=False)
|
||||
last_read_date = models.DateTimeField(default=UNREAD_CUTOFF)
|
||||
mark_read_date = models.DateTimeField(default=UNREAD_CUTOFF)
|
||||
|
@ -243,7 +244,7 @@ class UserSubscriptionFolders(models.Model):
|
|||
multiples_found = True
|
||||
logging.info(" ---> [%s] ~FM~SBDeleting feed, and a multiple has been found in '%s'" % (self.user, folder_name))
|
||||
if folder == feed_id and folder_name == in_folder and not deleted:
|
||||
logging.info(" ---> [%s] ~FMDelete feed: %s'th item: %s folders/feeds" % (
|
||||
logging.info(" ---> [%s] ~FRDelete feed: %s'th item: %s folders/feeds" % (
|
||||
self.user, k, len(old_folders)
|
||||
))
|
||||
deleted = True
|
||||
|
@ -286,7 +287,7 @@ class UserSubscriptionFolders(models.Model):
|
|||
elif isinstance(folder, dict):
|
||||
for f_k, f_v in folder.items():
|
||||
if f_k == folder_to_delete and folder_name == in_folder:
|
||||
logging.info(" ---> [%s] Deleting folder '%s' in '%s': %s" % (self.user, f_k, folder_name, folder))
|
||||
logging.info(" ---> [%s] ~FRDeleting folder '~SB%s~SN' in '%s': %s" % (self.user, f_k, folder_name, folder))
|
||||
else:
|
||||
nf, feeds_to_delete = _find_folder_in_folders(f_v, f_k, feeds_to_delete)
|
||||
new_folders.append({f_k: nf})
|
||||
|
@ -299,6 +300,28 @@ class UserSubscriptionFolders(models.Model):
|
|||
self.save()
|
||||
|
||||
UserSubscription.objects.filter(user=self.user, feed__in=feeds_to_delete).delete()
|
||||
|
||||
def rename_folder(self, folder_to_rename, new_folder_name, in_folder):
|
||||
def _find_folder_in_folders(old_folders, folder_name):
|
||||
new_folders = []
|
||||
for k, folder in enumerate(old_folders):
|
||||
if isinstance(folder, int):
|
||||
new_folders.append(folder)
|
||||
elif isinstance(folder, dict):
|
||||
for f_k, f_v in folder.items():
|
||||
nf = _find_folder_in_folders(f_v, f_k)
|
||||
if f_k == folder_to_rename and folder_name == in_folder:
|
||||
logging.info(" ---> [%s] ~FRRenaming folder '~SB%s~SN' in '%s' to: ~SB%s" % (
|
||||
self.user, f_k, folder_name, new_folder_name))
|
||||
f_k = new_folder_name
|
||||
new_folders.append({f_k: nf})
|
||||
|
||||
return new_folders
|
||||
|
||||
user_sub_folders = json.decode(self.folders)
|
||||
user_sub_folders = _find_folder_in_folders(user_sub_folders, '')
|
||||
self.folders = json.encode(user_sub_folders)
|
||||
self.save()
|
||||
|
||||
class Feature(models.Model):
|
||||
"""
|
||||
|
|
|
@ -20,6 +20,8 @@ urlpatterns = patterns('',
|
|||
url(r'^mark_feed_as_read', views.mark_feed_as_read),
|
||||
url(r'^delete_feed', views.delete_feed, name='delete-feed'),
|
||||
url(r'^delete_folder', views.delete_folder, name='delete-folder'),
|
||||
url(r'^rename_feed', views.rename_feed, name='rename-feed'),
|
||||
url(r'^rename_folder', views.rename_folder, name='rename-folder'),
|
||||
url(r'^add_url', views.add_url),
|
||||
url(r'^add_folder', views.add_folder),
|
||||
url(r'^add_feature', views.add_feature, name='add-feature'),
|
||||
|
|
|
@ -136,7 +136,7 @@ def load_feeds(request):
|
|||
for sub in user_subs:
|
||||
feeds[sub.feed.pk] = {
|
||||
'id': sub.feed.pk,
|
||||
'feed_title': sub.feed.feed_title,
|
||||
'feed_title': sub.user_title or sub.feed.feed_title,
|
||||
'feed_address': sub.feed.feed_address,
|
||||
'feed_link': sub.feed.feed_link,
|
||||
'ps': sub.unread_count_positive,
|
||||
|
@ -192,7 +192,7 @@ def load_feeds_iphone(request):
|
|||
sub.calculate_feed_scores(silent=True)
|
||||
feeds[sub.feed.pk] = {
|
||||
'id': sub.feed.pk,
|
||||
'feed_title': sub.feed.feed_title,
|
||||
'feed_title': sub.user_title or sub.feed.feed_title,
|
||||
'feed_link': sub.feed.feed_link,
|
||||
'ps': sub.unread_count_positive,
|
||||
'nt': sub.unread_count_neutral,
|
||||
|
@ -615,6 +615,36 @@ def delete_folder(request):
|
|||
|
||||
return dict(code=1)
|
||||
|
||||
@ajax_login_required
|
||||
@json.json_view
|
||||
def rename_feed(request):
|
||||
feed = get_object_or_404(Feed, pk=int(request.POST['feed_id']))
|
||||
user_sub = UserSubscription.objects.get(user=request.user, feed=feed)
|
||||
feed_title = request.POST['feed_title']
|
||||
|
||||
logging.info(" ---> [%s] ~FRRenaming feed '~SB%s~SN' to: ~SB%s" % (
|
||||
request.user, feed.feed_title, feed_title))
|
||||
|
||||
user_sub.user_title = feed_title
|
||||
user_sub.save()
|
||||
|
||||
return dict(code=1)
|
||||
|
||||
@ajax_login_required
|
||||
@json.json_view
|
||||
def rename_folder(request):
|
||||
folder_to_rename = request.POST['folder_name']
|
||||
new_folder_name = request.POST['new_folder_name']
|
||||
in_folder = request.POST.get('in_folder', '')
|
||||
|
||||
# Works piss poor with duplicate folder titles, if they are both in the same folder.
|
||||
# renames all, but only in the same folder parent. But nobody should be doing that, right?
|
||||
if new_folder_name:
|
||||
user_sub_folders = get_object_or_404(UserSubscriptionFolders, user=request.user)
|
||||
user_sub_folders.rename_folder(folder_to_rename, new_folder_name, in_folder)
|
||||
|
||||
return dict(code=1)
|
||||
|
||||
@login_required
|
||||
def add_feature(request):
|
||||
if not request.user.is_staff:
|
||||
|
|
|
@ -340,9 +340,6 @@ class Feed(models.Model):
|
|||
|
||||
existing_story, story_has_changed = self._exists_story(story, story_content, existing_stories)
|
||||
if existing_story is None:
|
||||
# pub_date = datetime.datetime.timetuple(story.get('published'))
|
||||
# logging.debug('- New story: %s %s' % (pub_date, story.get('title')))
|
||||
|
||||
s = MStory(story_feed_id = self.pk,
|
||||
story_date = story.get('published'),
|
||||
story_title = story.get('title'),
|
||||
|
|
|
@ -3343,7 +3343,7 @@ background: transparent;
|
|||
.NB-menu-manage .NB-menu-manage-delete .NB-menu-manage-image {
|
||||
background: transparent url('../img/icons/silk/bin_closed.png') no-repeat -2px -1px;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-delete.NB-menu-manage-delete-cancel .NB-menu-manage-image {
|
||||
.NB-menu-manage .NB-menu-manage-delete.NB-menu-manage-feed-delete-cancel .NB-menu-manage-image {
|
||||
background: transparent url('../img/icons/silk/arrow_rotate_clockwise.png') no-repeat 0 -1px;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-delete-confirm .NB-menu-manage-image {
|
||||
|
@ -3351,6 +3351,57 @@ background: transparent;
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.NB-menu-manage .NB-menu-manage-rename .NB-menu-manage-image {
|
||||
background: transparent url('../img/icons/silk/font.png') no-repeat 0px 0px;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-rename.NB-active .NB-menu-manage-image {
|
||||
background: transparent url('../img/icons/silk/accept.png') no-repeat 0 0;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-rename.NB-menu-manage-feed-rename-cancel .NB-menu-manage-image {
|
||||
background: transparent url('../img/icons/silk/arrow_rotate_clockwise.png') no-repeat 0 -1px;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-rename-confirm .NB-menu-manage-image {
|
||||
background: transparent url('../img/icons/silk/bullet_arrow_right.png') no-repeat 2px -3px;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-rename-confirm {
|
||||
overflow: hidden;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-rename-confirm input.NB-menu-manage-title {
|
||||
padding: 1px;
|
||||
font-size: 11px;
|
||||
margin: 2px 0 2px 36px;
|
||||
display: block;
|
||||
width: 164px;
|
||||
float: left;
|
||||
height: 15px;
|
||||
border: 1px solid #606060;
|
||||
-moz-box-shadow:2px 2px 0 #95AB76;
|
||||
-webkit-box-shadow:2px 2px 0 #95AB76;
|
||||
box-shadow:2px 2px 0 #95AB76;
|
||||
}
|
||||
.NB-menu-manage li.NB-menu-manage-rename-confirm:hover {
|
||||
background-color: #BAE3A8;
|
||||
cursor: default;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-rename-confirm input.NB-menu-manage-title,
|
||||
.NB-menu-manage .NB-menu-manage-rename-confirm:hover input.NB-menu-manage-title {
|
||||
text-shadow: none;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-rename-confirm .NB-menu-manage-rename-save {
|
||||
float: right;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
margin: 2px 4px 2px 0;
|
||||
background-color: #639510;
|
||||
cursor: pointer;
|
||||
-moz-box-shadow:2px 2px 0 #95AB76;
|
||||
-webkit-box-shadow:2px 2px 0 #95AB76;
|
||||
box-shadow:2px 2px 0 #95AB76;
|
||||
}
|
||||
|
||||
.NB-menu-manage .NB-menu-manage-site-mark-read .NB-menu-manage-image {
|
||||
background: transparent url('../img/icons/silk/control_fastforward_blue.png') no-repeat 0 -1px;
|
||||
}
|
||||
|
@ -3364,6 +3415,9 @@ background: transparent;
|
|||
background: transparent url('../img/icons/silk/color_swatch.png') no-repeat 0 0;
|
||||
}
|
||||
|
||||
.NB-menu-manage .NB-menu-manage-rename-confirm {
|
||||
display: none;
|
||||
}
|
||||
.NB-menu-manage .NB-menu-manage-delete-confirm {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -470,6 +470,30 @@ NEWSBLUR.AssetModel.Reader.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
rename_feed: function(feed_id, feed_title, callback) {
|
||||
this.feeds[feed_id].feed_title = feed_title;
|
||||
if (NEWSBLUR.Globals.is_authenticated) {
|
||||
this.make_request('/reader/rename_feed', {
|
||||
'feed_id' : feed_id,
|
||||
'feed_title' : feed_title
|
||||
}, callback, null);
|
||||
} else {
|
||||
if ($.isFunction(callback)) callback();
|
||||
}
|
||||
},
|
||||
|
||||
rename_folder: function(folder_name, new_folder_name, in_folder, callback) {
|
||||
if (NEWSBLUR.Globals.is_authenticated) {
|
||||
this.make_request('/reader/rename_folder', {
|
||||
'folder_name' : folder_name,
|
||||
'new_folder_name' : new_folder_name,
|
||||
'in_folder' : in_folder
|
||||
}, callback, null);
|
||||
} else {
|
||||
if ($.isFunction(callback)) callback();
|
||||
}
|
||||
},
|
||||
|
||||
save_add_url: function(url, folder, callback) {
|
||||
this.make_request('/reader/add_url/', {
|
||||
'url': url,
|
||||
|
|
|
@ -752,7 +752,7 @@
|
|||
$.make('img', { className: 'feed_favicon', src: NEWSBLUR.Globals.google_favicon_url + feed.feed_link }),
|
||||
$.make('span', { className: 'feed_title' }, [
|
||||
feed.feed_title,
|
||||
$.make('span', { className: 'NB-feedbar-train-feed', title: 'Train Intelligence' }),
|
||||
(type == 'story' && $.make('span', { className: 'NB-feedbar-train-feed', title: 'Train Intelligence' })),
|
||||
(type == 'story' && $.make('span', { className: 'NB-feedbar-statistics', title: 'Statistics' }))
|
||||
]),
|
||||
(type == 'story' && $.make('div', { className: 'NB-feedbar-last-updated' }, [
|
||||
|
@ -996,7 +996,7 @@
|
|||
$('.NB-hover', $folder).removeClass('NB-hover');
|
||||
$this.addClass("NB-hover");
|
||||
// NEWSBLUR.log(['scroll', $this.scrollTop(), $this.offset(), $this.position()]);
|
||||
if ($this.offset().top > $(window).height() - 181) {
|
||||
if ($this.offset().top > $(window).height() - 204) {
|
||||
$this.addClass('NB-hover-inverse');
|
||||
}
|
||||
}
|
||||
|
@ -1294,39 +1294,6 @@
|
|||
this.story_view = view;
|
||||
},
|
||||
|
||||
delete_feed: function(feed_id, $feed) {
|
||||
var self = this;
|
||||
$feed = $feed || this.find_feed_in_feed_list(feed_id);
|
||||
$feed.slideUp(500);
|
||||
|
||||
if (this.active_feed == $feed.data('feed_id')) {
|
||||
this.reset_feed();
|
||||
this.show_splash_page();
|
||||
}
|
||||
this.update_header_counts();
|
||||
},
|
||||
|
||||
delete_folder: function(folder_name, $folder) {
|
||||
var self = this;
|
||||
var feeds = this.get_feed_ids_in_folder($folder);
|
||||
|
||||
if ($folder.length) {
|
||||
$folder.slideUp(500);
|
||||
}
|
||||
|
||||
// If the active feed is under this folder, deselect it.
|
||||
var feed_active = false;
|
||||
_.each(feeds, _.bind(function(feed_id) {
|
||||
if (self.active_feed == feed_id) {
|
||||
this.reset_feed();
|
||||
this.show_splash_page();
|
||||
return false;
|
||||
}
|
||||
}, this));
|
||||
|
||||
this.update_header_counts();
|
||||
},
|
||||
|
||||
// ===============
|
||||
// = Feed Header =
|
||||
// ===============
|
||||
|
@ -2742,6 +2709,10 @@
|
|||
}, true);
|
||||
});
|
||||
},
|
||||
|
||||
// =======================
|
||||
// = Sidebar Manage Menu =
|
||||
// =======================
|
||||
|
||||
make_manage_menu: function(type, feed_id, inverse, $item) {
|
||||
var $manage_menu;
|
||||
|
@ -2808,6 +2779,15 @@
|
|||
$.make('div', { className: 'NB-menu-manage-title' }, 'Intelligence trainer')
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-separator' }),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-rename NB-menu-manage-feed-rename' }, [
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-menu-manage-title' }, 'Rename this site')
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-rename-confirm NB-menu-manage-feed-rename-confirm NB-modal-submit' }, [
|
||||
$.make('div', { className: 'NB-menu-manage-rename-save NB-menu-manage-feed-rename-save NB-modal-submit-green NB-modal-submit-button' }, 'Save'),
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('input', { name: 'new_title', className: 'NB-menu-manage-title', value: feed.feed_title })
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-delete NB-menu-manage-feed-delete' }, [
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-menu-manage-title' }, 'Delete this site')
|
||||
|
@ -2830,6 +2810,15 @@
|
|||
$.make('div', { className: 'NB-menu-manage-title' }, 'Mark folder as read')
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-separator' }),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-rename NB-menu-manage-folder-rename' }, [
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-menu-manage-title' }, 'Rename this folder')
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-rename-confirm NB-menu-manage-folder-rename-confirm NB-modal-submit' }, [
|
||||
$.make('div', { className: 'NB-menu-manage-rename-save NB-menu-manage-folder-rename-save NB-modal-submit-green NB-modal-submit-button' }, 'Save'),
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('input', { name: 'new_title', className: 'NB-menu-manage-title', value: feed_id })
|
||||
]),
|
||||
$.make('li', { className: 'NB-menu-manage-feed NB-menu-manage-delete NB-menu-manage-folder-delete' }, [
|
||||
$.make('div', { className: 'NB-menu-manage-image' }),
|
||||
$.make('div', { className: 'NB-menu-manage-title' }, 'Delete this folder')
|
||||
|
@ -2944,7 +2933,7 @@
|
|||
|
||||
// Hide menu on scroll.
|
||||
this.flags['feed_list_showing_manage_menu'] = true;
|
||||
this.$s.$feed_list.unbind('scroll.manage_menu').bind('scroll.manage_menu', function(e) {
|
||||
this.$s.$feed_list.parent().unbind('scroll.manage_menu').bind('scroll.manage_menu', function(e) {
|
||||
if (self.flags['feed_list_showing_manage_menu']) {
|
||||
self.hide_manage_menu(type, $item, true);
|
||||
} else {
|
||||
|
@ -2956,7 +2945,7 @@
|
|||
hide_manage_menu: function(type, $item, animate) {
|
||||
var $manage_menu_container = $('.NB-menu-manage-container');
|
||||
var height = $manage_menu_container.outerHeight();
|
||||
|
||||
if (this.flags['showing_rename_input_on_manage_menu'] && animate) return;
|
||||
// NEWSBLUR.log(['hide_manage_menu', type, $item, animate, $manage_menu_container.css('opacity')]);
|
||||
|
||||
clearTimeout(this.flags.closed_manage_menu);
|
||||
|
@ -2980,6 +2969,10 @@
|
|||
$('.NB-task-manage').removeClass('NB-hover');
|
||||
},
|
||||
|
||||
// ========================
|
||||
// = Manage menu - Delete =
|
||||
// ========================
|
||||
|
||||
show_confirm_delete_menu_item: function() {
|
||||
var $delete = $('.NB-menu-manage-feed-delete,.NB-menu-manage-folder-delete');
|
||||
var $confirm = $('.NB-menu-manage-feed-delete-confirm,.NB-menu-manage-folder-delete-confirm');
|
||||
|
@ -2994,10 +2987,10 @@
|
|||
var $confirm = $('.NB-menu-manage-feed-delete-confirm,.NB-menu-manage-folder-delete-confirm');
|
||||
|
||||
$delete.removeClass('NB-menu-manage-feed-delete-cancel');
|
||||
var text = 'Delete this site';
|
||||
if ($delete.hasClass('NB-menu-manage-folder-delete')) {
|
||||
text = "Delete this folder";
|
||||
}
|
||||
|
||||
var text = $delete.hasClass('NB-menu-manage-folder-delete') ?
|
||||
'Delete this folder' :
|
||||
'Delete this site';
|
||||
$('.NB-menu-manage-title', $delete).text(text);
|
||||
$confirm.slideUp(500);
|
||||
},
|
||||
|
@ -3028,6 +3021,126 @@
|
|||
});
|
||||
},
|
||||
|
||||
delete_feed: function(feed_id, $feed) {
|
||||
var self = this;
|
||||
$feed = $feed || this.find_feed_in_feed_list(feed_id);
|
||||
$feed.slideUp(500);
|
||||
|
||||
if (this.active_feed == $feed.data('feed_id')) {
|
||||
this.reset_feed();
|
||||
this.show_splash_page();
|
||||
}
|
||||
this.update_header_counts();
|
||||
},
|
||||
|
||||
delete_folder: function(folder_name, $folder) {
|
||||
var self = this;
|
||||
var feeds = this.get_feed_ids_in_folder($folder);
|
||||
|
||||
if ($folder.length) {
|
||||
$folder.slideUp(500);
|
||||
}
|
||||
|
||||
// If the active feed is under this folder, deselect it.
|
||||
var feed_active = false;
|
||||
_.each(feeds, _.bind(function(feed_id) {
|
||||
if (self.active_feed == feed_id) {
|
||||
this.reset_feed();
|
||||
this.show_splash_page();
|
||||
return false;
|
||||
}
|
||||
}, this));
|
||||
|
||||
this.update_header_counts();
|
||||
},
|
||||
|
||||
// ========================
|
||||
// = Manage menu - Rename =
|
||||
// ========================
|
||||
|
||||
show_confirm_rename_menu_item: function() {
|
||||
var self = this;
|
||||
var $rename = $('.NB-menu-manage-feed-rename,.NB-menu-manage-folder-rename');
|
||||
var $confirm = $('.NB-menu-manage-feed-rename-confirm,.NB-menu-manage-folder-rename-confirm');
|
||||
|
||||
$rename.addClass('NB-menu-manage-feed-rename-cancel');
|
||||
$('.NB-menu-manage-title', $rename).text('Cancel rename');
|
||||
var height = $confirm.height();
|
||||
$confirm.css({'height': 0, 'display': 'block'}).animate({'height': height}, {'duration': 500});
|
||||
$('input', $confirm).focus().select();
|
||||
this.flags['showing_rename_input_on_manage_menu'] = true;
|
||||
$('.NB-menu-manage-feed-rename-confirm input.NB-menu-manage-title').bind('keyup', 'return', function(e) {
|
||||
var $t = $(e.target);
|
||||
var feed_id = $t.closest('.NB-menu-manage').data('feed_id');
|
||||
var $feed = $t.closest('.NB-menu-manage').data('$feed');
|
||||
self.manage_menu_rename_feed(feed_id, $feed);
|
||||
});
|
||||
$('.NB-menu-manage-folder-rename-confirm input.NB-menu-manage-title').bind('keyup', 'return', function(e) {
|
||||
var $t = $(e.target);
|
||||
var folder_name = $t.parents('.NB-menu-manage').data('folder_name');
|
||||
var $folder = $t.parents('.NB-menu-manage').data('$folder');
|
||||
self.manage_menu_rename_folder(folder_name, $folder);
|
||||
});
|
||||
},
|
||||
|
||||
hide_confirm_rename_menu_item: function(renamed) {
|
||||
var $rename = $('.NB-menu-manage-feed-rename,.NB-menu-manage-folder-rename');
|
||||
var $confirm = $('.NB-menu-manage-feed-rename-confirm,.NB-menu-manage-folder-rename-confirm');
|
||||
|
||||
$rename.removeClass('NB-menu-manage-feed-rename-cancel');
|
||||
var text = $rename.hasClass('NB-menu-manage-folder-rename') ?
|
||||
'Rename this folder' :
|
||||
'Rename this site';
|
||||
if (renamed) {
|
||||
text = 'Renamed';
|
||||
$rename.addClass('NB-active');
|
||||
} else {
|
||||
$rename.removeClass('NB-active');
|
||||
}
|
||||
$('.NB-menu-manage-title', $rename).text(text);
|
||||
$confirm.slideUp(500);
|
||||
this.flags['showing_rename_input_on_manage_menu'] = false;
|
||||
},
|
||||
|
||||
manage_menu_rename_feed: function(feed, $feed) {
|
||||
var self = this;
|
||||
var feed_id = feed || this.active_feed;
|
||||
$feed = $feed || this.find_feed_in_feed_list(feed_id);
|
||||
var new_title = $('.NB-menu-manage-feed-rename-confirm .NB-menu-manage-title').val();
|
||||
|
||||
if (new_title.length <= 0) return this.hide_confirm_rename_menu_item();
|
||||
|
||||
this.model.rename_feed(feed_id, new_title, function() {
|
||||
});
|
||||
|
||||
$('.feed_title', $feed).text(new_title);
|
||||
if (feed_id == this.active_feed) {
|
||||
$('.feed_title', this.$s.$story_titles).text(new_title);
|
||||
}
|
||||
this.hide_confirm_rename_menu_item(true);
|
||||
},
|
||||
|
||||
manage_menu_rename_folder: function(folder, $folder) {
|
||||
var self = this;
|
||||
var in_folder = '';
|
||||
var $parent = $folder.parents('li.folder');
|
||||
var new_folder_name = $('.NB-menu-manage-folder-rename-confirm .NB-menu-manage-title').val();
|
||||
|
||||
if (new_folder_name.length <= 0) return this.hide_confirm_rename_menu_item();
|
||||
|
||||
if ($parent.length) {
|
||||
in_folder = $parent.eq(0).find('.folder_title_text').eq(0).text();
|
||||
}
|
||||
|
||||
this.model.rename_folder(folder, new_folder_name, in_folder, function() {
|
||||
});
|
||||
NEWSBLUR.log(['rename', $folder, new_folder_name]);
|
||||
$('.folder_title_text', $folder).text(new_folder_name);
|
||||
this.hide_confirm_rename_menu_item(true);
|
||||
|
||||
$('.NB-menu-manage-folder-rename').parents('.NB-menu-manage').data('folder_name', new_folder_name);
|
||||
},
|
||||
|
||||
// ==========================
|
||||
// = Taskbar - Intelligence =
|
||||
// ==========================
|
||||
|
@ -3907,6 +4020,38 @@
|
|||
var $folder = $t.parents('.NB-menu-manage').data('$folder');
|
||||
self.manage_menu_delete_folder(folder_name, $folder);
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-menu-manage-rename' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if ($t.hasClass('NB-menu-manage-feed-rename-cancel') ||
|
||||
$t.hasClass('NB-menu-manage-folder-rename-cancel')) {
|
||||
self.hide_confirm_rename_menu_item();
|
||||
} else {
|
||||
self.show_confirm_rename_menu_item();
|
||||
}
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-menu-manage-folder-rename-save' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var folder_name = $t.parents('.NB-menu-manage').data('folder_name');
|
||||
var $folder = $t.parents('.NB-menu-manage').data('$folder');
|
||||
self.manage_menu_rename_folder(folder_name, $folder);
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-menu-manage-feed-rename-save' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var feed_id = $t.parents('.NB-menu-manage').data('feed_id');
|
||||
var $feed = $t.parents('.NB-menu-manage').data('$feed');
|
||||
self.manage_menu_rename_feed(feed_id, $feed);
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-menu-manage-feed-rename-confirm' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-menu-manage-folder-rename-confirm' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
$.targetIs(e, { tagSelector: '.NB-menu-manage-feed-mark-read' }, function($t, $p){
|
||||
e.preventDefault();
|
||||
var feed_id = $t.parents('.NB-menu-manage').data('feed_id');
|
||||
|
|
|
@ -41,7 +41,8 @@ __contributors__ = ["Jason Diamond <http://injektilo.org/>",
|
|||
"Aaron Swartz <http://aaronsw.com/>",
|
||||
"Kevin Marks <http://epeus.blogspot.com/>",
|
||||
"Sam Ruby <http://intertwingly.net/>",
|
||||
"Ade Oshineye <http://blog.oshineye.com/>"]
|
||||
"Ade Oshineye <http://blog.oshineye.com/>",
|
||||
"Martin Pool <http://sourcefrog.net/>"]
|
||||
_debug = 0
|
||||
|
||||
# HTTP "User-Agent" header to send to servers when downloading feeds.
|
||||
|
@ -76,7 +77,7 @@ RESOLVE_RELATIVE_URIS = 1
|
|||
SANITIZE_HTML = 1
|
||||
|
||||
# ---------- required modules (should come with any Python distribution) ----------
|
||||
import sgmllib, re, sys, copy, urlparse, time, rfc822, types, cgi, urllib, urllib2
|
||||
import sgmllib, re, sys, copy, urlparse, time, rfc822, types, cgi, urllib, urllib2, datetime
|
||||
try:
|
||||
from cStringIO import StringIO as _StringIO
|
||||
except:
|
||||
|
@ -158,7 +159,7 @@ except:
|
|||
# older 2.x series. If it doesn't, and you can figure out why, I'll accept a
|
||||
# patch and modify the compatibility statement accordingly.
|
||||
try:
|
||||
raise Exception # import BeautifulSoup
|
||||
import BeautifulSoup
|
||||
except:
|
||||
BeautifulSoup = None
|
||||
|
||||
|
@ -928,9 +929,12 @@ class _FeedParserMixin:
|
|||
attrsD['href'] = href
|
||||
return attrsD
|
||||
|
||||
def _save(self, key, value):
|
||||
def _save(self, key, value, overwrite=False):
|
||||
context = self._getContext()
|
||||
context.setdefault(key, value)
|
||||
if overwrite:
|
||||
context[key] = value
|
||||
else:
|
||||
context.setdefault(key, value)
|
||||
|
||||
def _start_rss(self, attrsD):
|
||||
versionmap = {'0.91': 'rss091u',
|
||||
|
@ -1012,6 +1016,10 @@ class _FeedParserMixin:
|
|||
def _start_author(self, attrsD):
|
||||
self.inauthor = 1
|
||||
self.push('author', 1)
|
||||
# Append a new FeedParserDict when expecting an author
|
||||
context = self._getContext()
|
||||
context.setdefault('authors', [])
|
||||
context['authors'].append(FeedParserDict())
|
||||
_start_managingeditor = _start_author
|
||||
_start_dc_author = _start_author
|
||||
_start_dc_creator = _start_author
|
||||
|
@ -1146,6 +1154,8 @@ class _FeedParserMixin:
|
|||
context.setdefault(prefix + '_detail', FeedParserDict())
|
||||
context[prefix + '_detail'][key] = value
|
||||
self._sync_author_detail()
|
||||
context.setdefault('authors', [FeedParserDict()])
|
||||
context['authors'][-1][key] = value
|
||||
|
||||
def _save_contributor(self, key, value):
|
||||
context = self._getContext()
|
||||
|
@ -1251,7 +1261,7 @@ class _FeedParserMixin:
|
|||
|
||||
def _end_published(self):
|
||||
value = self.pop('published')
|
||||
self._save('published_parsed', _parse_date(value))
|
||||
self._save('published_parsed', _parse_date(value), overwrite=True)
|
||||
_end_dcterms_issued = _end_published
|
||||
_end_issued = _end_published
|
||||
|
||||
|
@ -1265,7 +1275,7 @@ class _FeedParserMixin:
|
|||
def _end_updated(self):
|
||||
value = self.pop('updated')
|
||||
parsed_value = _parse_date(value)
|
||||
self._save('updated_parsed', parsed_value)
|
||||
self._save('updated_parsed', parsed_value, overwrite=True)
|
||||
_end_modified = _end_updated
|
||||
_end_dcterms_modified = _end_updated
|
||||
_end_pubdate = _end_updated
|
||||
|
@ -1277,14 +1287,14 @@ class _FeedParserMixin:
|
|||
|
||||
def _end_created(self):
|
||||
value = self.pop('created')
|
||||
self._save('created_parsed', _parse_date(value))
|
||||
self._save('created_parsed', _parse_date(value), overwrite=True)
|
||||
_end_dcterms_created = _end_created
|
||||
|
||||
def _start_expirationdate(self, attrsD):
|
||||
self.push('expired', 1)
|
||||
|
||||
def _end_expirationdate(self):
|
||||
self._save('expired_parsed', _parse_date(self.pop('expired')))
|
||||
self._save('expired_parsed', _parse_date(self.pop('expired')), overwrite=True)
|
||||
|
||||
def _start_cc_license(self, attrsD):
|
||||
context = self._getContext()
|
||||
|
@ -1558,7 +1568,10 @@ class _FeedParserMixin:
|
|||
|
||||
def _end_itunes_explicit(self):
|
||||
value = self.pop('itunes_explicit', 0)
|
||||
self._getContext()['itunes_explicit'] = (value == 'yes') and 1 or 0
|
||||
# Convert 'yes' -> True, 'clean' to False, and any other value to None
|
||||
# False and None both evaluate as False, so the difference can be ignored
|
||||
# by applications that only need to know if the content is explicit.
|
||||
self._getContext()['itunes_explicit'] = (None, False, True)[(value == 'yes' and 2) or value == 'clean' or 0]
|
||||
|
||||
def _start_media_content(self, attrsD):
|
||||
context = self._getContext()
|
||||
|
@ -2070,8 +2083,8 @@ class _MicroformatsParser:
|
|||
sAgentValue = sAgentValue.replace(';', '\\;')
|
||||
if sAgentValue:
|
||||
arLines.append(self.vcardFold('AGENT:' + sAgentValue))
|
||||
elmAgent['class'] = ''
|
||||
elmAgent.contents = []
|
||||
# Completely remove the agent element from the parse tree
|
||||
elmAgent.extract()
|
||||
else:
|
||||
sAgentValue = self.getPropertyValue(elmAgent, 'value', self.URI, bAutoEscape=1);
|
||||
if sAgentValue:
|
||||
|
@ -2662,7 +2675,7 @@ class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler
|
|||
except:
|
||||
return self.http_error_default(req, fp, code, msg, headers)
|
||||
|
||||
def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers):
|
||||
def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers, extra_headers):
|
||||
"""URL, filename, or string --> stream
|
||||
|
||||
This function lets you define parsers that take any input source
|
||||
|
@ -2689,6 +2702,9 @@ def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, h
|
|||
|
||||
If handlers is supplied, it is a list of handlers used to build a
|
||||
urllib2 opener.
|
||||
|
||||
if extra_headers is supplied it is a dictionary of HTTP request headers
|
||||
that will override the values generated by FeedParser.
|
||||
"""
|
||||
|
||||
if hasattr(url_file_stream_or_string, 'read'):
|
||||
|
@ -2721,36 +2737,8 @@ def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, h
|
|||
pass
|
||||
|
||||
# try to open with urllib2 (to use optional headers)
|
||||
request = urllib2.Request(url_file_stream_or_string)
|
||||
request.add_header('User-Agent', agent)
|
||||
if etag:
|
||||
request.add_header('If-None-Match', etag)
|
||||
if type(modified) == type(''):
|
||||
modified = _parse_date(modified)
|
||||
if modified:
|
||||
# format into an RFC 1123-compliant timestamp. We can't use
|
||||
# time.strftime() since the %a and %b directives can be affected
|
||||
# by the current locale, but RFC 2616 states that dates must be
|
||||
# in English.
|
||||
short_weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
request.add_header('If-Modified-Since', '%s, %02d %s %04d %02d:%02d:%02d GMT' % (short_weekdays[modified[6]], modified[2], months[modified[1] - 1], modified[0], modified[3], modified[4], modified[5]))
|
||||
if referrer:
|
||||
request.add_header('Referer', referrer)
|
||||
if gzip and zlib:
|
||||
request.add_header('Accept-encoding', 'gzip, deflate')
|
||||
elif gzip:
|
||||
request.add_header('Accept-encoding', 'gzip')
|
||||
elif zlib:
|
||||
request.add_header('Accept-encoding', 'deflate')
|
||||
else:
|
||||
request.add_header('Accept-encoding', '')
|
||||
if auth:
|
||||
request.add_header('Authorization', 'Basic %s' % auth)
|
||||
if ACCEPT_HEADER:
|
||||
request.add_header('Accept', ACCEPT_HEADER)
|
||||
request.add_header('A-IM', 'feed') # RFC 3229 support
|
||||
opener = apply(urllib2.build_opener, tuple([_FeedURLHandler()] + handlers))
|
||||
request = _build_urllib2_request(url_file_stream_or_string, agent, etag, modified, referrer, auth, extra_headers)
|
||||
opener = apply(urllib2.build_opener, tuple(handlers + [_FeedURLHandler()]))
|
||||
opener.addheaders = [] # RMK - must clear so we only send our custom User-Agent
|
||||
try:
|
||||
return opener.open(request)
|
||||
|
@ -2766,6 +2754,44 @@ def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, h
|
|||
# treat url_file_stream_or_string as string
|
||||
return _StringIO(str(url_file_stream_or_string))
|
||||
|
||||
def _build_urllib2_request(url, agent, etag, modified, referrer, auth, extra_headers):
|
||||
request = urllib2.Request(url)
|
||||
request.add_header('User-Agent', agent)
|
||||
if etag:
|
||||
request.add_header('If-None-Match', etag)
|
||||
if type(modified) == type(''):
|
||||
modified = _parse_date(modified)
|
||||
elif isinstance(modified, datetime.datetime):
|
||||
modified = modified.utctimetuple()
|
||||
if modified:
|
||||
# format into an RFC 1123-compliant timestamp. We can't use
|
||||
# time.strftime() since the %a and %b directives can be affected
|
||||
# by the current locale, but RFC 2616 states that dates must be
|
||||
# in English.
|
||||
short_weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
request.add_header('If-Modified-Since', '%s, %02d %s %04d %02d:%02d:%02d GMT' % (short_weekdays[modified[6]], modified[2], months[modified[1] - 1], modified[0], modified[3], modified[4], modified[5]))
|
||||
if referrer:
|
||||
request.add_header('Referer', referrer)
|
||||
if gzip and zlib:
|
||||
request.add_header('Accept-encoding', 'gzip, deflate')
|
||||
elif gzip:
|
||||
request.add_header('Accept-encoding', 'gzip')
|
||||
elif zlib:
|
||||
request.add_header('Accept-encoding', 'deflate')
|
||||
else:
|
||||
request.add_header('Accept-encoding', '')
|
||||
if auth:
|
||||
request.add_header('Authorization', 'Basic %s' % auth)
|
||||
if ACCEPT_HEADER:
|
||||
request.add_header('Accept', ACCEPT_HEADER)
|
||||
# use this for whatever -- cookies, special headers, etc
|
||||
# [('Cookie','Something'),('x-special-header','Another Value')]
|
||||
for header_name, header_value in extra_headers.items():
|
||||
request.add_header(header_name, header_value)
|
||||
request.add_header('A-IM', 'feed') # RFC 3229 support
|
||||
return request
|
||||
|
||||
_date_handlers = []
|
||||
def registerDateHandler(func):
|
||||
'''Register a date handler function (takes string, returns 9-tuple date in GMT)'''
|
||||
|
@ -3121,7 +3147,7 @@ def _parse_date_w3dtf(dateString):
|
|||
__tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)'
|
||||
__tzd_rx = re.compile(__tzd_re)
|
||||
__time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)'
|
||||
'(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?'
|
||||
'(?:(?P=tsep)(?P<seconds>\d\d)(?:[.,]\d+)?)?'
|
||||
+ __tzd_re)
|
||||
__datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
|
||||
__datetime_rx = re.compile(__datetime_re)
|
||||
|
@ -3412,8 +3438,12 @@ def _stripDoctype(data):
|
|||
|
||||
return version, data, dict(replacement and safe_pattern.findall(replacement))
|
||||
|
||||
def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, referrer=None, handlers=[]):
|
||||
'''Parse a feed from a URL, file, stream, or string'''
|
||||
def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, referrer=None, handlers=[], extra_headers={}):
|
||||
'''Parse a feed from a URL, file, stream, or string.
|
||||
|
||||
extra_headers, if given, is a dict from http header name to value to add
|
||||
to the request; this overrides internally generated values.
|
||||
'''
|
||||
result = FeedParserDict()
|
||||
result['feed'] = FeedParserDict()
|
||||
result['entries'] = []
|
||||
|
@ -3422,7 +3452,7 @@ def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, refer
|
|||
if type(handlers) == types.InstanceType:
|
||||
handlers = [handlers]
|
||||
try:
|
||||
f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers)
|
||||
f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers, extra_headers)
|
||||
data = f.read()
|
||||
except Exception, e:
|
||||
result['bozo'] = 1
|
||||
|
@ -3566,7 +3596,7 @@ def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, refer
|
|||
elif proposed_encoding != result['encoding']:
|
||||
result['bozo'] = 1
|
||||
result['bozo_exception'] = CharacterEncodingOverride( \
|
||||
'documented declared as %s, but parsed as %s' % \
|
||||
'document declared as %s, but parsed as %s' % \
|
||||
(result['encoding'], proposed_encoding))
|
||||
result['encoding'] = proposed_encoding
|
||||
|
||||
|
|
|
@ -42,4 +42,6 @@ def pre_process_story(entry):
|
|||
+ urlquote(entry_link[protocol_index+3:]))
|
||||
else:
|
||||
entry['link'] = urlquote(entry_link)
|
||||
if isinstance(entry.get('guid'), dict):
|
||||
entry['guid'] = unicode(entry['guid'])
|
||||
return entry
|
Loading…
Add table
Reference in a new issue