diff --git a/.gitignore b/.gitignore index 7451fd76f..81d9feb83 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ logs/*.pid *.pyc static/* local_settings.py +celerybeat-schedule +celerybeat.pid media/iphone/NewsBlur/build media/iphone/build build/ @@ -24,8 +26,8 @@ media/maintenance.html.unused config/settings static.tgz media/css/circular - config/settings +config/secrets # ---------------------- # Android diff --git a/apps/analyzer/models.py b/apps/analyzer/models.py index 09aa80691..bec8e33fc 100644 --- a/apps/analyzer/models.py +++ b/apps/analyzer/models.py @@ -95,7 +95,10 @@ class MClassifierFeed(mongo.Document): def __unicode__(self): user = User.objects.get(pk=self.user_id) - feed = Feed.objects.get(pk=self.feed_id) if self.feed_id else None + if self.feed_id: + feed = Feed.objects.get(pk=self.feed_id) + else: + feed = User.objects.get(pk=self.social_user_id) return "%s - %s/%s: (%s) %s" % (user, self.feed_id, self.social_user_id, self.score, feed) @@ -132,15 +135,19 @@ def apply_classifier_tags(classifiers, story): if score > 0: return classifier.score return score -def apply_classifier_feeds(classifiers, feed, social_user_id=None): +def apply_classifier_feeds(classifiers, feed, social_user_ids=None): if not feed: return 0 feed_id = feed if isinstance(feed, int) else feed.pk + if social_user_ids and not isinstance(social_user_ids, list): + social_user_ids = [social_user_ids] + for classifier in classifiers: if classifier.feed_id == feed_id: # print 'Feeds: %s -- %s' % (classifier.feed_id, feed.pk) return classifier.score - if social_user_id and not classifier.feed_id and social_user_id == classifier.social_user_id: + if (social_user_ids and not classifier.feed_id and + classifier.social_user_id in social_user_ids): return classifier.score return 0 diff --git a/apps/api/views.py b/apps/api/views.py index 317738cb3..f7498c335 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -286,6 +286,12 @@ def share_story(request, token): shared_story.save() logging.user(profile.user, "~BM~FY~SBUpdating~SN shared story from site: ~SB%s: %s" % (story_url, comments)) + socialsub = MSocialSubscription.objects.get(user_id=profile.user.pk, + subscription_user_id=profile.user.pk) + socialsub.mark_story_ids_as_read([shared_story.story_guid], + shared_story.story_feed_id, + request=request) + shared_story.publish_update_to_subscribers() response = HttpResponse(json.encode({ diff --git a/apps/feed_import/models.py b/apps/feed_import/models.py index bab0f0a4b..0c62cd9f6 100644 --- a/apps/feed_import/models.py +++ b/apps/feed_import/models.py @@ -227,18 +227,20 @@ class GoogleReaderImporter(Importer): feeds_xml = self.send_request(sub_url) else: feeds_xml = self.xml - self.process_feeds(feeds_xml) + if feeds_xml: + self.process_feeds(feeds_xml) def send_request(self, url): user_tokens = OAuthToken.objects.filter(user=self.user) if user_tokens.count(): user_token = user_tokens[0] - credential = pickle.loads(base64.b64decode(user_token.credential)) - http = httplib2.Http() - http = credential.authorize(http) - content = http.request(url) - return content and content[1] + if user_token.credential: + credential = pickle.loads(base64.b64decode(user_token.credential)) + http = httplib2.Http() + http = credential.authorize(http) + content = http.request(url) + return content and content[1] def process_feeds(self, feeds_xml): self.clear_feeds() diff --git a/apps/profile/models.py b/apps/profile/models.py index 3fc095bbe..2f2febe37 100644 --- a/apps/profile/models.py +++ b/apps/profile/models.py @@ -195,7 +195,10 @@ NewsBlur""" % {'user': self.user.username, 'feeds': subs.count()} if not self.user.email or not self.send_emails: return - if self.is_premium and not force: + sent_email, created = MSentEmail.objects.get_or_create(receiver_user_id=self.user.pk, + email_type='new_premium') + + if not created and not force: return user = self.user diff --git a/apps/reader/forms.py b/apps/reader/forms.py index 325f0e2c9..771780a3e 100644 --- a/apps/reader/forms.py +++ b/apps/reader/forms.py @@ -11,10 +11,10 @@ from utils import log as logging class LoginForm(forms.Form): username = forms.CharField(label=_("Username or Email"), max_length=30, - widget=forms.TextInput(attrs={'tabindex': 1}), + widget=forms.TextInput(attrs={'tabindex': 1, 'class': 'NB-input'}), error_messages={'required': 'Please enter a username.'}) password = forms.CharField(label=_("Password"), - widget=forms.PasswordInput(attrs={'tabindex': 2}), + widget=forms.PasswordInput(attrs={'tabindex': 2, 'class': 'NB-input'}), required=False) # error_messages={'required': 'Please enter a password.'}) @@ -69,17 +69,17 @@ class LoginForm(forms.Form): class SignupForm(forms.Form): username = forms.RegexField(regex=r'^\w+$', max_length=30, - widget=forms.TextInput(), + widget=forms.TextInput(attrs={'class': 'NB-input'}), label=_(u'username'), error_messages={ 'required': 'Please enter a username.', 'invalid': "Your username may only contain letters and numbers." }) - email = forms.EmailField(widget=forms.TextInput(attrs=dict(maxlength=75)), + email = forms.EmailField(widget=forms.TextInput(attrs={'maxlength': 75, 'class': 'NB-input'}), label=_(u'email address'), required=False) # error_messages={'required': 'Please enter your email.'}) - password = forms.CharField(widget=forms.PasswordInput(), + password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'NB-input'}), label=_(u'password'), required=False) # error_messages={'required': 'Please enter a password.'}) diff --git a/apps/reader/models.py b/apps/reader/models.py index 847b0e14f..69938c4d8 100644 --- a/apps/reader/models.py +++ b/apps/reader/models.py @@ -555,7 +555,7 @@ class MUserStory(mongo.Document): user_id = mongo.IntField() feed_id = mongo.IntField() read_date = mongo.DateTimeField() - story_id = mongo.StringField(unique_with=('user_id', 'feed_id')) + story_id = mongo.StringField() story_date = mongo.DateTimeField() story = mongo.ReferenceField(MStory, dbref=True) found_story = mongo.GenericReferenceField() @@ -569,6 +569,7 @@ class MUserStory(mongo.Document): ], 'allow_inheritance': False, 'index_drop_dups': True, + 'cascade': False, } def save(self, *args, **kwargs): diff --git a/apps/reader/tasks.py b/apps/reader/tasks.py index f2ce05860..3d7f15c84 100644 --- a/apps/reader/tasks.py +++ b/apps/reader/tasks.py @@ -41,7 +41,6 @@ class CollectStats(Task): def run(self, **kwargs): logging.debug(" ---> Collecting stats...") MStatistics.collect_statistics() - MStatistics.delete_old_stats() class CollectFeedback(Task): @@ -50,4 +49,19 @@ class CollectFeedback(Task): def run(self, **kwargs): logging.debug(" ---> Collecting feedback...") MFeedback.collect_feedback() - \ No newline at end of file + +class CleanAnalytics(Task): + name = 'clean-analytics' + + def run(self, **kwargs): + logging.debug(" ---> Cleaning analytics... %s page loads and %s feed fetches" % ( + settings.MONGOANALYTICSDB.nbanalytics.page_loads.count(), + settings.MONGOANALYTICSDB.nbanalytics.feed_fetches.count(), + )) + day_ago = datetime.datetime.utcnow() - datetime.timedelta(days=2) + settings.MONGOANALYTICSDB.nbanalytics.feed_fetches.remove({ + "date": {"$lt": day_ago}, + }) + settings.MONGOANALYTICSDB.nbanalytics.page_loads.remove({ + "date": {"$lt": day_ago}, + }) \ No newline at end of file diff --git a/apps/reader/views.py b/apps/reader/views.py index 9edb3dce4..ed976569a 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -30,7 +30,7 @@ from apps.reader.forms import SignupForm, LoginForm, FeatureForm from apps.rss_feeds.models import MFeedIcon from apps.statistics.models import MStatistics try: - from apps.rss_feeds.models import Feed, MFeedPage, DuplicateFeed, MStory, MStarredStory, FeedLoadtime + from apps.rss_feeds.models import Feed, MFeedPage, DuplicateFeed, MStory, MStarredStory except: pass from apps.social.models import MSharedStory, MSocialProfile, MSocialServices @@ -106,7 +106,7 @@ def welcome(request, **kwargs): social_profile = MSocialProfile.get_user(user.pk) if request.method == "POST": - if request.POST.get('submit') == 'login': + if request.POST.get('submit', '').startswith('log'): login_form = LoginForm(request.POST, prefix='login') signup_form = SignupForm(prefix='signup') else: @@ -123,6 +123,7 @@ def welcome(request, **kwargs): 'signup_form' : signup_form, 'statistics' : statistics, 'social_profile' : social_profile, + 'post_request' : request.method == 'POST', }, "reader/welcome.xhtml" @never_cache @@ -541,7 +542,6 @@ def load_single_feed(request, feed_id): if timediff > 0.50 else "") logging.user(request, "~FYLoading feed: ~SB%s%s (%s/%s) %s" % ( feed.feed_title[:22], ('~SN/p%s' % page) if page > 1 else '', order, read_filter, time_breakdown)) - FeedLoadtime.objects.create(feed=feed, loadtime=timediff) data = dict(stories=stories, user_profiles=user_profiles, @@ -563,13 +563,34 @@ def load_feed_page(request, feed_id): raise Http404 feed = Feed.get_by_id(feed_id) + + if (feed.has_page and + not feed.has_page_exception and + settings.BACKED_BY_AWS['pages_on_s3'] and + feed.s3_page): + if settings.PROXY_S3_PAGES: + key = settings.S3_PAGES_BUCKET.get_key(feed.s3_pages_key) + compressed_data = key.get_contents_as_string() + response = HttpResponse(compressed_data, mimetype="text/html; charset=utf-8") + response['Content-Encoding'] = 'gzip' + + logging.user(request, "~FYLoading original page, proxied: ~SB%s bytes" % + (len(compressed_data))) + return response + else: + logging.user(request, "~FYLoading original page, non-proxied") + return HttpResponseRedirect('//%s/%s' % (settings.S3_PAGES_BUCKET_NAME, + feed.s3_pages_key)) + data = MFeedPage.get_data(feed_id=feed_id) if not data or not feed.has_page or feed.has_page_exception: + logging.user(request, "~FYLoading original page, ~FRmissing") return render(request, 'static/404_original_page.xhtml', {}, content_type='text/html', status=404) + logging.user(request, "~FYLoading original page, from the db") return HttpResponse(data, mimetype="text/html; charset=utf-8") @json.json_view diff --git a/apps/rss_feeds/icon_importer.py b/apps/rss_feeds/icon_importer.py index ee57bc77b..6405fdfda 100644 --- a/apps/rss_feeds/icon_importer.py +++ b/apps/rss_feeds/icon_importer.py @@ -6,8 +6,11 @@ import scipy.cluster import urlparse import struct import operator +import gzip import BmpImagePlugin, PngImagePlugin, Image +from boto.s3.key import Key from StringIO import StringIO +from django.conf import settings from apps.rss_feeds.models import MFeedPage, MFeedIcon from utils.feed_functions import timelimit, TimeoutError @@ -28,7 +31,10 @@ class IconImporter(object): if not self.force and self.feed.favicon_not_found: # print 'Not found, skipping...' return - if not self.force and not self.feed.favicon_not_found and self.feed_icon.icon_url: + if (not self.force and + not self.feed.favicon_not_found and + self.feed_icon.icon_url and + self.feed.s3_icon): # print 'Found, but skipping...' return image, image_file, icon_url = self.fetch_image_from_page_data() @@ -46,15 +52,19 @@ class IconImporter(object): color = self.determine_dominant_color_in_image(image) image_str = self.string_from_image(image) - if (self.feed_icon.color != color or + if (self.force or + self.feed_icon.color != color or self.feed_icon.data != image_str or self.feed_icon.icon_url != icon_url or - self.feed_icon.not_found): + self.feed_icon.not_found or + (settings.BACKED_BY_AWS.get('icons_on_s3') and not self.feed.s3_icon)): self.feed_icon.data = image_str self.feed_icon.icon_url = icon_url self.feed_icon.color = color self.feed_icon.not_found = False self.feed_icon.save() + if settings.BACKED_BY_AWS.get('icons_on_s3'): + self.save_to_s3(image_str) self.feed.favicon_color = color self.feed.favicon_not_found = False else: @@ -63,7 +73,16 @@ class IconImporter(object): self.feed.save() return not self.feed.favicon_not_found - + + def save_to_s3(self, image_str): + k = Key(settings.S3_ICONS_BUCKET) + k.key = self.feed.s3_icons_key + k.set_metadata('Content-Type', 'image/png') + k.set_contents_from_string(image_str.decode('base64')) + k.set_acl('public-read') + + self.feed.s3_icon = True + def load_icon(self, image_file, index=None): ''' Load Windows ICO image. @@ -146,6 +165,15 @@ class IconImporter(object): image_file = None if self.page_data: content = self.page_data + elif settings.BACKED_BY_AWS.get('pages_on_s3') and self.feed.s3_page: + key = settings.S3_PAGES_BUCKET.get_key(self.feed.s3_pages_key) + compressed_content = key.get_contents_as_string() + stream = StringIO(compressed_content) + gz = gzip.GzipFile(fileobj=stream) + try: + content = gz.read() + except IOError: + content = None else: content = MFeedPage.get_data(feed_id=self.feed.pk) url = self._url_from_html(content) @@ -172,6 +200,9 @@ class IconImporter(object): def get_image_from_url(self, url): # print 'Requesting: %s' % url + if not url: + return None, None + @timelimit(30) def _1(url): try: diff --git a/apps/rss_feeds/migrations/0059_s3_pages_and_icons.py b/apps/rss_feeds/migrations/0059_s3_pages_and_icons.py new file mode 100644 index 000000000..2edfb15d3 --- /dev/null +++ b/apps/rss_feeds/migrations/0059_s3_pages_and_icons.py @@ -0,0 +1,97 @@ +# -*- coding: 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 'Feed.s3_page' + db.add_column('feeds', 's3_page', + self.gf('django.db.models.fields.NullBooleanField')(default=False, null=True, blank=True), + keep_default=False) + + # Adding field 'Feed.s3_icon' + db.add_column('feeds', 's3_icon', + self.gf('django.db.models.fields.NullBooleanField')(default=False, null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Feed.s3_page' + db.delete_column('feeds', 's3_page') + + # Deleting field 'Feed.s3_icon' + db.delete_column('feeds', 's3_icon') + + + models = { + 'rss_feeds.duplicatefeed': { + 'Meta': {'object_name': 'DuplicateFeed'}, + 'duplicate_address': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'duplicate_feed_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_index': 'True'}), + 'duplicate_link': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'db_index': 'True'}), + 'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'duplicate_addresses'", 'to': "orm['rss_feeds.Feed']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'rss_feeds.feed': { + 'Meta': {'ordering': "['feed_title']", 'object_name': 'Feed', 'db_table': "'feeds'"}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'active_premium_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1', 'db_index': 'True'}), + 'active_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1', 'db_index': 'True'}), + 'average_stories_per_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'branch_from_feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']", 'null': 'True', 'blank': 'True'}), + 'creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'days_to_trim': ('django.db.models.fields.IntegerField', [], {'default': '90'}), + 'errors_since_good': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'etag': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'exception_code': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'favicon_color': ('django.db.models.fields.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'favicon_not_found': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_address': ('django.db.models.fields.URLField', [], {'max_length': '255', 'db_index': 'True'}), + 'feed_address_locked': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), + 'feed_link': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '1000', 'null': 'True', 'blank': 'True'}), + 'feed_link_locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_title': ('django.db.models.fields.CharField', [], {'default': "'[Untitled]'", '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': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'has_page_exception': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'hash_address_and_link': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_push': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), + 'known_good': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': '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': '0'}), + 'next_scheduled_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'num_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1'}), + 'premium_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '-1'}), + 'queued_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 's3_icon': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), + 's3_page': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), + 'stories_last_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'rss_feeds.feeddata': { + 'Meta': {'object_name': 'FeedData'}, + 'feed': ('utils.fields.AutoOneToOneField', [], {'related_name': "'data'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}), + 'feed_classifier_counts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'feed_tagline': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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'}), + 'story_count_history': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + 'rss_feeds.feedloadtime': { + 'Meta': {'object_name': 'FeedLoadtime'}, + 'date_accessed': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'loadtime': ('django.db.models.fields.FloatField', [], {}) + } + } + + complete_apps = ['rss_feeds'] \ No newline at end of file diff --git a/apps/rss_feeds/models.py b/apps/rss_feeds/models.py index e30e2db10..d8f2ec696 100644 --- a/apps/rss_feeds/models.py +++ b/apps/rss_feeds/models.py @@ -29,8 +29,7 @@ from utils.feed_functions import levenshtein_distance from utils.feed_functions import timelimit, TimeoutError from utils.feed_functions import relative_timesince from utils.feed_functions import seconds_timesince -from utils.story_functions import strip_tags -from utils.diff import HTMLDiff +from utils.story_functions import strip_tags, htmldiff ENTRY_NEW, ENTRY_UPDATED, ENTRY_SAME, ENTRY_ERR = range(4) @@ -69,6 +68,8 @@ class Feed(models.Model): last_load_time = models.IntegerField(default=0) favicon_color = models.CharField(max_length=6, null=True, blank=True) favicon_not_found = models.BooleanField(default=False) + s3_page = models.NullBooleanField(default=False, blank=True, null=True) + s3_icon = models.NullBooleanField(default=False, blank=True, null=True) class Meta: db_table="feeds" @@ -87,14 +88,27 @@ class Feed(models.Model): @property def favicon_url(self): + if settings.BACKED_BY_AWS['icons_on_s3'] and self.s3_icon: + return "http://%s/%s.png" % (settings.S3_ICONS_BUCKET_NAME, self.pk) return reverse('feed-favicon', kwargs={'feed_id': self.pk}) @property def favicon_url_fqdn(self): + if settings.BACKED_BY_AWS['icons_on_s3'] and self.s3_icon: + return self.favicon_url return "http://%s%s" % ( Site.objects.get_current().domain, self.favicon_url ) + + @property + def s3_pages_key(self): + return "%s.gz.html" % self.pk + + @property + def s3_icons_key(self): + return "%s.png" % self.pk + def canonical(self, full=False, include_favicon=True): feed = { 'id': self.pk, @@ -114,6 +128,8 @@ class Feed(models.Model): 'favicon_text_color': self.favicon_text_color(), 'favicon_fetching': self.favicon_fetching, 'favicon_url': self.favicon_url, + 's3_page': self.s3_page, + 's3_icon': self.s3_icon, } if include_favicon: @@ -780,8 +796,7 @@ class Feed(models.Model): original_content = zlib.decompress(existing_story.story_content_z) # print 'Type: %s %s' % (type(original_content), type(story_content)) if story_content and len(story_content) > 10: - diff = HTMLDiff(unicode(original_content), story_content) - story_content_diff = diff.getDiff() + story_content_diff = htmldiff(unicode(original_content), unicode(story_content)) else: story_content_diff = original_content # logging.debug("\t\tDiff: %s %s %s" % diff.getStats()) @@ -1311,6 +1326,7 @@ class MStory(mongo.Document): 'index_drop_dups': True, 'ordering': ['-story_date'], 'allow_inheritance': False, + 'cascade': False, } @property diff --git a/apps/rss_feeds/page_importer.py b/apps/rss_feeds/page_importer.py index b3b2ef8f8..0ac2ee00c 100644 --- a/apps/rss_feeds/page_importer.py +++ b/apps/rss_feeds/page_importer.py @@ -6,6 +6,9 @@ import feedparser import time import urllib2 import httplib +import gzip +import StringIO +from boto.s3.key import Key from django.conf import settings from utils import log as logging from apps.rss_feeds.models import MFeedPage @@ -77,6 +80,9 @@ class PageImporter(object): response = requests.get(feed_link, headers=self.headers) except requests.exceptions.TooManyRedirects: response = requests.get(feed_link) + except AttributeError: + self.save_no_page() + return try: data = response.text except (LookupError, TypeError): @@ -169,10 +175,35 @@ class PageImporter(object): def save_page(self, html): if html and len(html) > 100: - feed_page, created = MFeedPage.objects.get_or_create(feed_id=self.feed.pk, auto_save=True) - feed_page.page_data = html - if not created: - feed_page.save() + if settings.BACKED_BY_AWS.get('pages_on_s3'): + k = Key(settings.S3_PAGES_BUCKET) + k.key = self.feed.s3_pages_key + k.set_metadata('Content-Encoding', 'gzip') + k.set_metadata('Content-Type', 'text/html') + k.set_metadata('Access-Control-Allow-Origin', '*') + out = StringIO.StringIO() + f = gzip.GzipFile(fileobj=out, mode='w') + f.write(html) + f.close() + compressed_html = out.getvalue() + k.set_contents_from_string(compressed_html) + k.set_acl('public-read') + + if not self.feed.s3_page: + try: + feed_page = MFeedPage.objects.get(feed_id=self.feed.pk) + feed_page.delete() + logging.debug(' --->> [%-30s] ~FYTransfering page data to S3...' % (self.feed)) + except MFeedPage.DoesNotExist: + pass + + self.feed.s3_page = True + self.feed.save() else: - feed_page.save(force_insert=True) - return feed_page + try: + feed_page = MFeedPage.objects.get(feed_id=self.feed.pk) + feed_page.page_data = html + feed_page.save() + except MFeedPage.DoesNotExist: + feed_page = MFeedPage.objects.create(feed_id=self.feed.pk, page_data=html) + return feed_page diff --git a/apps/social/models.py b/apps/social/models.py index 28876e238..d42607fae 100644 --- a/apps/social/models.py +++ b/apps/social/models.py @@ -449,7 +449,6 @@ class MSocialProfile(mongo.Document): logging.user(user, "~FMDisabled emails, skipping.") return if self.user_id == follower_user_id: - logging.user(user, "~FMDisabled emails, skipping.") return emails_sent = MSentEmail.objects.filter(receiver_user_id=user.pk, @@ -918,7 +917,7 @@ class MSocialSubscription(mongo.Document): read_stories = MUserStory.objects(user_id=self.user_id, feed_id__in=story_feed_ids, story_id__in=story_ids) - read_stories_ids = [rs.story_id for rs in read_stories] + read_stories_ids = list(set(rs.story_id for rs in read_stories)) oldest_unread_story_date = now unread_stories_db = [] @@ -953,7 +952,7 @@ class MSocialSubscription(mongo.Document): for story in stories: scores = { 'feed' : apply_classifier_feeds(classifier_feeds, story['story_feed_id'], - social_user_id=self.subscription_user_id), + social_user_ids=self.subscription_user_id), 'author' : apply_classifier_authors(classifier_authors, story), 'tags' : apply_classifier_tags(classifier_tags, story), 'title' : apply_classifier_titles(classifier_titles, story), diff --git a/apps/social/views.py b/apps/social/views.py index f4f9bb4dd..56c059b2e 100644 --- a/apps/social/views.py +++ b/apps/social/views.py @@ -145,7 +145,7 @@ def load_social_stories(request, user_id, username=None): story['intelligence'] = { 'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id'], - social_user_id=social_user_id), + social_user_ids=social_user_id), 'author': apply_classifier_authors(classifier_authors, story), 'tags': apply_classifier_tags(classifier_tags, story), 'title': apply_classifier_titles(classifier_titles, story), @@ -248,6 +248,8 @@ def load_river_blurblog(request): # Intelligence classifiers for all feeds involved if story_feed_ids: classifier_feeds = list(MClassifierFeed.objects(user_id=user.pk, + social_user_id__in=social_user_ids)) + classifier_feeds = classifier_feeds + list(MClassifierFeed.objects(user_id=user.pk, feed_id__in=story_feed_ids)) classifier_authors = list(MClassifierAuthor.objects(user_id=user.pk, feed_id__in=story_feed_ids)) @@ -260,11 +262,6 @@ def load_river_blurblog(request): classifier_authors = [] classifier_titles = [] classifier_tags = [] - classifiers = sort_classifiers_by_feed(user=user, feed_ids=story_feed_ids, - classifier_feeds=classifier_feeds, - classifier_authors=classifier_authors, - classifier_titles=classifier_titles, - classifier_tags=classifier_tags) # Just need to format stories for story in stories: @@ -282,7 +279,8 @@ def load_river_blurblog(request): starred_date = localtime_for_timezone(starred_stories[story['id']], user.profile.timezone) story['starred_date'] = format_story_link_date__long(starred_date, now) story['intelligence'] = { - 'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id']), + 'feed': apply_classifier_feeds(classifier_feeds, story['story_feed_id'], + social_user_ids=story['friend_user_ids']), 'author': apply_classifier_authors(classifier_authors, story), 'tags': apply_classifier_tags(classifier_tags, story), 'title': apply_classifier_titles(classifier_titles, story), @@ -294,6 +292,11 @@ def load_river_blurblog(request): story['shared_date'] = format_story_link_date__long(shared_date, now) story['shared_comments'] = strip_tags(shared_stories[story['id']]['comments']) + classifiers = sort_classifiers_by_feed(user=user, feed_ids=story_feed_ids, + classifier_feeds=classifier_feeds, + classifier_authors=classifier_authors, + classifier_titles=classifier_titles, + classifier_tags=classifier_tags) diff = time.time() - start timediff = round(float(diff), 2) diff --git a/apps/statistics/management/commands/collect_stats.py b/apps/statistics/management/commands/collect_stats.py index d16c11967..f70e34e21 100644 --- a/apps/statistics/management/commands/collect_stats.py +++ b/apps/statistics/management/commands/collect_stats.py @@ -8,5 +8,4 @@ class Command(BaseCommand): def handle(self, *args, **options): MStatistics.collect_statistics() - - MStatistics.delete_old_stats() \ No newline at end of file + \ No newline at end of file diff --git a/apps/statistics/models.py b/apps/statistics/models.py index 096811ed1..21e3f2673 100644 --- a/apps/statistics/models.py +++ b/apps/statistics/models.py @@ -1,10 +1,8 @@ import datetime import mongoengine as mongo import urllib2 -from django.db.models import Avg, Count from django.conf import settings from apps.rss_feeds.models import MFeedFetchHistory, MPageFetchHistory, MFeedPushHistory -from apps.rss_feeds.models import FeedLoadtime from apps.social.models import MSharedStory from apps.profile.models import Profile from utils import json_functions as json @@ -57,24 +55,22 @@ class MStatistics(mongo.Document): @classmethod def collect_statistics(cls): now = datetime.datetime.now() - last_day = datetime.datetime.now() - datetime.timedelta(hours=24) - cls.collect_statistics_feeds_fetched(last_day) + cls.collect_statistics_feeds_fetched() print "Feeds Fetched: %s" % (datetime.datetime.now() - now) - cls.collect_statistics_premium_users(last_day) + cls.collect_statistics_premium_users() print "Premiums: %s" % (datetime.datetime.now() - now) - cls.collect_statistics_standard_users(last_day) + cls.collect_statistics_standard_users() print "Standard users: %s" % (datetime.datetime.now() - now) - cls.collect_statistics_sites_loaded(last_day) + cls.collect_statistics_sites_loaded() print "Sites loaded: %s" % (datetime.datetime.now() - now) - cls.collect_statistics_stories_shared(last_day) + cls.collect_statistics_stories_shared() print "Stories shared: %s" % (datetime.datetime.now() - now) cls.collect_statistics_for_db() print "DB Stats: %s" % (datetime.datetime.now() - now) @classmethod - def collect_statistics_feeds_fetched(cls, last_day=None): - if not last_day: - last_day = datetime.datetime.now() - datetime.timedelta(hours=24) + def collect_statistics_feeds_fetched(cls): + last_day = datetime.datetime.now() - datetime.timedelta(hours=24) last_month = datetime.datetime.now() - datetime.timedelta(days=30) feeds_fetched = MFeedFetchHistory.objects.filter(fetch_date__gte=last_day).count() @@ -100,19 +96,17 @@ class MStatistics(mongo.Document): return feeds_fetched @classmethod - def collect_statistics_premium_users(cls, last_day=None): - if not last_day: - last_day = datetime.datetime.now() - datetime.timedelta(hours=24) - + def collect_statistics_premium_users(cls): + last_day = datetime.datetime.now() - datetime.timedelta(hours=24) + premium_users = Profile.objects.filter(last_seen_on__gte=last_day, is_premium=True).count() cls.objects(key='premium_users').update_one(upsert=True, set__key='premium_users', set__value=premium_users) return premium_users @classmethod - def collect_statistics_standard_users(cls, last_day=None): - if not last_day: - last_day = datetime.datetime.now() - datetime.timedelta(hours=24) + def collect_statistics_standard_users(cls): + last_day = datetime.datetime.now() - datetime.timedelta(hours=24) standard_users = Profile.objects.filter(last_seen_on__gte=last_day, is_premium=False).count() cls.objects(key='standard_users').update_one(upsert=True, set__key='standard_users', set__value=standard_users) @@ -120,9 +114,7 @@ class MStatistics(mongo.Document): return standard_users @classmethod - def collect_statistics_sites_loaded(cls, last_day=None): - if not last_day: - last_day = datetime.datetime.now() - datetime.timedelta(hours=24) + def collect_statistics_sites_loaded(cls): now = datetime.datetime.now() sites_loaded = [] avg_time_taken = [] @@ -130,13 +122,39 @@ class MStatistics(mongo.Document): for hour in range(24): start_hours_ago = now - datetime.timedelta(hours=hour) end_hours_ago = now - datetime.timedelta(hours=hour+1) - aggregates = dict(count=Count('loadtime'), avg=Avg('loadtime')) - load_times = FeedLoadtime.objects.filter( - date_accessed__lte=start_hours_ago, - date_accessed__gte=end_hours_ago - ).aggregate(**aggregates) - sites_loaded.append(load_times['count'] or 0) - avg_time_taken.append(load_times['avg'] or 0) + + load_times = settings.MONGOANALYTICSDB.nbanalytics.page_loads.aggregate([{ + "$match": { + "date": { + "$gte": end_hours_ago, + "$lte": start_hours_ago, + }, + "path": { + "$in": [ + "/reader/feed/", + "/social/stories/", + "/reader/river_stories/", + "/social/river_stories/", + ] + } + }, + }, { + "$group": { + "_id" : 1, + "count" : {"$sum": 1}, + "avg" : {"$avg": "$duration"}, + }, + }]) + + count = 0 + avg = 0 + if load_times['result']: + count = load_times['result'][0]['count'] + avg = load_times['result'][0]['avg'] + + sites_loaded.append(count) + avg_time_taken.append(avg) + sites_loaded.reverse() avg_time_taken.reverse() @@ -152,9 +170,7 @@ class MStatistics(mongo.Document): cls.objects(key=key).update_one(upsert=True, set__key=key, set__value=value) @classmethod - def collect_statistics_stories_shared(cls, last_day=None): - if not last_day: - last_day = datetime.datetime.now() - datetime.timedelta(hours=24) + def collect_statistics_stories_shared(cls): now = datetime.datetime.now() stories_shared = [] @@ -182,11 +198,6 @@ class MStatistics(mongo.Document): lag = db_functions.mongo_max_replication_lag(settings.MONGODB) cls.set('mongodb_replication_lag', lag) - @classmethod - def delete_old_stats(cls): - now = datetime.datetime.now() - old_age = now - datetime.timedelta(days=7) - FeedLoadtime.objects.filter(date_accessed__lte=old_age).delete() class MFeedback(mongo.Document): date = mongo.StringField() @@ -268,8 +279,21 @@ class MAnalyticsPageLoad(mongo.Document): @classmethod def clean_path(cls, path): - if path and path.startswith('/reader/feed/'): + if not path: + return + + if path.startswith('/reader/feed'): path = '/reader/feed/' + elif path.startswith('/social/stories'): + path = '/social/stories/' + elif path.startswith('/reader/river_stories'): + path = '/reader/river_stories/' + elif path.startswith('/social/river_stories'): + path = '/social/river_stories/' + elif path.startswith('/reader/page/'): + path = '/reader/page/' + elif path.startswith('/api/check_share_on_site'): + path = '/api/check_share_on_site/' return path @@ -326,6 +350,9 @@ class MAnalyticsFetcher(mongo.Document): @classmethod def add(cls, feed_id, feed_fetch, feed_process, page, icon, total, feed_code): + server_name = settings.SERVER_NAME + if 'app' in server_name: return + if icon and page: icon -= page if page and feed_process: @@ -334,7 +361,6 @@ class MAnalyticsFetcher(mongo.Document): page -= feed_fetch if feed_process and feed_fetch: feed_process -= feed_fetch - server_name = settings.SERVER_NAME cls.objects.create(feed_id=feed_id, feed_fetch=feed_fetch, feed_process=feed_process, diff --git a/assets.yml b/assets.yml index 49418fd7d..e55fa87da 100644 --- a/assets.yml +++ b/assets.yml @@ -50,7 +50,7 @@ javascripts: - media/js/vendor/jquery.tipsy.js - media/js/vendor/jquery.chosen.js - media/js/vendor/jquery.effects.core.js - - media/js/vendor/jquery.effects.slide.js + - media/js/vendor/jquery.effects.slideOffscreen.js # - media/js/vendor/jquery.linkify.js - media/js/vendor/bootstrap.*.js - media/js/vendor/audio.js @@ -65,7 +65,7 @@ javascripts: - media/js/newsblur/reader/reader_utils.js - media/js/newsblur/reader/reader.js - media/js/newsblur/reader/*.js - - media/js/newsblur/static/*.js + - media/js/newsblur/welcome/*.js mobile: - media/js/vendor/jquery-1.7.2.js - media/js/mobile/jquery.mobile-1.0b1.js diff --git a/config/hosts b/config/hosts index 3c5599103..fd6db5ee4 100644 --- a/config/hosts +++ b/config/hosts @@ -4,13 +4,12 @@ 199.15.250.229 app02 app02.newsblur.com push 199.15.252.156 app03 app03.newsblur.com dev 199.15.252.109 app04 app04.newsblur.com www -# 199.15.253.218 db01 db01.newsblur.com 199.15.249.101 db01 db01.newsblur.com 199.15.252.50 db02 db02.newsblur.com -199.15.253.226 db03 db03.newsblur.com +# 199.15.253.226 db03 db03.newsblur.com 199.15.249.98 db04 db04.newsblur.com 199.15.249.99 db05 db05.newsblur.com -199.15.249.100 db06 db06.newsblur.com + 199.15.249.101 db07 db07.newsblur.com 199.15.250.231 task01 task01.newsblur.com 199.15.250.250 task02 task02.newsblur.com @@ -19,3 +18,7 @@ 199.15.252.106 task05 task05.newsblur.com 199.15.252.107 task06 task06.newsblur.com 199.15.252.108 task07 task07.newsblur.com +199.15.251.144 task08 task08.newsblur.com +199.15.251.154 task09 task09.newsblur.com +199.15.251.137 task10 task10.newsblur.com +199.15.251.155 task11 task11.newsblur.com diff --git a/config/mongodb.ec2.conf b/config/mongodb.ec2.conf new file mode 100644 index 000000000..3d2f441f3 --- /dev/null +++ b/config/mongodb.ec2.conf @@ -0,0 +1,90 @@ +# mongodb.conf + +# Where to store the data. + +# Note: if you run mongodb as a non-root user (recommended) you may +# need to create and set permissions for this directory manually, +# e.g., if the parent directory isn't mutable by the mongodb user. +dbpath=/srv/db/mongodb + +#where to log +logpath=/var/log/mongodb/mongodb.log + +logappend=true + +#port = 27017 + +slowms=100 + +rest = true +#profile = 2 +# Enables periodic logging of CPU utilization and I/O wait +#cpu = true + +# Turn on/off security. Off is currently the default +noauth = true +#auth = true + +# Verbose logging output. +#verbose = true + +# Inspect all client data for validity on receipt (useful for +# developing drivers) +#objcheck = true + +# Enable db quota management +#quota = true + +# Set oplogging level where n is +# 0=off (default) +# 1=W +# 2=R +# 3=both +# 7=W+some reads +#diaglog = 0 + +# Diagnostic/debugging option +#nocursors = true + +# Ignore query hints +#nohints = true + +# Disable the HTTP interface (Defaults to localhost:27018). +#nohttpinterface = true + +# Turns off server-side scripting. This will result in greatly limited +# functionality +#noscripting = true + +# Turns off table scans. Any query that would do a table scan fails. +#notablescan = true + +# Disable data file preallocation. +#noprealloc = true + +# Specify .ns file size for new databases. +# nssize = + +# Accout token for Mongo monitoring server. +#mms-token = + +# Server name for Mongo monitoring server. +#mms-name = + +# Ping interval for Mongo monitoring server. +#mms-interval = + +# Replication Options + +# in master/slave replicated mongo databases, specify here whether +# this is a slave or master +#slave = true +#source = master.example.com +# Slave only: specify a single database to replicate +#only = master.example.com +# or +#master = true +#source = slave.example.com + +# in replica set configuration, specify the name of the replica set +replSet = nbset diff --git a/config/mongodb.prod.conf b/config/mongodb.prod.conf index afd08c4a1..dc0520d41 100644 --- a/config/mongodb.prod.conf +++ b/config/mongodb.prod.conf @@ -8,7 +8,7 @@ dbpath=/var/lib/mongodb #where to log -logpath=/var/log/mongodb +logpath=/var/log/mongodb/mongodb.log logappend=true diff --git a/fabfile.py b/fabfile.py index 039515631..fa034c9f1 100644 --- a/fabfile.py +++ b/fabfile.py @@ -34,31 +34,43 @@ env.roledefs ={ 'app': ['app01.newsblur.com', 'app02.newsblur.com', 'app03.newsblur.com', - 'app04.newsblur.com'], + 'app04.newsblur.com', + ], 'dev': ['dev.newsblur.com'], 'web': ['app01.newsblur.com', 'app02.newsblur.com', - 'app04.newsblur.com'], + 'app04.newsblur.com', + ], 'db': ['db01.newsblur.com', 'db02.newsblur.com', 'db03.newsblur.com', 'db04.newsblur.com', - 'db05.newsblur.com', - 'db06.newsblur.com'], + 'db05.newsblur.com', + ], 'task': ['task01.newsblur.com', 'task02.newsblur.com', 'task03.newsblur.com', 'task04.newsblur.com', 'task05.newsblur.com', 'task06.newsblur.com', - 'task07.newsblur.com'], + 'task07.newsblur.com', + 'task08.newsblur.com', + 'task09.newsblur.com', + 'task10.newsblur.com', + 'task11.newsblur.com', + ], 'vps': ['task01.newsblur.com', 'task02.newsblur.com', 'task03.newsblur.com', 'task04.newsblur.com', + 'task08.newsblur.com', + 'task09.newsblur.com', + 'task10.newsblur.com', + 'task11.newsblur.com', 'app01.newsblur.com', 'app02.newsblur.com', - 'app03.newsblur.com'], + 'app03.newsblur.com', + ], } # ================ @@ -66,8 +78,8 @@ env.roledefs ={ # ================ def server(): - env.NEWSBLUR_PATH = "/home/sclay/newsblur" - env.VENDOR_PATH = "/home/sclay/code" + env.NEWSBLUR_PATH = "/home/%s/newsblur" % env.user + env.VENDOR_PATH = "/home/%s/code" % env.user def app(): server() @@ -92,6 +104,11 @@ def task(): def vps(): server() env.roles = ['vps'] + +def ec2(): + env.user = 'ubuntu' + env.key_filename = ['/Users/sclay/.ec2/sclay.pem'] + server() # ========== # = Deploy = @@ -263,7 +280,6 @@ def setup_app(): setup_app_firewall() setup_app_motd() copy_app_settings() - copy_certificates() configure_nginx() setup_gunicorn(supervisor=True) update_gunicorn() @@ -278,12 +294,15 @@ def setup_db(): setup_db_firewall() setup_db_motd() copy_task_settings() - setup_memcached() - setup_postgres(standby=False) - # setup_mongo() + # setup_memcached() + # setup_postgres(standby=False) + setup_mongo() setup_gunicorn(supervisor=False) - setup_redis() + # setup_redis() setup_db_munin() + + if env.user == 'ubuntu': + setup_db_mdadm() def setup_task(): setup_common() @@ -318,14 +337,15 @@ def setup_installs(): run('mkdir -p %s' % env.VENDOR_PATH) def setup_user(): - # run('useradd -c "NewsBlur" -m conesus -s /bin/zsh') + # run('useradd -c "NewsBlur" -m newsblur -s /bin/zsh') # run('openssl rand -base64 8 | tee -a ~conesus/.password | passwd -stdin conesus') run('mkdir -p ~/.ssh && chmod 700 ~/.ssh') run('rm -fr ~/.ssh/id_dsa*') run('ssh-keygen -t dsa -f ~/.ssh/id_dsa -N ""') run('touch ~/.ssh/authorized_keys') put("~/.ssh/id_dsa.pub", "authorized_keys") - run('mv authorized_keys ~/.ssh/') + run('echo `cat authorized_keys` >> ~/.ssh/authorized_keys') + run('rm authorized_keys') def add_machine_to_ssh(): put("~/.ssh/id_dsa.pub", "local_keys") @@ -371,7 +391,7 @@ def setup_psycopg(): def setup_python(): # sudo('easy_install -U pip') - sudo('easy_install -U fabric django==1.3.1 readline pyflakes iconv celery django-celery django-celery-with-redis django-compress South django-extensions pymongo==2.2.0 stripe BeautifulSoup pyyaml nltk lxml oauth2 pytz boto seacucumber django_ses mongoengine redis requests django-subdomains psutil python-gflags') + sudo('easy_install -U fabric django==1.3.1 readline pyflakes iconv celery django-celery django-celery-with-redis django-compress South django-extensions pymongo==2.2.0 stripe BeautifulSoup pyyaml nltk lxml oauth2 pytz boto seacucumber django_ses mongoengine redis requests django-subdomains psutil python-gflags cssutils') put('config/pystartup.py', '.pystartup') # with cd(os.path.join(env.NEWSBLUR_PATH, 'vendor/cjson')): @@ -392,11 +412,12 @@ def setup_hosts(): def config_pgbouncer(): put('config/pgbouncer.conf', '/etc/pgbouncer/pgbouncer.ini', use_sudo=True) - put('config/pgbouncer_userlist.txt', '/etc/pgbouncer/userlist.txt', use_sudo=True) + # put('config/pgbouncer_userlist.txt', '/etc/pgbouncer/userlist.txt', use_sudo=True) + put('config/secrets/pgbouncer_auth.conf', '/etc/pgbouncer/userlist.txt', use_sudo=True) sudo('echo "START=1" > /etc/default/pgbouncer') sudo('su postgres -c "/etc/init.d/pgbouncer stop"', pty=False) with settings(warn_only=True): - sudo('pkill pgbouncer') + sudo('pkill -9 pgbouncer') run('sleep 2') sudo('/etc/init.d/pgbouncer start', pty=False) @@ -442,15 +463,15 @@ def setup_forked_mongoengine(): with settings(warn_only=True): run('git checkout master') run('git branch -D dev') - run('git remote add sclay git://github.com/samuelclay/mongoengine.git') - run('git fetch sclay') - run('git checkout -b dev sclay/dev') - run('git pull sclay dev') + run('git remote add %s git://github.com/samuelclay/mongoengine.git' % env.user) + run('git fetch %s' % env.user) + run('git checkout -b dev %s/dev' % env.user) + run('git pull %s dev' % env.user) def switch_forked_mongoengine(): with cd(os.path.join(env.VENDOR_PATH, 'mongoengine')): run('git co dev') - run('git pull sclay dev --force') + run('git pull %s dev --force' % env.user) # run('git checkout .') # run('git checkout master') # run('get branch -D dev') @@ -485,6 +506,7 @@ def configure_nginx(): sudo("chmod 0755 /etc/init.d/nginx") sudo("/usr/sbin/update-rc.d -f nginx defaults") sudo("/etc/init.d/nginx restart") + copy_certificates() def setup_vps(): # VPS suffer from severe time drift. Force blunt hourly time recalibration. @@ -575,9 +597,18 @@ def setup_db_firewall(): sudo('ufw allow from 199.15.248.0/21 to any port 5432 ') # PostgreSQL sudo('ufw allow from 199.15.248.0/21 to any port 27017') # MongoDB sudo('ufw allow from 199.15.248.0/21 to any port 28017') # MongoDB web - # sudo('ufw allow from 199.15.248.0/21 to any port 5672 ') # RabbitMQ sudo('ufw allow from 199.15.248.0/21 to any port 6379 ') # Redis sudo('ufw allow from 199.15.248.0/21 to any port 11211 ') # Memcached + + # EC2 + sudo('ufw delete allow from 23.22.0.0/16 to any port 5432 ') # PostgreSQL + sudo('ufw delete allow from 23.22.0.0/16 to any port 27017') # MongoDB + sudo('ufw delete allow from 23.22.0.0/16 to any port 6379 ') # Redis + sudo('ufw delete allow from 23.22.0.0/16 to any port 11211 ') # Memcached + sudo('ufw allow from 23.20.0.0/16 to any port 5432 ') # PostgreSQL + sudo('ufw allow from 23.20.0.0/16 to any port 27017') # MongoDB + sudo('ufw allow from 23.20.0.0/16 to any port 6379 ') # Redis + sudo('ufw allow from 23.20.0.0/16 to any port 11211 ') # Memcached sudo('ufw --force enable') def setup_db_motd(): @@ -628,7 +659,8 @@ def setup_mongo(): sudo('echo "deb http://downloads-distro.mongodb.org/repo/debian-sysvinit dist 10gen" >> /etc/apt/sources.list') sudo('apt-get update') sudo('apt-get -y install mongodb-10gen') - put('config/mongodb.prod.conf', '/etc/mongodb.conf', use_sudo=True) + put('config/mongodb.%s.conf' % ('prod' if env.user != 'ubuntu' else 'ec2'), + '/etc/mongodb.conf', use_sudo=True) sudo('/etc/init.d/mongodb restart') def setup_redis(): @@ -651,8 +683,10 @@ def setup_db_munin(): sudo('cp -frs %s/config/munin/mongo* /etc/munin/plugins/' % env.NEWSBLUR_PATH) sudo('cp -frs %s/config/munin/pg_* /etc/munin/plugins/' % env.NEWSBLUR_PATH) with cd(env.VENDOR_PATH): - run('git clone git://github.com/samuel/python-munin.git') - run('sudo python python-munin/setup.py install') + with settings(warn_only=True): + run('git clone git://github.com/samuel/python-munin.git') + with cd(os.path.join(env.VENDOR_PATH, 'python-munin')): + run('sudo python setup.py install') def enable_celerybeat(): with cd(env.NEWSBLUR_PATH): @@ -662,6 +696,19 @@ def enable_celerybeat(): sudo('supervisorctl reread') sudo('supervisorctl update') +def setup_db_mdadm(): + sudo('apt-get -y install xfsprogs mdadm') + sudo('yes | mdadm --create /dev/md0 --level=0 -c256 --raid-devices=4 /dev/xvdf /dev/xvdg /dev/xvdh /dev/xvdi') + sudo('mkfs.xfs /dev/md0') + sudo('mkdir -p /srv/db') + sudo('mount -t xfs -o rw,nobarrier,noatime,nodiratime /dev/md0 /srv/db') + sudo('mkdir -p /srv/db/mongodb') + sudo('chown mongodb.mongodb /srv/db/mongodb') + sudo("echo 'DEVICE /dev/xvdf /dev/xvdg /dev/xvdh /dev/xvdi' | sudo tee -a /etc/mdadm/mdadm.conf") + sudo("mdadm --examine --scan | sudo tee -a /etc/mdadm/mdadm.conf") + sudo("echo '/dev/md0 /srv/db xfs rw,nobarrier,noatime,nodiratime,noauto 0 0' | sudo tee -a /etc/fstab") + sudo("sudo update-initramfs -u -v -k `uname -r`") + # ================ # = Setup - Task = # ================ @@ -697,7 +744,7 @@ def restore_postgres(port=5432): def restore_mongo(): backup_date = '2012-07-24-09-00' - run('PYTHONPATH=/home/sclay/newsblur python s3.py get backup_mongo_%s.tgz' % backup_date) + run('PYTHONPATH=/home/%s/newsblur python s3.py get backup_mongo_%s.tgz' % (env.user, backup_date)) run('tar -xf backup_mongo_%s.tgz' % backup_date) run('mongorestore backup_mongo_%s' % backup_date) diff --git a/local_settings.py.template b/local_settings.py.template index 9e2e982b9..a04add561 100644 --- a/local_settings.py.template +++ b/local_settings.py.template @@ -39,6 +39,8 @@ OAUTH_SECRET = 'SECRET_KEY_FROM_GOOGLE' S3_ACCESS_KEY = 'XXX' S3_SECRET = 'SECRET' S3_BACKUP_BUCKET = 'newsblur_backups' +S3_PAGES_BUCKET_NAME = 'pages-XXX.newsblur.com' +S3_ICONS_BUCKET_NAME = 'icons-XXX.newsblur.com' STRIPE_SECRET = "YOUR-SECRET-API-KEY" STRIPE_PUBLISHABLE = "YOUR-PUBLISHABLE-API-KEY" @@ -76,19 +78,19 @@ MONGODB_SLAVE = { 'host': '127.0.0.1' } -# Celery RabbitMQ Broker -BROKER_HOST = "127.0.0.1" +# Celery RabbitMQ/Redis Broker +CELERY_REDIS_HOST = "127.0.0.1" +BROKER_URL = "redis://127.0.0.1:6379/0" REDIS = { 'host': '127.0.0.1', } -# AMQP - RabbitMQ server -BROKER_HOST = "db01.newsblur.com" -BROKER_PORT = 5672 -BROKER_USER = "newsblur" -BROKER_PASSWORD = "newsblur" -BROKER_VHOST = "newsblurvhost" +BACKED_BY_AWS = { + 'pages_on_s3': False, + 'icons_on_s3': False, + 'stories_on_dynamodb': False, +} # =========== # = Logging = diff --git a/media/css/modals.css b/media/css/modals.css index b95769531..21fb095a8 100644 --- a/media/css/modals.css +++ b/media/css/modals.css @@ -25,9 +25,9 @@ padding: 2px; 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; + -moz-box-shadow:2px 2px 0 rgba(50, 50, 50, 0.15); + -webkit-box-shadow:2px 2px 0 rgba(50, 50, 50, 0.15); + box-shadow:2px 2px 0 rgba(50, 50, 50, 0.15); } /* ========== */ @@ -117,9 +117,9 @@ padding: 2px; margin: 0px 4px 6px; border: 1px solid #606060; - -moz-box-shadow:2px 2px 0 #D0D0D0; - -webkit-box-shadow:2px 2px 0 #D0D0D0; - box-shadow:2px 2px 0 #D0D0D0; + -moz-box-shadow:2px 2px 0 rgba(50, 50, 50, 0.15); + -webkit-box-shadow:2px 2px 0 rgba(50, 50, 50, 0.15); + box-shadow:2px 2px 0 rgba(50, 50, 50, 0.15); } .NB-modal .NB-modal-field input[type=checkbox] { @@ -205,9 +205,9 @@ 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; + -moz-box-shadow:2px 2px 0 rgba(50, 50, 50, 0.15); + -webkit-box-shadow:2px 2px 0 rgba(50, 50, 50, 0.15); + box-shadow:2px 2px 0 rgba(50, 50, 50, 0.15); border-radius: 4px; -moz-border-radius: 4px; cursor: pointer; diff --git a/media/css/reader.css b/media/css/reader.css index 6abed7dc9..a57a2118c 100644 --- a/media/css/reader.css +++ b/media/css/reader.css @@ -16,7 +16,12 @@ body { background-color: white; -webkit-overflow-scrolling: touch; } - +.NB-layout { + overflow: hidden; + height: 100%; + width: 100%; + position: absolute; +} a, a:active, a:hover, a:visited, button { outline: none; } @@ -367,7 +372,7 @@ body.NB-theme-serif #story_pane .NB-feed-story-content { border-bottom: 1px solid #A0A0A0; } .NB-feedlists .NB-socialfeeds .feed .feed_title { - text-shadow: 0 1px 0 #DAE2E8; + text-shadow: 0 1px 0 rgba(250, 250, 250, .4); } .NB-feedlists .NB-socialfeeds img.feed_favicon { border-radius: 3px; @@ -574,12 +579,11 @@ body.NB-theme-serif #story_pane .NB-feed-story-content { line-height: 1.3em; height: 14px; overflow: hidden; - text-shadow: 0 1px 0 #EBF3FA; + text-shadow: 0 1px 0 rgba(250, 250, 250, .4); } .NB-feedlist .feed.selected .feed_title, .NB-feedlist .feed.NB-selected .feed_title { - text-shadow: 0 1px 0 #FFC97D; color: #000000; } @@ -625,41 +629,50 @@ body.NB-theme-serif #story_pane .NB-feed-story-content { display: block; } -.NB-feedlist .folder .folder_title .feed_counts_floater { +.NB-feedlists .folder .folder_title .feed_counts_floater, +.NB-feeds-header .feed_counts_floater { margin-right: 2px; } -.NB-feedlist .folder .folder_title .NB-feedlist-collapse-icon { +.NB-feedlist .folder .folder_title .NB-feedlist-collapse-icon, +.NB-feeds-header .NB-feedlist-collapse-icon { position: absolute; top: 3px; right: 4px; width: 16px; height: 16px; - background: transparent url('/media/embed/icons/silk/toggle_minus.png') no-repeat 0 0; + background: transparent url('/media/embed/reader/toggle_minus.png') no-repeat 0 0; + background-size: 16px; opacity: .6; display: none; } -.NB-feedlist .folder.NB-folder-collapsed .folder_title .NB-feedlist-collapse-icon { - background-image: url('/media/embed/icons/silk/toggle_plus.png'); +.NB-feedlist .folder.NB-folder-collapsed .folder_title .NB-feedlist-collapse-icon, +.NB-feeds-header.NB-folder-collapsed .NB-feedlist-collapse-icon { + background-image: url('/media/embed/reader/toggle_plus.png'); + background-size: 16px; } -.NB-feedlist .folder .folder_title:hover .NB-feedlist-collapse-icon:hover { +.NB-feedlist .folder .folder_title:hover .NB-feedlist-collapse-icon:hover, +.NB-feeds-header:hover .NB-feedlist-collapse-icon:hover { opacity: 1; } -.NB-feedlist .folder .folder_title:hover .NB-feedlist-collapse-icon { +.NB-feedlist .folder .folder_title:hover .NB-feedlist-collapse-icon, +.NB-feeds-header:hover .NB-feedlist-collapse-icon { /*.NB-feedlist .folder.NB-showing-menu > .folder_title .NB-feedlist-collapse-icon {*/ display: block; opacity: .6; } .NB-feedlist .folder .folder_title:hover .feed_counts_floater, +.NB-feeds-header:hover .feed_counts_floater, .NB-feedlist .folder.NB-showing-menu > .folder_title .feed_counts_floater { margin-right: 24px; } -.NB-feedlist .folder .folder_title.NB-feedlist-folder-title-recently-collapsed:hover .feed_counts_floater { +.NB-feedlist .folder .folder_title.NB-feedlist-folder-title-recently-collapsed:hover .feed_counts_floater, +.NB-feeds-header.NB-feedlist-folder-title-recently-collapsed:hover .feed_counts_floater { display: block; } @@ -688,17 +701,29 @@ body.NB-theme-serif #story_pane .NB-feed-story-content { display: none; } .NB-feedlist .feed.selected, -.NB-feedlist .feed.NB-selected { - background: #f6a828 url('/media/css/jquery-ui/images/ui-bg_highlight-hard_35_f6a828_1x100.png') 0 50% repeat-x; - border-top: 1px solid #A8A8A8; - border-bottom: 1px solid #A8A8A8; +.NB-feedlist .feed.NB-selected, +.NB-feeds-header.NB-selected { + background-color: #F6A828; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#F7BA55), to(#F6A828)); + background: -moz-linear-gradient(center top , #F7BA55 0%, #F6A828 100%); + border-top: 1px solid #C59977; + border-bottom: 1px solid #C59977; } .NB-feedlist .folder.NB-selected > .folder_title { - background: #f6a828 url('/media/css/jquery-ui/images/ui-bg_highlight-hard_35_f6a828_1x100.png') 0 50% repeat-x; - border-top: 1px solid #A8A8A8; - border-bottom: 1px solid #A8A8A8; + background-color: #F6A828; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#F7BA55), to(#F6A828)); + background: -moz-linear-gradient(center top , #F7BA55 0%, #F6A828 100%); + border-top: 1px solid #C59977; + border-bottom: 1px solid #C59977; text-shadow: 1px 1px 0 #FAC898; } +.NB-feedlist .feed.NB-feed-selector-selected { + background-color: #7AC0FE; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#A7D3FE), to(#7AC0FE)); + background: -moz-linear-gradient(center top , #A7D3FE 0%, #7AC0FE 100%); + border-top: 1px solid #789FC6; + border-bottom: 1px solid #789FC6; +} .NB-feedlist .NB-feeds-list-highlight { @@ -715,29 +740,31 @@ body.NB-theme-serif #story_pane .NB-feed-story-content { border: none !important; } -.NB-feedlist .folder_title .unread_count { +.NB-feedlists .folder_title .unread_count { margin-top: -3px; line-height: 20px; + text-shadow: none; } - -.NB-feedlist .folder_title .unread_count { - text-shadow: none; +.NB-feeds-header .unread_count { + margin-top: 2px; + line-height: 18px; + text-shadow: none; } .NB-feedlist-hide-read-feeds .NB-feedlist .feed { display: none; } -.NB-feedlist-hide-read-feeds .NB-feedlist.unread_view_positive .unread_positive { +.NB-feedlist-hide-read-feeds .NB-sidebar.unread_view_positive .unread_positive { display: block; } -.NB-feedlist-hide-read-feeds .NB-feedlist.unread_view_neutral .unread_positive, -.NB-feedlist-hide-read-feeds .NB-feedlist.unread_view_neutral .unread_neutral { +.NB-feedlist-hide-read-feeds .NB-sidebar.unread_view_neutral .unread_positive, +.NB-feedlist-hide-read-feeds .NB-sidebar.unread_view_neutral .unread_neutral { display: block; } -.NB-feedlist-hide-read-feeds .NB-feedlist.unread_view_negative .unread_positive, -.NB-feedlist-hide-read-feeds .NB-feedlist.unread_view_negative .unread_neutral, -.NB-feedlist-hide-read-feeds .NB-feedlist.unread_view_negative .unread_negative { +.NB-feedlist-hide-read-feeds .NB-sidebar.unread_view_negative .unread_positive, +.NB-feedlist-hide-read-feeds .NB-sidebar.unread_view_negative .unread_neutral, +.NB-feedlist-hide-read-feeds .NB-sidebar.unread_view_negative .unread_negative { display: block; } @@ -747,6 +774,20 @@ body.NB-theme-serif #story_pane .NB-feed-story-content { .NB-feedlist-hide-read-feeds .NB-feedlist .feed.selected { display: block; } +#feed_list.NB-feedlist.NB-selector-active .feed, +.NB-sidebar .NB-socialfeeds-folder.NB-selector-active .feed { + display: none; +} +#feed_list.NB-feedlist.NB-selector-active .feed.NB-feed-selector-active, +.NB-socialfeeds-folder.NB-selector-active .feed.NB-feed-selector-active { + display: block; + opacity: 1; +} +.NB-feedlist.NB-selector-active .NB-folder-collapsed .folder, +.NB-socialfeeds-folder.NB-selector-active { + display: block !important; + opacity: 1 !important; +} /* ================= */ /* = Unread Counts = */ @@ -1183,7 +1224,8 @@ background: transparent; } #story_titles .NB-feedbar .folder ul.folder, -#story_titles .NB-feedbar .folder .NB-feedlist-collapse-icon { +#story_titles .NB-feedbar .folder .NB-feedlist-collapse-icon, +.NB-feeds-header .NB-feedlist-collapse-icon { display: none; } @@ -1291,6 +1333,7 @@ background: transparent; color: #272727; line-height: 1em; background-color: white; + border-bottom: 1px solid #FFF; } .NB-story-pane-west #story_titles .story { padding-right: 4px; @@ -1307,7 +1350,6 @@ background: transparent; height: 20px; left: 0; top: 0; -/* background: transparent url('/media/embed/icons/silk/bullet_orange.png') no-repeat 6px 2px;*/ } #story_titles .story.NB-story-positive .NB-storytitles-sentiment { @@ -1523,11 +1565,11 @@ background: transparent; } #story_titles .story.NB-selected { color: #304080; - border-top: 1px solid #D7DDE6; - background: #dadada url('/media/css/jquery-ui/images/dadada_40x100_textures_03_highlight_soft_75.png') 0 50% repeat-x; -} -#story_titles .story.after_selected { - border-top: 1px solid #D7DDE6; + border-top: 1px solid #6EADF5; + border-bottom: 1px solid #6EADF5; + background-color: #D2E6FD; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#E9EFF5), to(#E2EEFB)); + background: -moz-linear-gradient(center top , #E9EFF5 0%, #E2EEFB 100%); } #story_titles .NB-story-titles-end-stories-line { @@ -2041,19 +2083,25 @@ background: transparent; #story_pane .NB-feed-story-view-narrow .NB-feed-story-content { margin-right: 28px; } -#story_pane .NB-feed-story-content ins { +.NB-modal-preferences ins, +#story_pane .NB-feed-story-content ins, +.NB-pref-hide-changes #story_pane .NB-story-show-changes .NB-feed-story-content ins { text-decoration: underline; - color: #27452D; + color: #27652F; } -#story_pane .NB-feed-story-content del { +.NB-modal-preferences del, +#story_pane .NB-feed-story-content del, +.NB-pref-hide-changes #story_pane .NB-story-show-changes .NB-feed-story-content del { display: inline; - color: #661616; + color: #861616; } -.NB-pref-hide-changes #story_pane .NB-feed-story-content ins { +.NB-pref-hide-changes #story_pane .NB-feed-story-content ins, +#story_pane .NB-story-hide-changes .NB-feed-story-content ins { text-decoration: none; color: inherit; } -.NB-pref-hide-changes #story_pane .NB-feed-story-content del { +.NB-pref-hide-changes #story_pane .NB-feed-story-content del, +#story_pane .NB-story-hide-changes .NB-feed-story-content del { display: none; } #story_pane .NB-feed-story-comments { @@ -2729,6 +2777,9 @@ background: transparent; display: block; font-size: 20px; } +.NB-is-anonymous .content-pane .feed_counts_floater { + display: none; +} .content-pane .feed_counts_floater .unread_count { float: right; @@ -2833,7 +2884,7 @@ background: transparent; .NB-feeds-header-container { position: relative; - height: 20px; + height: 24px; display: none; overflow: hidden; } @@ -2844,22 +2895,17 @@ background: transparent; bottom: 0; left: 0; width: 100%; - height: 18px; + height: 22px; border-top: 1px solid #303030; border-bottom: 1px solid #E9E9E9; - padding-right: 2px; font-size: 10px; cursor: pointer; overflow: hidden; } -.NB-feeds-header.NB-selected { - background: #f6a828 url('/media/css/jquery-ui/images/ui-bg_highlight-hard_35_f6a828_1x100.png') 0 50% repeat-x; -} - .NB-feeds-header .NB-feeds-header-icon { position: absolute; - top: 1px; + top: 3px; left: 2px; width: 16px; height: 16px; @@ -2870,8 +2916,8 @@ background: transparent; padding: 0 40px 2px 23px; text-decoration: none; color: #F0F0F0; - line-height: 18px; - height: 14px; + line-height: 22px; + height: 22px; overflow: hidden; text-shadow: 0 1px 0 #060607; text-transform: uppercase; @@ -2881,6 +2927,10 @@ background: transparent; color: #C1C1C1; } +.NB-feeds-header.NB-selected { + border-top: 1px solid #303030; +} + .NB-feeds-header.NB-selected .NB-feeds-header-title { text-shadow: 0 1px 0 #FFC97D; color: #000000; @@ -3158,7 +3208,8 @@ background: transparent; overflow: hidden; } #story_taskbar .NB-tryfeed-add, -#story_taskbar .NB-tryfeed-follow { +#story_taskbar .NB-tryfeed-follow, +#story_taskbar .NB-tryout-signup { margin: 2px auto 0px; width: 60px; height: 14px; @@ -3229,10 +3280,6 @@ background: transparent; left: 12px; background: transparent url('/media/embed/icons/silk/arrow_down.png') no-repeat 0 0; } -.NB-taskbar .task_button.task_button_signup .NB-task-image { - left: 12px; - background: transparent url('/media/embed/icons/media-devices/imac.png') no-repeat 0 0; -} .NB-taskbar .task_button_background { z-index: 0; @@ -3388,7 +3435,7 @@ form.opml_import_form input { /* =============== */ #NB-splash { - z-index: 0; + z-index: 1; position: absolute; top: 0; left: 0; @@ -3466,6 +3513,7 @@ form.opml_import_form input { .NB-splash-info .NB-splash-links .NB-splash-link { display: block; + overflow: hidden; line-height: 12px; margin: 0; float: left; @@ -3490,6 +3538,7 @@ form.opml_import_form input { } .NB-splash-info .NB-splash-links .NB-splash-link.NB-splash-link-logo a { padding-top: 0; + padding-bottom: 0; margin-top: -1px; } @@ -4469,6 +4518,9 @@ form.opml_import_form input { .NB-account .NB-module.NB-module-account .NB-module-account-stats { min-height: 0; } +.NB-account .NB-module.NB-module-account .NB-module-stats-counts { + overflow: hidden; +} .NB-account .NB-module.NB-module-account .NB-module-stats-count { margin: 0; } @@ -8626,3 +8678,32 @@ form.opml_import_form input { .NB-interaction-sharedstory-content { cursor: pointer; } + +/* ================= */ +/* = Feed Selector = */ +/* ================= */ + +.NB-feeds-selector { + display: none; + background-color: #434343; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#434343), to(#696969)); + background: -moz-linear-gradient(center top , #4E4E4E 0%, #696969 100%); + border-bottom: 1px solid #E9E9E9; + overflow: hidden; +} +.NB-feeds-selector-input { + border-radius: 16px; + border: 1px solid #5F5F5F; + font-weight: bold; + width: 88%; + padding: 1px 8px; + margin: 4px auto; + display: block; + font-size: 11px; + outline: none; +} + +.NB-feeds-selector-input:focus { + border: 1px solid #5F5F5F; + box-shadow: 0 1px 0 #B1B1B1; +} \ No newline at end of file diff --git a/media/css/social/social_page.css b/media/css/social/social_page.css index 36bdb1dd0..f4e658642 100644 --- a/media/css/social/social_page.css +++ b/media/css/social/social_page.css @@ -401,21 +401,13 @@ header { .NB-story-date { position: absolute; - right: 24px; + right: 28px; top: 12px; font-size: 15px; font-weight: bold; - color: silver; + color: #D0D4D7; text-transform: uppercase; } -@media all and (max-width: 800px) { - .NB-story-date { - top: 6px; - } - .NB-story-date-break { - display: block; - } -} @media all and (max-width: 500px) { .NB-story-date { position: static; @@ -971,7 +963,6 @@ header { } .NB-story-share-label { display: inline-block; - margin: 0 4px 0 0; } .NB-story-share-profiles { display: inline-block; diff --git a/media/css/welcome.css b/media/css/welcome.css new file mode 100644 index 000000000..0ae110444 --- /dev/null +++ b/media/css/welcome.css @@ -0,0 +1,601 @@ +.NB-welcome .NB-body-inner { + overflow: auto; + height: 100%; + width: 100%; + position: absolute; +} +.NB-welcome .NB-splash-info.NB-splash-top .NB-splash-title { + display: none; +} +.NB-welcome .NB-splash-info.NB-splash-top, +.NB-welcome .NB-splash-info.NB-splash-bottom { + height: 6px; + position: absolute; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#4B638C), to(#23364D)); + background: -moz-linear-gradient(center top , #4B638C 0%, #23364D 100%); + border-bottom: 1px solid #000D41; + border-top: none; +} +.NB-welcome .NB-splash-info.NB-splash-bottom { + position: static; +} +.NB-welcome .NB-splash-info.NB-splash-bottom .NB-splash-links { + display: none; +} + +.NB-welcome .NB-taskbar-sidebar-toggle-open { + display: none; +} + +/* ========== */ +/* = Common = */ +/* ========== */ + +.NB-welcome .NB-inner { + width: 960px; + margin: 0 auto; + position: relative; + height: 100%; +} +.NB-welcome .NB-splash-links { + overflow: hidden; +} +.NB-button { + border: 1px solid #07360F; + font-size: 12px; + padding: 4px 12px; + text-transform: uppercase; + margin: 2px 4px 2px; + -moz-box-shadow:2px 2px 0 rgba(35, 35, 35, 0.4); + -webkit-box-shadow:2px 2px 0 rgba(35, 35, 35, 0.4); + box-shadow:2px 2px 0 rgba(35, 35, 35, 0.4); + border-radius: 4px; + -moz-border-radius: 4px; + cursor: pointer; + text-decoration: none; + color: #404040; +} +.NB-button:active { + -moz-box-shadow:1px 1px 0 rgba(35, 35, 35, 0.6); + -webkit-box-shadow:1px 1px 0 rgba(35, 35, 35, 0.6); + box-shadow:1px 1px 0 rgba(35, 35, 35, 0.6); +} +.NB-welcome-container.NB-welcome-tryout { + overflow-x: hidden; +} + +/* ========== */ +/* = Header = */ +/* ========== */ + +.NB-welcome-header { + background-color: #238232; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#69A1D1), to(#E5B568)); + background: -moz-linear-gradient(center top , #69A1D1 0%, #E5B568 100%) repeat scroll 0 0 transparent; + height: 400px; + color: white; + overflow: hidden; + box-shadow: 0 1px 2px rgba(10, 10, 10, 0.3); +} + +.NB-welcome-header .NB-welcome-header-logo { + padding: 108px 0 0 0; + text-align: center; + width: 460px; +} +.NB-welcome-header .NB-welcome-header-tagline { + margin: 36px 0 0 0; + font-size: 22px; + text-shadow: 0 1px 0 rgba(35,35,35,0.35); + text-align: center; + width: 460px; +} +.NB-welcome-header .NB-welcome-header-tagline b { + padding: 2px 8px; + background-color: rgba(205, 205, 205, 0.1); + font-weight: normal; + border-radius: 4px; +} + +.NB-welcome-header-image { + position: absolute; + right: 0; + bottom: 0; +} +.NB-welcome-header-image img.NB-1 { + height: 300px; + position: absolute; + bottom: -350px; + right: -40px; + border-radius: 2px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.NB-welcome-header-image.NB-active img.NB-1 { + bottom: 0px; +} +.NB-welcome-header-image img.NB-2 { + opacity: 0; + height: 300px; + position: absolute; + bottom: -300px; + right: 0; + border-radius: 2px; + margin-right: 100px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.NB-welcome-header-image img.NB-3 { + opacity: 0; + height: 300px; + position: absolute; + bottom: -300px; + right: -24px; + border-radius: 2px; + margin-right: 140px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.NB-welcome-header-captions { + text-align: center; + margin: 0 auto; + position: absolute; + top: 0; + right: 32px; + overflow: hidden; + text-shadow: 0 1px 0 rgba(35,35,35,0.35); + padding-left: 64px; +} +.NB-welcome-header-caption { + float: left; + color: white; + cursor: pointer; + padding: 36px 12px 12px; + text-transform: uppercase; +} +.NB-welcome-header-caption.NB-welcome-header-caption-signin { + color: #E8F64A; +} +.NB-welcome-header-caption span { + padding: 2px 8px; + border-radius: 4px; + display: block; + -webkit-transition: all 1s ease-in-out; + -moz-transition: all 1s ease-in-out; + -o-transition: all 1s ease-in-out; + -ms-transition: all 1s ease-in-out; +} +.NB-welcome-header-caption:hover span { + -webkit-transition: all .25s ease-in-out; + -moz-transition: all .25s ease-in-out; + -o-transition: all .25s ease-in-out; + -ms-transition: all .25s ease-in-out; +} +.NB-welcome-header-caption.NB-active span { + color: #FDC85B; + background-color: rgba(235, 235, 235, 0.1); +} + +.NB-welcome-header-actions { + margin: 36px 0 0; + line-height: 16px; +} +.NB-welcome-header-action { + float: left; +} +.NB-welcome-header-action-subtext { + text-align: center; + line-height: 24px; + padding: 0 12px; + font-size: 11px; + text-transform: uppercase; + color: white; + text-shadow: 0 1px 0 rgba(35, 35, 35, 0.4); +} +.NB-welcome-header-actions img { + vertical-align: top; + width: 16px; + height: 16px; +} +.NB-welcome-header-actions .NB-welcome-header-action-arrow { + display: none; +} +.NB-welcome-header-actions .NB-active .NB-welcome-header-action-bolt { + display: none; +} +.NB-welcome-header-actions .NB-active .NB-welcome-header-action-arrow { + display: block; +} +.NB-welcome-header-actions .NB-button { + float: left; + font-size: 16px; + padding: 6px 10px; + margin: 0 24px; + text-transform: none; + text-shadow: 0 1px 0 rgba(235, 235, 235, 0.4); + background-color: #F5FFE2; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#F5FFE2), to(#E8FACA)); + background: -moz-linear-gradient(center top, #F5FFE2 0%, #E8FACA 100%); +} +.NB-welcome-header-actions .NB-button:hover { + background-color: #F4DD43; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#F4DD43), to(#F1D526)); + background: -moz-linear-gradient(center top, #F4DD43 0%, #F1D526 100%); +} +.NB-welcome-header-actions .NB-button:active { + text-shadow: 0 1px 0 rgba(35, 35, 35, 0.4); + background-color: #E97326; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#E97326), to(#D94B0D)); + background: -moz-linear-gradient(center top, #E97326 0%, #D94B0D 100%); + color: white; +} + +/* ================== */ +/* = Login / Signup = */ +/* ================== */ + +.NB-welcome-header-account { + position: absolute; + bottom: -350px; + right: 12px; + width: 400px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + background-color: rgba(245, 245, 245, 0.4); + padding: 36px 36px 12px; +} +.NB-welcome-header-account.NB-active { + bottom: 0; +} +.NB-welcome-header-account .NB-module-header-login { + width: 142px; + margin: 0 50px 0 0; + padding: 0 4px; + float: left; + text-shadow: 0 1px 0 rgba(35, 35, 35, 0.3); +} + +.NB-welcome-header-account .NB-module-header-signup { + width: 142px; + margin: 0; + padding: 0 4px; + float: left; + text-shadow: 0 1px 0 rgba(35, 35, 35, 0.3); +} + +.NB-welcome-header-account .NB-login { + padding: 0 4px; + margin: 12px 50px 0 12px; + width: 146px; + float: left; +} + +.NB-welcome-header-account .NB-signup { + float: left; + width: 146px; + padding: 0 4px; + margin: 12px 0 0; + height: 206px; + overflow: hidden; +} + +.NB-welcome-header-account .NB-import-signup { + float: left; + width: 146px; + padding: 0 0 64px 0; + margin: 0 24px 0 12px; + height: 206px; + overflow: hidden; +} + +.NB-welcome-header-account .NB-import-signup-text { + text-align: center; + width: 190px; +} +.NB-welcome-header-account .NB-import-signup-text h3 { + margin-top: 48px; + color: #20843D; +} +.NB-welcome-header-account .NB-import-signup-text p { + color: #636363; +} + +.NB-welcome-header-account .NB-signup-orline { + margin: 30px auto 24px; + text-align: center; + font-size: 14px; + line-height: 10px; + color: #F9F9F9; + font-weight: bold; +} + +.NB-welcome-header-account .NB-signup-orline-reduced { + margin: 0px auto 0px; +} +.NB-welcome-header-account .NB-signup-orline .NB-signup-orline-or { + padding: 0 4px; +} + +.NB-welcome-header-account .NB-signup-optional { + float: right; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.4); + font-weight: bold; + font-size: 9px; + line-height: 17px; +} +.NB-welcome-header-account .NB-signup-hidden { + display: none; +} + +.NB-welcome-header-account .NB-import-signup { + height: auto; +} + +.NB-welcome-header-account .NB-signup .NB-signup-google { + margin: 12px auto; + display: block; + font-size: 12px; + text-align: center; +} + +.NB-welcome-header-account input[type=text], +.NB-welcome-header-account input[type=password] { + border: 1px solid rgba(35, 35, 35, 0.5); + display:block; + font-size:13px; + margin:0 0 12px; + padding:5px; + width:134px; +} + +.NB-welcome-header-account input[type=text]:focus, +.NB-welcome-header-account input[type=password]:focus { + border-color: #739BBE; +} + +.NB-welcome-header-account input[type=submit] { + outline: none; + width: 146px; /* 174=input-width + 5*2=padding + 2=border */ + margin: 4px 5px 0 0; + padding: 4px 10px 5px; + text-shadow: 0 -1px 0 rgba(35, 35, 35, 0.4); + font-weight: bold; + font-size: 12px; + border: 1px solid rgba(35, 35, 35, 0.5); + color: white; + cursor: pointer; +} + +.NB-welcome-header-account input[type=hidden] { + display: none; +} + +.NB-welcome-header-account label, +.NB-welcome-header-account .NB-account-label { + margin: 0; + color: #FFFFD5; + text-shadow: 0 1px 0 rgba(35, 35, 35, 0.3); + font-size: 12px; + display: block; + text-transform: uppercase; +} + +.NB-welcome-header-account .NB-account-label { + font-weight: bold; +} + +.NB-welcome-header-account .NB-account-text { + font-size: 13px; + color: #90A0B0; + margin-bottom: 30px; +} + +.NB-welcome-header-account .NB-account-text a { + text-decoration: none; + color: #3E4773; +} + +.NB-welcome-header-account .NB-account-text a:hover { + color: #0E1763; +} + +.NB-welcome-header-account .errorlist { + list-style: none; + font-size: 12px; + color: #AF4D18; + font-weight: bold; + padding: 0; +} + + + +/* ============ */ +/* = Features = */ +/* ============ */ + +.NB-welcome-features { + margin: 48px 0; + overflow: hidden; +} + +.NB-welcome-features .NB-feature { + float: left; + width: 204px; + margin: 0 36px 0 0; + text-align: center; +} + +.NB-welcome-features img { + width: 200px; + height: 175px; + box-shadow: 0 0 3px rgba(30, 30, 30, 0.3); +} +.NB-welcome-features .NB-feature-caption { + font-weight: bold; + font-size: 24px; + margin: 24px 0 0; +} +.NB-welcome-features .NB-feature-text { + color: #808080; + margin: 24px 0 0; + font-size: 15px; + line-height: 22px; +} + +/* =========== */ +/* = Pricing = */ +/* =========== */ + +.NB-welcome-pricing { + overflow: hidden; + clear: both; + margin: 48px 0 0; + padding: 36px 0; + + background-color: #FAFAFA; + border-top: 1px solid #F0F0F0; + border-bottom: 1px solid #F0F0F0; +} +.NB-welcome-pricing p { + line-height: 24px; +} +.NB-welcome-pricing .NB-price { + float: right; + margin: 0; + padding: 0; + line-height: 16px; +} +.NB-pricing { + margin: 12px 0; + width: 100%; + font-size: 14px; +} +.NB-pricing th, +.NB-pricing td { + padding: 10px 8px; + text-align: left; + line-height: 16px; + width: 33%; + vertical-align: top; +} +.NB-pricing th { + border-right: 1px solid #E4E4E4; +} +.NB-pricing td { + border-top: 1px solid #E4E4E4; + border-right: 1px solid #E4E4E4; +} +.NB-pricing .NB-last { + border-right: none; +} +.NB-pricing .NB-bold { + font-weight: bold; +} +.NB-pricing .NB-shiloh { + display: block; + float: left; + border: 1px solid #606060; + height: 54px; + margin: 0 12px 0 0; +} + +/* ============ */ +/* = Activity = */ +/* ============ */ + +.NB-welcome-activity { + overflow: hidden; + clear: both; + padding: 36px 0; +} +.NB-welcome-activity .NB-module { + width: 47%; +} +.NB-welcome-activity .NB-module-features { + float: right; +} +.NB-welcome-activity .NB-module-stats { + float: left; + margin-right: 3%; +} +.NB-welcome-activity .NB-module .NB-module-header { + display: none; +} +.NB-welcome-activity .NB-module .NB-module-stats-counts { + overflow: hidden; +} +.NB-welcome-activity .NB-module-stats .NB-module-content-header-hour { + margin-top: 36px; +} +.NB-welcome-activity .NB-module-stats-count-graph { + margin-left: 4px; +} +.NB-welcome-activity .NB-graph-bar { + width: 4px; +} +.NB-welcome-activity .NB-graph-value { + width: 4px; +} + +/* ========== */ +/* = Footer = */ +/* ========== */ + +.NB-welcome-footer { + overflow: hidden; + clear: both; + padding: 48px 0 52px; + color: #363C53; + line-height: 24px; + background-color: #FAFAFA; + border-top: 1px solid #F0F0F0; +} +.NB-welcome-footer .NB-footer-logo { + vertical-align: text-bottom; + position: relative; + bottom: -4px; +} +.NB-welcome-footer .NB-splash-link { + color: #363C53; + font-weight: bold; + text-shadow: 0 1px 0 rgba(245, 245, 245, 0.4); +} +.NB-welcome-footer .NB-splash-link:hover { + color: #822216; +} +.NB-welcome-footer .NB-footer-icons { + float: right; +} +.NB-welcome-footer .NB-footer-icons a { + line-height: 0; + margin: 0 12px 0 0; + float: right; +} +.NB-welcome-footer .NB-footer-icons a img { + vertical-align: middle; + width: 32px; + height: 32px; + position: relative; + bottom: -4px; + opacity: .8; + -webkit-transition: all .25s ease-in-out; + -moz-transition: all .25s ease-in-out; + -o-transition: all .25s ease-in-out; + -ms-transition: all .25s ease-in-out; +} +.NB-welcome-footer .NB-footer-icons a img:hover { + opacity: 1; +} + +.NB-welcome-footer .NB-twitter-avatar { + width: 24px; + height: 24px; + border: none; + box-shadow: 0 0 1px #C0C0C0; + vertical-align: bottom; + position: relative; + bottom: 0; + margin: 0 2px 0 4px; + border-radius: 2px; +} \ No newline at end of file diff --git a/media/img/favicon.ico b/media/img/favicon.ico new file mode 100644 index 000000000..1b6bab0d5 Binary files /dev/null and b/media/img/favicon.ico differ diff --git a/media/img/favicon_32.png b/media/img/favicon_32.png new file mode 100644 index 000000000..24869ecc8 Binary files /dev/null and b/media/img/favicon_32.png differ diff --git a/media/img/favicon_64.png b/media/img/favicon_64.png new file mode 100644 index 000000000..2744a0c2c Binary files /dev/null and b/media/img/favicon_64.png differ diff --git a/media/img/logo_144.png b/media/img/logo_144.png new file mode 100644 index 000000000..58785ff45 Binary files /dev/null and b/media/img/logo_144.png differ diff --git a/media/img/logo_newsblur.png b/media/img/logo_newsblur.png index fc29ed0fb..46f3af4e0 100644 Binary files a/media/img/logo_newsblur.png and b/media/img/logo_newsblur.png differ diff --git a/media/img/originals/iOS - Collapse Icon.png b/media/img/originals/iOS - Collapse Icon.png new file mode 100644 index 000000000..9e857c910 Binary files /dev/null and b/media/img/originals/iOS - Collapse Icon.png differ diff --git a/media/img/originals/logo_newsblur.png b/media/img/originals/logo_newsblur.png new file mode 100644 index 000000000..11f89d491 Binary files /dev/null and b/media/img/originals/logo_newsblur.png differ diff --git a/media/img/originals/toggle.png b/media/img/originals/toggle.png new file mode 100644 index 000000000..e7caf73df Binary files /dev/null and b/media/img/originals/toggle.png differ diff --git a/media/img/reader/32-Arrow-Down.png b/media/img/reader/32-Arrow-Down.png new file mode 100644 index 000000000..fac397b12 Binary files /dev/null and b/media/img/reader/32-Arrow-Down.png differ diff --git a/media/img/reader/32-Arrow-Left.png b/media/img/reader/32-Arrow-Left.png new file mode 100644 index 000000000..914f56e6c Binary files /dev/null and b/media/img/reader/32-Arrow-Left.png differ diff --git a/media/img/reader/32-Arrow-Right.png b/media/img/reader/32-Arrow-Right.png new file mode 100644 index 000000000..5f4951bb6 Binary files /dev/null and b/media/img/reader/32-Arrow-Right.png differ diff --git a/media/img/reader/32-Arrow-Up.png b/media/img/reader/32-Arrow-Up.png new file mode 100644 index 000000000..8c66de480 Binary files /dev/null and b/media/img/reader/32-Arrow-Up.png differ diff --git a/media/img/reader/32-Power.png b/media/img/reader/32-Power.png new file mode 100644 index 000000000..ee869d67b Binary files /dev/null and b/media/img/reader/32-Power.png differ diff --git a/media/img/reader/download_computer.png b/media/img/reader/download_computer.png new file mode 100644 index 000000000..8a5299784 Binary files /dev/null and b/media/img/reader/download_computer.png differ diff --git a/media/img/reader/search_icon.png b/media/img/reader/search_icon.png new file mode 100644 index 000000000..1426f179a Binary files /dev/null and b/media/img/reader/search_icon.png differ diff --git a/media/img/reader/search_icon2.png b/media/img/reader/search_icon2.png new file mode 100644 index 000000000..ff9941140 Binary files /dev/null and b/media/img/reader/search_icon2.png differ diff --git a/media/img/reader/toggle_minus.png b/media/img/reader/toggle_minus.png new file mode 100644 index 000000000..05d42f4eb Binary files /dev/null and b/media/img/reader/toggle_minus.png differ diff --git a/media/img/reader/toggle_plus.png b/media/img/reader/toggle_plus.png new file mode 100644 index 000000000..423902b9b Binary files /dev/null and b/media/img/reader/toggle_plus.png differ diff --git a/media/img/welcome/Google_Reader_logo.png b/media/img/welcome/Google_Reader_logo.png new file mode 100644 index 000000000..22900671c Binary files /dev/null and b/media/img/welcome/Google_Reader_logo.png differ diff --git a/media/img/welcome/feature_1.png b/media/img/welcome/feature_1.png new file mode 100644 index 000000000..4c24b8d8b Binary files /dev/null and b/media/img/welcome/feature_1.png differ diff --git a/media/img/welcome/feature_1_original.png b/media/img/welcome/feature_1_original.png new file mode 100644 index 000000000..7f34e4977 Binary files /dev/null and b/media/img/welcome/feature_1_original.png differ diff --git a/media/img/welcome/feature_2.png b/media/img/welcome/feature_2.png new file mode 100644 index 000000000..20650f213 Binary files /dev/null and b/media/img/welcome/feature_2.png differ diff --git a/media/img/welcome/feature_3.png b/media/img/welcome/feature_3.png new file mode 100644 index 000000000..8370697ca Binary files /dev/null and b/media/img/welcome/feature_3.png differ diff --git a/media/img/welcome/feature_4.png b/media/img/welcome/feature_4.png new file mode 100644 index 000000000..d6f3cc4c4 Binary files /dev/null and b/media/img/welcome/feature_4.png differ diff --git a/media/img/welcome/fleuron.png b/media/img/welcome/fleuron.png new file mode 100644 index 000000000..6053ca2a3 Binary files /dev/null and b/media/img/welcome/fleuron.png differ diff --git a/media/img/welcome/github_favicon.png b/media/img/welcome/github_favicon.png new file mode 100644 index 000000000..e1e0e18b6 Binary files /dev/null and b/media/img/welcome/github_favicon.png differ diff --git a/media/img/welcome/header-android-original.png b/media/img/welcome/header-android-original.png new file mode 100644 index 000000000..230f65386 Binary files /dev/null and b/media/img/welcome/header-android-original.png differ diff --git a/media/img/welcome/header-android.jpg b/media/img/welcome/header-android.jpg new file mode 100644 index 000000000..2ac589726 Binary files /dev/null and b/media/img/welcome/header-android.jpg differ diff --git a/media/img/welcome/header-android.png b/media/img/welcome/header-android.png new file mode 100644 index 000000000..85d4422bf Binary files /dev/null and b/media/img/welcome/header-android.png differ diff --git a/media/img/welcome/header-ios-original.png b/media/img/welcome/header-ios-original.png new file mode 100644 index 000000000..864ecf156 Binary files /dev/null and b/media/img/welcome/header-ios-original.png differ diff --git a/media/img/welcome/header-ios.jpg b/media/img/welcome/header-ios.jpg new file mode 100644 index 000000000..93a083c84 Binary files /dev/null and b/media/img/welcome/header-ios.jpg differ diff --git a/media/img/welcome/header-ios.png b/media/img/welcome/header-ios.png new file mode 100644 index 000000000..fe17c6d88 Binary files /dev/null and b/media/img/welcome/header-ios.png differ diff --git a/media/img/welcome/header-web-original.png b/media/img/welcome/header-web-original.png new file mode 100644 index 000000000..ba2074e7f Binary files /dev/null and b/media/img/welcome/header-web-original.png differ diff --git a/media/img/welcome/header-web.png b/media/img/welcome/header-web.png new file mode 100644 index 000000000..227bd3bfa Binary files /dev/null and b/media/img/welcome/header-web.png differ diff --git a/media/img/welcome/header1.jpg b/media/img/welcome/header1.jpg new file mode 100644 index 000000000..e8289799c Binary files /dev/null and b/media/img/welcome/header1.jpg differ diff --git a/media/img/welcome/twitter_favicon.png b/media/img/welcome/twitter_favicon.png new file mode 100644 index 000000000..04ce437b2 Binary files /dev/null and b/media/img/welcome/twitter_favicon.png differ diff --git a/media/ios/Classes/ActivityCell.m b/media/ios/Classes/ActivityCell.m index 87e444115..38e6bc84e 100644 --- a/media/ios/Classes/ActivityCell.m +++ b/media/ios/Classes/ActivityCell.m @@ -118,8 +118,8 @@ } else if ([category isEqualToString:@"comment_like"]) { withUserUsername = [[activity objectForKey:@"with_user"] objectForKey:@"username"]; txt = [NSString stringWithFormat:@"%@ favorited %@'s comment on %@:\n%@", username, withUserUsername, title, comment]; - } else if ([category isEqualToString:@"sharedstory"]) { - if ([content isEqualToString:@""] || content == nil) { + } else if ([category isEqualToString:@"sharedstory"]) { + if ([content class] == [NSNull class] || [content isEqualToString:@""] || content == nil) { txt = [NSString stringWithFormat:@"%@ shared %@.", username, title]; } else { txt = [NSString stringWithFormat:@"%@ shared %@:\n%@", username, title, comment]; @@ -130,7 +130,7 @@ } else if ([category isEqualToString:@"feedsub"]) { txt = [NSString stringWithFormat:@"You subscribed to %@.", content]; } else if ([category isEqualToString:@"signup"]) { - txt = [NSString stringWithFormat:@"You signed up for NewsBlur.", content]; + txt = [NSString stringWithFormat:@"You signed up for NewsBlur."]; } NSString *txtWithTime = [NSString stringWithFormat:@"%@\n%@", txt, time]; diff --git a/media/ios/Classes/AddSiteViewController.m b/media/ios/Classes/AddSiteViewController.m index f72256732..29a5b9964 100644 --- a/media/ios/Classes/AddSiteViewController.m +++ b/media/ios/Classes/AddSiteViewController.m @@ -100,7 +100,6 @@ [self showFolderPicker]; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - NSLog(@"%@", self.siteTable.frame); self.siteTable.hidden = NO; self.siteScrollView.frame = CGRectMake(self.siteScrollView.frame.origin.x, self.siteScrollView.frame.origin.y, @@ -278,6 +277,7 @@ [request setDelegate:self]; [request setDidFinishSelector:@selector(requestFinished:)]; [request setDidFailSelector:@selector(requestFailed:)]; + [request setTimeOutSeconds:30]; [request startAsynchronous]; } diff --git a/media/ios/Classes/FeedDetailTableCell.m b/media/ios/Classes/FeedDetailTableCell.m index 7dface224..7567c4e06 100644 --- a/media/ios/Classes/FeedDetailTableCell.m +++ b/media/ios/Classes/FeedDetailTableCell.m @@ -66,7 +66,6 @@ static UIFont *indicatorFont = nil; [backgroundColor set]; CGContextFillRect(context, r); - NSLog(@"WIDTH is %f", rect.size.width); // set site title UIColor *textColor; UIFont *font; diff --git a/media/ios/Classes/FeedDetailViewController.m b/media/ios/Classes/FeedDetailViewController.m index c202333a9..7a0812813 100644 --- a/media/ios/Classes/FeedDetailViewController.m +++ b/media/ios/Classes/FeedDetailViewController.m @@ -122,14 +122,16 @@ [super viewWillAppear:animated]; - if ((appDelegate.isSocialRiverView || appDelegate.isRiverView || appDelegate.isSocialView)) { + if ((appDelegate.isSocialRiverView || + appDelegate.isSocialView || + [appDelegate.activeFolder isEqualToString:@"everything"])) { settingsButton.enabled = NO; } else { settingsButton.enabled = YES; } if (appDelegate.isSocialRiverView || - [appDelegate.activeFolder isEqualToString:@"All Stories"]) { + [appDelegate.activeFolder isEqualToString:@"everything"]) { feedMarkReadButton.enabled = NO; } else { feedMarkReadButton.enabled = YES; @@ -152,11 +154,6 @@ [appDelegate.storyDetailViewController clearStory]; [self checkScroll]; } - - NSString *title = appDelegate.isRiverView ? - appDelegate.activeFolder : - [appDelegate.activeFeed objectForKey:@"feed_title"]; - NSLog(@"title %@", title); } - (void)viewWillDisappear:(BOOL)animated { @@ -266,14 +263,6 @@ [self.storyTitlesTable reloadData]; [storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES]; } - int readStoriesCount = 0; - if (self.feedPage > 1) { - for (id story in appDelegate.activeFeedStories) { - if ([[story objectForKey:@"read_status"] intValue] == 1) { - readStoriesCount += 1; - } - } - } NSString *theFeedDetailURL; @@ -284,11 +273,10 @@ self.feedPage]; } else { theFeedDetailURL = [NSString stringWithFormat: - @"http://%@/reader/river_stories/?feeds=%@&page=%d&read_stories_count=%d", + @"http://%@/reader/river_stories/?feeds=%@&page=%d", NEWSBLUR_URL, [appDelegate.activeFolderFeeds componentsJoinedByString:@"&feeds="], - self.feedPage, - readStoriesCount]; + self.feedPage]; } [self cancelRequests]; @@ -973,10 +961,11 @@ self.actionSheet_ = nil; return; } - NSString *title = appDelegate.isRiverView ? - appDelegate.activeFolder : - [appDelegate.activeFeed objectForKey:@"feed_title"]; - UIActionSheet *options = [[UIActionSheet alloc] + NSString *title = appDelegate.isRiverView ? + appDelegate.activeFolder : + [appDelegate.activeFeed objectForKey:@"feed_title"]; + + UIActionSheet *options = [[UIActionSheet alloc] initWithTitle:title delegate:self cancelButtonTitle:nil @@ -985,20 +974,19 @@ self.actionSheet_ = options; - if (![title isEqualToString:@"Everything"]) { - NSString *deleteText = [NSString stringWithFormat:@"Delete %@", - appDelegate.isRiverView ? - @"this entire folder" : - @"this site"]; - [options addButtonWithTitle:deleteText]; - options.destructiveButtonIndex = 0; - - NSString *moveText = @"Move to another folder"; - [options addButtonWithTitle:moveText]; - + NSString *deleteText = [NSString stringWithFormat:@"Delete %@", + appDelegate.isRiverView ? + @"this entire folder" : + @"this site"]; + [options addButtonWithTitle:deleteText]; + options.destructiveButtonIndex = 0; + + NSString *moveText = @"Move to another folder"; + [options addButtonWithTitle:moveText]; + + if (!appDelegate.isRiverView) { NSString *fetchText = @"Insta-fetch stories"; [options addButtonWithTitle:fetchText]; - } options.cancelButtonIndex = [options addButtonWithTitle:@"Cancel"]; diff --git a/media/ios/Classes/FeedTableCell.h b/media/ios/Classes/FeedTableCell.h index 2c56e4a3d..cdf1737f3 100644 --- a/media/ios/Classes/FeedTableCell.h +++ b/media/ios/Classes/FeedTableCell.h @@ -8,6 +8,7 @@ #import #import "NewsBlurAppDelegate.h" +#import "UnreadCountView.h" #import "ABTableViewCell.h" @class NewsBlurAppDelegate; @@ -20,8 +21,6 @@ int _positiveCount; int _neutralCount; int _negativeCount; - NSString *_positiveCountStr; - NSString *_neutralCountStr; NSString *_negativeCountStr; BOOL isSocial; } @@ -33,8 +32,6 @@ @property (assign, nonatomic) int neutralCount; @property (assign, nonatomic) int negativeCount; @property (assign, nonatomic) BOOL isSocial; -@property (nonatomic) NSString *positiveCountStr; -@property (nonatomic) NSString *neutralCountStr; @property (nonatomic) NSString *negativeCountStr; @end diff --git a/media/ios/Classes/FeedTableCell.m b/media/ios/Classes/FeedTableCell.m index e24bc3b06..689332e33 100644 --- a/media/ios/Classes/FeedTableCell.m +++ b/media/ios/Classes/FeedTableCell.m @@ -8,17 +8,10 @@ #import "NewsBlurAppDelegate.h" #import "FeedTableCell.h" +#import "UnreadCountView.h" #import "ABTableViewCell.h" -#import "UIView+TKCategory.h" static UIFont *textFont = nil; -static UIFont *indicatorFont = nil; -static UIColor *indicatorWhiteColor = nil; -static UIColor *indicatorBlackColor = nil; -static UIColor *positiveBackgroundColor = nil; -static UIColor *neutralBackgroundColor = nil; -static UIColor *negativeBackgroundColor = nil; -static CGFloat *psColors = nil; @implementation FeedTableCell @@ -28,24 +21,12 @@ static CGFloat *psColors = nil; @synthesize positiveCount = _positiveCount; @synthesize neutralCount = _neutralCount; @synthesize negativeCount = _negativeCount; -@synthesize positiveCountStr; -@synthesize neutralCountStr; @synthesize negativeCountStr; @synthesize isSocial; + (void) initialize{ if (self == [FeedTableCell class]) { textFont = [UIFont boldSystemFontOfSize:18]; - indicatorFont = [UIFont boldSystemFontOfSize:12]; - indicatorWhiteColor = [UIColor whiteColor]; - indicatorBlackColor = [UIColor blackColor]; - - UIColor *ps = UIColorFromRGB(0x3B7613); - UIColor *nt = UIColorFromRGB(0xF9C72A); - UIColor *ng = UIColorFromRGB(0xCC2A2E); - positiveBackgroundColor = ps; - neutralBackgroundColor = nt; - negativeBackgroundColor = ng; // UIColor *psGrad = UIColorFromRGB(0x559F4D); // UIColor *ntGrad = UIColorFromRGB(0xE4AB00); // UIColor *ngGrad = UIColorFromRGB(0x9B181B); @@ -64,7 +45,6 @@ static CGFloat *psColors = nil; if (ps == _positiveCount) return; _positiveCount = ps; - _positiveCountStr = [NSString stringWithFormat:@"%d", ps]; [self setNeedsDisplay]; } @@ -72,7 +52,6 @@ static CGFloat *psColors = nil; if (nt == _neutralCount) return; _neutralCount = nt; - _neutralCountStr = [NSString stringWithFormat:@"%d", nt]; [self setNeedsDisplay]; } @@ -115,89 +94,9 @@ static CGFloat *psColors = nil; CGContextStrokePath(context); } - CGRect rect = CGRectInset(r, 12, 12); - rect.size.width -= 18; // Scrollbar padding - - int psWidth = _positiveCount == 0 ? 0 : _positiveCount < 10 ? - 14 : _positiveCount < 100 ? 22 : 28; - int ntWidth = _neutralCount == 0 ? 0 : _neutralCount < 10 ? - 14 : _neutralCount < 100 ? 22 : 28; - int ngWidth = _negativeCount == 0 ? 0 : _negativeCount < 10 ? - 14 : _negativeCount < 100 ? 22 : 28; - - int psOffset = _positiveCount == 0 ? 0 : psWidth - 20; - int ntOffset = _neutralCount == 0 ? 0 : ntWidth - 20; - int ngOffset = _negativeCount == 0 ? 0 : ngWidth - 20; - - int psPadding = _positiveCount == 0 ? 0 : 2; - int ntPadding = _neutralCount == 0 ? 0 : 2; - - if(_positiveCount > 0){ - [positiveBackgroundColor set]; - CGRect rr; - - if (self.isSocial) { - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - rr = CGRectMake(rect.size.width + rect.origin.x - psOffset, 14, psWidth, 17); - } else { - rr = CGRectMake(rect.size.width + rect.origin.x - psOffset, 10, psWidth, 17); - } - } else { - rr = CGRectMake(rect.size.width + rect.origin.x - psOffset, 9, psWidth, 17); - } - - ; - [UIView drawLinearGradientInRect:rr colors:psColors]; - [UIView drawRoundRectangleInRect:rr withRadius:4]; - - [indicatorWhiteColor set]; - - CGSize size = [_positiveCountStr sizeWithFont:indicatorFont]; - float x_pos = (rr.size.width - size.width) / 2; - float y_pos = (rr.size.height - size.height) / 2; - [_positiveCountStr - drawAtPoint:CGPointMake(rr.origin.x + x_pos, rr.origin.y + y_pos) - withFont:indicatorFont]; - } - if(_neutralCount > 0 && appDelegate.selectedIntelligence <= 0){ - [neutralBackgroundColor set]; - - CGRect rr; - if (self.isSocial) { - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntOffset, 14, ntWidth, 17); - } else { - rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntOffset, 10, ntWidth, 17); - } - } else { - rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntOffset, 9, ntWidth, 17); - } - - [UIView drawRoundRectangleInRect:rr withRadius:4]; -// [UIView drawLinearGradientInRect:rr colors:ntColors]; - - [indicatorBlackColor set]; - CGSize size = [_neutralCountStr sizeWithFont:indicatorFont]; - float x_pos = (rr.size.width - size.width) / 2; - float y_pos = (rr.size.height - size.height) / 2; - [_neutralCountStr - drawAtPoint:CGPointMake(rr.origin.x + x_pos, rr.origin.y + y_pos) - withFont:indicatorFont]; - } - if(_negativeCount > 0 && appDelegate.selectedIntelligence <= -1){ - [negativeBackgroundColor set]; - CGRect rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntWidth - ntPadding - ngOffset, self.isSocial ? 14: 9, ngWidth, 17); - [UIView drawRoundRectangleInRect:rr withRadius:4]; -// [UIView drawLinearGradientInRect:rr colors:ngColors]; - - [indicatorWhiteColor set]; - CGSize size = [_negativeCountStr sizeWithFont:indicatorFont]; - float x_pos = (rr.size.width - size.width) / 2; - float y_pos = (rr.size.height - size.height) / 2; - [_negativeCountStr - drawAtPoint:CGPointMake(rr.origin.x + x_pos, rr.origin.y + y_pos) - withFont:indicatorFont]; - } + UnreadCountView *unreadCount = [UnreadCountView alloc]; + [unreadCount drawInRect:r ps:_positiveCount nt:_neutralCount + listType:(isSocial ? NBFeedListSocial : NBFeedListFeed)]; UIColor *textColor = self.selected || self.highlighted ? [UIColor blackColor]: @@ -215,14 +114,14 @@ static CGFloat *psColors = nil; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { [self.feedFavicon drawInRect:CGRectMake(12.0, 5.0, 36.0, 36.0)]; [feedTitle - drawInRect:CGRectMake(56, 13, rect.size.width - psWidth - psPadding - ntWidth - ntPadding - ngWidth - 10 - 20, 20.0) + drawInRect:CGRectMake(56, 13, [unreadCount offsetWidth] - 10 - 20, 20.0) withFont:font lineBreakMode:UILineBreakModeTailTruncation alignment:UITextAlignmentLeft]; } else { [self.feedFavicon drawInRect:CGRectMake(9.0, 3.0, 32.0, 32.0)]; [feedTitle - drawInRect:CGRectMake(50, 11, rect.size.width - psWidth - psPadding - ntWidth - ntPadding - ngWidth - 10 - 20, 20.0) + drawInRect:CGRectMake(50, 11, [unreadCount offsetWidth] - 10 - 20, 20.0) withFont:font lineBreakMode:UILineBreakModeTailTruncation alignment:UITextAlignmentLeft]; @@ -230,16 +129,16 @@ static CGFloat *psColors = nil; } else { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - [self.feedFavicon drawInRect:CGRectMake(12.0, 9.0, 16.0, 16.0)]; + [self.feedFavicon drawInRect:CGRectMake(12.0, 7.0, 16.0, 16.0)]; [feedTitle - drawInRect:CGRectMake(36.0, 9.0, rect.size.width - psWidth - psPadding - ntWidth - ntPadding - ngWidth - 10, 20.0) + drawInRect:CGRectMake(36.0, 7.0, [unreadCount offsetWidth] - 10, 20.0) withFont:font lineBreakMode:UILineBreakModeTailTruncation alignment:UITextAlignmentLeft]; } else { - [self.feedFavicon drawInRect:CGRectMake(9.0, 9.0, 16.0, 16.0)]; + [self.feedFavicon drawInRect:CGRectMake(9.0, 7.0, 16.0, 16.0)]; [feedTitle - drawInRect:CGRectMake(34.0, 9.0, rect.size.width - psWidth - psPadding - ntWidth - ntPadding - ngWidth - 10, 20.0) + drawInRect:CGRectMake(34.0, 7.0, [unreadCount offsetWidth] - 10, 20.0) withFont:font lineBreakMode:UILineBreakModeTailTruncation alignment:UITextAlignmentLeft]; diff --git a/media/ios/Classes/FirstTimeUserAddFriendsViewController.xib b/media/ios/Classes/FirstTimeUserAddFriendsViewController.xib index 7161cbbf5..bfb445cfb 100644 --- a/media/ios/Classes/FirstTimeUserAddFriendsViewController.xib +++ b/media/ios/Classes/FirstTimeUserAddFriendsViewController.xib @@ -1,23 +1,23 @@ - 1296 - 11E53 - 2182 - 1138.47 - 569.00 + 1536 + 12C54 + 2840 + 1187.34 + 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 1181 + 1926 - IBUIView - IBUIImageView - IBUILabel IBProxyObject IBUIActivityIndicatorView - IBUISwitch IBUIButton + IBUIImageView + IBUILabel + IBUISwitch + IBUIView com.apple.InterfaceBuilder.IBCocoaTouchPlugin @@ -69,10 +69,11 @@ 7 NO IBIPadFramework - Connect with your friends to easily follow the stories that matter to them. + Connect with your friends to easily follow them. 1 MCAwIDAAA + darkTextColor @@ -93,11 +94,12 @@ 18 16 + 400 303 - {{121, 421}, {298, 106}} + {{116, 421}, {314, 106}} @@ -124,6 +126,7 @@ 13 16 + 314 @@ -1521,6 +1524,7 @@ UIView UISegmentedControl UIView + UIBarButtonItem @@ -1555,6 +1559,10 @@ noFocusMessage UIView + + toolbarLeftMargin + UIBarButtonItem + IBProjectSource @@ -1848,7 +1856,7 @@ IBIPadFramework com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - + YES 3 @@ -1857,6 +1865,6 @@ {1000, 1000} {184, 34} - 1181 + 1926 diff --git a/media/ios/Classes/FolderTitleView.h b/media/ios/Classes/FolderTitleView.h new file mode 100644 index 000000000..2c6fa8eac --- /dev/null +++ b/media/ios/Classes/FolderTitleView.h @@ -0,0 +1,23 @@ +// +// FolderTitleView.h +// NewsBlur +// +// Created by Samuel Clay on 10/2/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import +#import "NewsBlurAppDelegate.h" + + +@class NewsBlurAppDelegate; + +@interface FolderTitleView : UIControl { + NewsBlurAppDelegate *appDelegate; +} + +@property (nonatomic) NewsBlurAppDelegate *appDelegate; + +- (UIControl *)drawWithRect:(CGRect)rect inSection:(NSInteger)section; + +@end diff --git a/media/ios/Classes/FolderTitleView.m b/media/ios/Classes/FolderTitleView.m new file mode 100644 index 000000000..133668d3e --- /dev/null +++ b/media/ios/Classes/FolderTitleView.m @@ -0,0 +1,164 @@ +// +// FolderTitleView.m +// NewsBlur +// +// Created by Samuel Clay on 10/2/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "NewsBlurAppDelegate.h" +#import "FolderTitleView.h" +#import "UnreadCountView.h" + +@implementation FolderTitleView + +@synthesize appDelegate; + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + + } + return self; +} + +- (UIControl *)drawWithRect:(CGRect)rect inSection:(NSInteger)section { + + self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate]; + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + + NSString *folderName; + if (section == 0) { + folderName = @"river_blurblogs"; + } else { + folderName = [appDelegate.dictFoldersArray objectAtIndex:section]; + } + NSString *collapseKey = [NSString stringWithFormat:@"folderCollapsed:%@", folderName]; + bool isFolderCollapsed = [userPreferences boolForKey:collapseKey]; + + // create the parent view that will hold header Label + UIControl* customView = [[UIControl alloc] + initWithFrame:rect]; + UIView *borderTop = [[UIView alloc] + initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, + rect.size.width, 1.0)]; + borderTop.backgroundColor = UIColorFromRGB(0xe0e0e0); + borderTop.opaque = NO; + [customView addSubview:borderTop]; + + + UIView *borderBottom = [[UIView alloc] + initWithFrame:CGRectMake(rect.origin.x, rect.size.height-1, + rect.size.width, 1.0)]; + borderBottom.backgroundColor = [UIColorFromRGB(0xB7BDC6) colorWithAlphaComponent:0.5]; + borderBottom.opaque = NO; + [customView addSubview:borderBottom]; + + UILabel * headerLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + customView.opaque = NO; + headerLabel.backgroundColor = [UIColor clearColor]; + headerLabel.opaque = NO; + headerLabel.textColor = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0]; + headerLabel.highlightedTextColor = [UIColor whiteColor]; + headerLabel.font = [UIFont boldSystemFontOfSize:11]; + headerLabel.frame = CGRectMake(36.0, 1.0, rect.size.width - 36, rect.size.height); + headerLabel.shadowColor = [UIColor colorWithRed:.94 green:0.94 blue:0.97 alpha:1.0]; + headerLabel.shadowOffset = CGSizeMake(0.0, 1.0); + if (section == 0) { + headerLabel.text = @"ALL BLURBLOG STORIES"; + // customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) + // colorWithAlphaComponent:0.8]; + } else if (section == 1) { + headerLabel.text = @"ALL STORIES"; + // customView.backgroundColor = [UIColorFromRGB(0xE6DDD7) + // colorWithAlphaComponent:0.8]; + } else { + headerLabel.text = [[appDelegate.dictFoldersArray objectAtIndex:section] uppercaseString]; + // customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) + // colorWithAlphaComponent:0.8]; + } + + customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) + colorWithAlphaComponent:0.8]; + [customView addSubview:headerLabel]; + + UIButton *invisibleHeaderButton = [UIButton buttonWithType:UIButtonTypeCustom]; + invisibleHeaderButton.frame = CGRectMake(0, 0, customView.frame.size.width, customView.frame.size.height); + invisibleHeaderButton.alpha = .1; + invisibleHeaderButton.tag = section; + [invisibleHeaderButton addTarget:appDelegate.feedsViewController action:@selector(didSelectSectionHeader:) forControlEvents:UIControlEventTouchUpInside]; + [customView addSubview:invisibleHeaderButton]; + + [invisibleHeaderButton addTarget:appDelegate.feedsViewController action:@selector(sectionTapped:) forControlEvents:UIControlEventTouchDown]; + [invisibleHeaderButton addTarget:appDelegate.feedsViewController action:@selector(sectionUntapped:) forControlEvents:UIControlEventTouchUpInside]; + [invisibleHeaderButton addTarget:appDelegate.feedsViewController action:@selector(sectionUntappedOutside:) forControlEvents:UIControlEventTouchUpOutside]; + + if (!appDelegate.hasNoSites) { + if (section != 1) { + UIImage *disclosureBorder = [UIImage imageNamed:@"disclosure_border.png"]; + UIImageView *disclosureBorderView = [[UIImageView alloc] initWithImage:disclosureBorder]; + disclosureBorderView.frame = CGRectMake(customView.frame.size.width - 30, -1, 29, 29); + [customView addSubview:disclosureBorderView]; + } + + UIButton *disclosureButton = [UIButton buttonWithType:UIButtonTypeCustom]; + UIImage *disclosureImage = [UIImage imageNamed:@"disclosure.png"]; + [disclosureButton setImage:disclosureImage forState:UIControlStateNormal]; + disclosureButton.frame = CGRectMake(customView.frame.size.width - 30, -1, 29, 29); + if (section != 1) { + if (!isFolderCollapsed) { + disclosureButton.transform = CGAffineTransformMakeRotation(M_PI_2); + } + + disclosureButton.tag = section; + [disclosureButton addTarget:appDelegate.feedsViewController action:@selector(didCollapseFolder:) forControlEvents:UIControlEventTouchUpInside]; + } + [customView addSubview:disclosureButton]; + } + + UIImage *folderImage; + int folderImageViewX = 10; + + if (section == 0) { + folderImage = [UIImage imageNamed:@"group.png"]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + folderImageViewX = 10; + } else { + folderImageViewX = 8; + } + } else if (section == 1) { + folderImage = [UIImage imageNamed:@"archive.png"]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + folderImageViewX = 10; + } else { + folderImageViewX = 7; + } + } else { + if (isFolderCollapsed) { + folderImage = [UIImage imageNamed:@"folder_collapsed.png"]; + } else { + folderImage = [UIImage imageNamed:@"folder_2.png"]; + } + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + } else { + folderImageViewX = 7; + } + } + UIImageView *folderImageView = [[UIImageView alloc] initWithImage:folderImage]; + folderImageView.frame = CGRectMake(folderImageViewX, 3, 20, 20); + [customView addSubview:folderImageView]; + + [customView setAutoresizingMask:UIViewAutoresizingNone]; + + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextFillRect(context, rect); + + UnreadCountView *unreadCount = [UnreadCountView alloc]; + [unreadCount drawInRect:rect ps:123 nt:321 + listType:NBFeedListFolder]; + + return customView; +} + +@end diff --git a/media/ios/Classes/LoginViewController.m b/media/ios/Classes/LoginViewController.m index 525904002..bf2bd3af7 100644 --- a/media/ios/Classes/LoginViewController.m +++ b/media/ios/Classes/LoginViewController.m @@ -90,8 +90,6 @@ // Return YES for supported orientations if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { return YES; - } else if (UIInterfaceOrientationIsPortrait(interfaceOrientation)) { - return YES; } return NO; } @@ -145,7 +143,7 @@ } #pragma mark - -#pragma mark Loginp +#pragma mark Login - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; @@ -165,8 +163,6 @@ if(textField == usernameInput) { [passwordInput becomeFirstResponder]; } else if (textField == passwordInput && [self.loginControl selectedSegmentIndex] == 0) { - NSLog(@"Password return"); - NSLog(@"appdelegate:: %@", [self appDelegate]); [self checkPassword]; } else if (textField == passwordInput && [self.loginControl selectedSegmentIndex] == 1) { [emailInput becomeFirstResponder]; diff --git a/media/ios/Classes/MoveSiteViewController.m b/media/ios/Classes/MoveSiteViewController.m index 693536a2c..0cb7725ec 100644 --- a/media/ios/Classes/MoveSiteViewController.m +++ b/media/ios/Classes/MoveSiteViewController.m @@ -235,6 +235,8 @@ [self.folders addObject:@"— Top Level —"]; for (NSString *folder in appDelegate.dictFoldersArray) { + if ([folder isEqualToString:@"everything"]) continue; + if ([folder isEqualToString:@"river_blurblogs"]) continue; if ([[folder trim] isEqualToString:@""]) continue; if (appDelegate.isRiverView) { if (![folder containsString:appDelegate.activeFolder]) { diff --git a/media/ios/Classes/NewsBlurAppDelegate.h b/media/ios/Classes/NewsBlurAppDelegate.h index 37149d97d..f15267a40 100644 --- a/media/ios/Classes/NewsBlurAppDelegate.h +++ b/media/ios/Classes/NewsBlurAppDelegate.h @@ -73,6 +73,7 @@ NSString * activeUsername; NSString * activeUserProfileId; NSString * activeUserProfileName; + BOOL hasNoSites; BOOL isRiverView; BOOL isSocialView; BOOL isSocialRiverView; @@ -147,6 +148,7 @@ @property (readwrite) NSString * activeUsername; @property (readwrite) NSString * activeUserProfileId; @property (readwrite) NSString * activeUserProfileName; +@property (nonatomic, readwrite) BOOL hasNoSites; @property (nonatomic, readwrite) BOOL isRiverView; @property (nonatomic, readwrite) BOOL isSocialView; @property (nonatomic, readwrite) BOOL isSocialRiverView; diff --git a/media/ios/Classes/NewsBlurAppDelegate.m b/media/ios/Classes/NewsBlurAppDelegate.m index 9cd6fa4cd..fb75b7c9e 100644 --- a/media/ios/Classes/NewsBlurAppDelegate.m +++ b/media/ios/Classes/NewsBlurAppDelegate.m @@ -70,6 +70,7 @@ @synthesize activeUsername; @synthesize activeUserProfileId; @synthesize activeUserProfileName; +@synthesize hasNoSites; @synthesize isRiverView; @synthesize isSocialView; @synthesize isSocialRiverView; @@ -509,9 +510,8 @@ - (void)adjustStoryDetailWebView { // change UIWebView int contentWidth = storyDetailViewController.view.frame.size.width; - NSLog(@"contentWidth is %i", contentWidth); +// NSLog(@"contentWidth is %i", contentWidth); [storyDetailViewController changeWebViewWidth:contentWidth]; - } - (void)calibrateStoryTitles { @@ -545,7 +545,13 @@ - (void)loadStoryDetailView { NSString *feedTitle; if (self.isRiverView) { - feedTitle = self.activeFolder; + if ([self.activeFolder isEqualToString:@"river_blurblogs"]) { + feedTitle = @"All Shared Stories"; + } else if ([self.activeFolder isEqualToString:@"everything"]) { + feedTitle = @"All Stories"; + } else { + feedTitle = self.activeFolder; + } } else { feedTitle = [activeFeed objectForKey:@"feed_title"]; } @@ -758,11 +764,11 @@ int total = 0; NSArray *folder; - if (!folderName && self.activeFolder == @"ALL BLURBLOG STORIES") { + if (!folderName && self.activeFolder == @"river_blurblogs") { for (id feedId in self.dictSocialFeeds) { total += [self unreadCountForFeed:feedId]; } - } else if (!folderName && self.activeFolder == @"ALL STORIES STORIES") { + } else if (!folderName && self.activeFolder == @"everything") { for (id feedId in self.dictFeeds) { total += [self unreadCountForFeed:feedId]; } @@ -960,7 +966,7 @@ } - (void)markActiveFolderAllRead { - if (self.activeFolder == @"Everything") { + if (self.activeFolder == @"everything") { for (NSString *folderName in self.dictFoldersArray) { for (id feedId in [self.dictFolders objectForKey:folderName]) { [self markFeedAllRead:feedId]; @@ -1153,9 +1159,11 @@ - (UIView *)makeFeedTitle:(NSDictionary *)feed { UILabel *titleLabel = [[UILabel alloc] init]; if (self.isSocialRiverView) { - titleLabel.text = [NSString stringWithFormat:@" All Blurblog Stories"]; + titleLabel.text = [NSString stringWithFormat:@" All Shared Stories"]; + } else if (self.isRiverView && [self.activeFolder isEqualToString:@"everything"]) { + titleLabel.text = [NSString stringWithFormat:@" All Stories"]; } else if (self.isRiverView) { - titleLabel.text = [NSString stringWithFormat:@" %@", self.activeFolder]; + titleLabel.text = [NSString stringWithFormat:@" %@", self.activeFolder]; } else if (self.isSocialView) { titleLabel.text = [NSString stringWithFormat:@" %@", [feed objectForKey:@"feed_title"]]; } else { diff --git a/media/ios/Classes/NewsBlurViewController.h b/media/ios/Classes/NewsBlurViewController.h index 3668d04b3..4fe0c65dc 100644 --- a/media/ios/Classes/NewsBlurViewController.h +++ b/media/ios/Classes/NewsBlurViewController.h @@ -8,6 +8,7 @@ #import #import "NewsBlurAppDelegate.h" +#import "FolderTitleView.h" #import "ASIHTTPRequest.h" #import "PullToRefreshView.h" #import "BaseViewController.h" @@ -27,7 +28,6 @@ NSMutableDictionary *visibleFeeds; NSMutableDictionary *stillVisibleFeeds; BOOL viewShowingAllFeeds; - BOOL hasNoSites; PullToRefreshView *pull; NSDate *lastUpdate; NSCache *imageCache; @@ -52,7 +52,6 @@ @property (nonatomic) NSMutableDictionary *visibleFeeds; @property (nonatomic) NSMutableDictionary *stillVisibleFeeds; @property (nonatomic, readwrite) BOOL viewShowingAllFeeds; -@property (nonatomic, readwrite) BOOL hasNoSites; @property (nonatomic) PullToRefreshView *pull; @property (nonatomic) NSDate *lastUpdate; @property (nonatomic) NSCache *imageCache; @@ -70,6 +69,7 @@ - (void)setUserAvatarLayout:(UIInterfaceOrientation)orientation; - (void)didSelectSectionHeader:(UIButton *)button; - (IBAction)selectIntelligence; +- (void)didCollapseFolder:(UIButton *)button; - (void)changeToAllMode; - (void)updateFeedsWithIntelligence:(int)previousLevel newLevel:(int)newLevel; - (void)calculateFeedLocations:(BOOL)markVisible; diff --git a/media/ios/Classes/NewsBlurViewController.m b/media/ios/Classes/NewsBlurViewController.m index 08bfe9bfe..c71f01f3f 100644 --- a/media/ios/Classes/NewsBlurViewController.m +++ b/media/ios/Classes/NewsBlurViewController.m @@ -22,10 +22,11 @@ #import "UIBarButtonItem+WEPopover.h" -#define kPhoneTableViewRowHeight 36; -#define kTableViewRowHeight 36; +#define kPhoneTableViewRowHeight 32; +#define kTableViewRowHeight 32; #define kBlurblogTableViewRowHeight 47; #define kPhoneBlurblogTableViewRowHeight 39; +static const CGFloat kFolderTitleHeight = 28; @interface NewsBlurViewController () @@ -55,7 +56,6 @@ @synthesize currentRowAtIndexPath; @synthesize noFocusMessage; @synthesize toolbarLeftMargin; -@synthesize hasNoSites; @synthesize updatedDictFeeds_; @synthesize updatedDictSocialFeeds_; @synthesize inPullToRefresh_; @@ -295,7 +295,7 @@ return [self informError:@"The server barfed!"]; } - self.hasNoSites = NO; + appDelegate.hasNoSites = NO; NSString *responseString = [request responseString]; NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding]; NSError *error; @@ -393,10 +393,10 @@ allFolders = [[results objectForKey:@"flat_folders"] mutableCopy]; } - [allFolders setValue:socialFolder forKey:@""]; + [allFolders setValue:socialFolder forKey:@"river_blurblogs"]; - if (![[allFolders allKeys] containsObject:@" "]) { - [allFolders setValue:[[NSArray alloc] init] forKey:@" "]; + if (![[allFolders allKeys] containsObject:@"everything"]) { + [allFolders setValue:[[NSArray alloc] init] forKey:@"everything"]; } appDelegate.dictFolders = allFolders; @@ -434,6 +434,16 @@ } appDelegate.dictFolders = sortedFolders; [appDelegate.dictFoldersArray sortUsingSelector:@selector(caseInsensitiveCompare:)]; + + // Move River Blurblog and Everything to the top + if ([appDelegate.dictFoldersArray containsObject:@"river_blurblogs"]) { + [appDelegate.dictFoldersArray removeObject:@"river_blurblogs"]; + [appDelegate.dictFoldersArray insertObject:@"river_blurblogs" atIndex:0]; + } + if ([appDelegate.dictFoldersArray containsObject:@"everything"]) { + [appDelegate.dictFoldersArray removeObject:@"everything"]; + [appDelegate.dictFoldersArray insertObject:@"everything" atIndex:1]; + } if (self.viewShowingAllFeeds) { [self calculateFeedLocations:NO]; @@ -445,7 +455,7 @@ if ([[appDelegate.dictFeeds allKeys] count] == 0 && [[appDelegate.dictSocialFeeds allKeys] count] == 0) { - self.hasNoSites = YES; + appDelegate.hasNoSites = YES; } [self.feedTitlesTable reloadData]; @@ -567,7 +577,7 @@ - (void)switchSitesUnread { NSDictionary *feed; - + NSInteger intelligenceLevel = [appDelegate selectedIntelligence]; NSMutableArray *indexPaths = [NSMutableArray array]; @@ -617,18 +627,25 @@ [self calculateFeedLocations:YES]; } - [self.feedTitlesTable beginUpdates]; - if ([indexPaths count] > 0) { - if (self.viewShowingAllFeeds) { - [self.feedTitlesTable insertRowsAtIndexPaths:indexPaths - withRowAnimation:UITableViewRowAnimationNone]; - } else { - - [self.feedTitlesTable deleteRowsAtIndexPaths:indexPaths - withRowAnimation:UITableViewRowAnimationNone]; +// @try { + [self.feedTitlesTable beginUpdates]; + if ([indexPaths count] > 0) { + if (self.viewShowingAllFeeds) { + [self.feedTitlesTable insertRowsAtIndexPaths:indexPaths + withRowAnimation:UITableViewRowAnimationNone]; + } else { + [self.feedTitlesTable deleteRowsAtIndexPaths:indexPaths + withRowAnimation:UITableViewRowAnimationNone]; + } } - } - [self.feedTitlesTable endUpdates]; + [self.feedTitlesTable endUpdates]; +// } +// @catch (NSException *exception) { +// NSLog(@"EXCEPTION: %@", exception); +// [self.feedTitlesTable beginUpdates]; +// [self.feedTitlesTable endUpdates]; +// [self.feedTitlesTable reloadData]; +// } CGPoint offset = CGPointMake(0, 0); [self.feedTitlesTable setContentOffset:offset animated:YES]; @@ -644,7 +661,7 @@ #pragma mark Table View - Feed List - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - if (self.hasNoSites) { + if (appDelegate.hasNoSites) { return 2; } return [appDelegate.dictFoldersArray count]; @@ -655,11 +672,12 @@ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (self.hasNoSites) { + if (appDelegate.hasNoSites) { return 1; } NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:section]; + return [[self.activeFeedLocations objectForKey:folderName] count]; } @@ -667,7 +685,7 @@ cellForRowAtIndexPath:(NSIndexPath *)indexPath { // messaging when there are no sites - if (self.hasNoSites) { + if (appDelegate.hasNoSites) { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"EmptyCell"]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; @@ -732,7 +750,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - if (self.hasNoSites) { + if (appDelegate.hasNoSites) { return; } @@ -740,7 +758,14 @@ self.currentRowAtIndexPath = indexPath; NSDictionary *feed; - NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:indexPath.section]; + NSString *folderName; + if (indexPath.section == 0) { + folderName = @"river_blurblogs"; + } else if (indexPath.section == 1) { + folderName = @"everything"; + } else { + folderName = [appDelegate.dictFoldersArray objectAtIndex:indexPath.section]; + } NSArray *feeds = [appDelegate.dictFolders objectForKey:folderName]; NSArray *activeFolderFeeds = [self.activeFeedLocations objectForKey:folderName]; int location = [[activeFolderFeeds objectAtIndex:indexPath.row] intValue]; @@ -772,7 +797,7 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - if (self.hasNoSites) { + if (appDelegate.hasNoSites) { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { return kBlurblogTableViewRowHeight; } else { @@ -780,9 +805,21 @@ } } - NSString *folderName = [appDelegate.dictFoldersArray objectAtIndex:indexPath.section]; + NSString *folderName; + if (indexPath.section == 0) { + folderName = @"river_blurblogs"; + } else { + folderName = [appDelegate.dictFoldersArray objectAtIndex:indexPath.section]; + } + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + NSString *collapseKey = [NSString stringWithFormat:@"folderCollapsed:%@", folderName]; + bool isFolderCollapsed = [userPreferences boolForKey:collapseKey]; - if ([folderName isEqualToString:@""]) { // blurblogs + if (isFolderCollapsed) { + return 0; + } + + if ([folderName isEqualToString:@"river_blurblogs"]) { // blurblogs if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { return kBlurblogTableViewRowHeight; } else { @@ -800,113 +837,11 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { - int headerLabelHeight, folderImageViewY, disclosureImageViewY; -// if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { - headerLabelHeight = 27; - folderImageViewY = 3; - disclosureImageViewY = 7; -// } else { -// headerLabelHeight = 20; -// folderImageViewY = 0; -// disclosureImageViewY = 4; -// } - - // create the parent view that will hold header Label - UIControl* customView = [[UIControl alloc] - initWithFrame:CGRectMake(0.0, 0.0, - tableView.bounds.size.width, headerLabelHeight + 1)]; - UIView *borderTop = [[UIView alloc] - initWithFrame:CGRectMake(0.0, 0, - tableView.bounds.size.width, 1.0)]; - borderTop.backgroundColor = UIColorFromRGB(0xe0e0e0); - borderTop.opaque = NO; - [customView addSubview:borderTop]; + CGRect rect = CGRectMake(0.0, 0.0, tableView.bounds.size.width, kFolderTitleHeight); + UIView *folderTitle = [[FolderTitleView alloc] drawWithRect:rect inSection:section]; - - UIView *borderBottom = [[UIView alloc] - initWithFrame:CGRectMake(0.0, headerLabelHeight, - tableView.bounds.size.width, 1.0)]; - borderBottom.backgroundColor = [UIColorFromRGB(0xB7BDC6) colorWithAlphaComponent:0.5]; - borderBottom.opaque = NO; - [customView addSubview:borderBottom]; - - UILabel * headerLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - customView.opaque = NO; - headerLabel.backgroundColor = [UIColor clearColor]; - headerLabel.opaque = NO; - headerLabel.textColor = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0]; - headerLabel.highlightedTextColor = [UIColor whiteColor]; - headerLabel.font = [UIFont boldSystemFontOfSize:11]; - headerLabel.frame = CGRectMake(36.0, 1.0, 286.0, headerLabelHeight); - headerLabel.shadowColor = [UIColor colorWithRed:.94 green:0.94 blue:0.97 alpha:1.0]; - headerLabel.shadowOffset = CGSizeMake(0.0, 1.0); - if (section == 0) { - headerLabel.text = @"ALL BLURBLOG STORIES"; -// customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) -// colorWithAlphaComponent:0.8]; - } else if (section == 1) { - headerLabel.text = @"ALL STORIES"; -// customView.backgroundColor = [UIColorFromRGB(0xE6DDD7) -// colorWithAlphaComponent:0.8]; - } else { - headerLabel.text = [[appDelegate.dictFoldersArray objectAtIndex:section] uppercaseString]; -// customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) -// colorWithAlphaComponent:0.8]; - } - - customView.backgroundColor = [UIColorFromRGB(0xD7DDE6) - colorWithAlphaComponent:0.8]; - [customView addSubview:headerLabel]; - - UIImage *folderImage; - int folderImageViewX = 10; - - if (section == 0) { - folderImage = [UIImage imageNamed:@"group.png"]; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - folderImageViewX = 10; - } else { - folderImageViewX = 8; - } - } else if (section == 1) { - folderImage = [UIImage imageNamed:@"archive.png"]; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - folderImageViewX = 10; - } else { - folderImageViewX = 7; - } - } else { - folderImage = [UIImage imageNamed:@"folder_2.png"]; - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - } else { - folderImageViewX = 7; - } - } - UIImageView *folderImageView = [[UIImageView alloc] initWithImage:folderImage]; - folderImageView.frame = CGRectMake(folderImageViewX, folderImageViewY, 20, 20); - [customView addSubview:folderImageView]; - - if (!self.hasNoSites) { - UIImage *disclosureImage = [UIImage imageNamed:@"disclosure.png"]; - UIImageView *disclosureImageView = [[UIImageView alloc] initWithImage:disclosureImage]; - disclosureImageView.frame = CGRectMake(customView.frame.size.width - 20, disclosureImageViewY, 9.0, 14.0); - [customView addSubview:disclosureImageView]; - } - - UIButton *invisibleHeaderButton = [UIButton buttonWithType:UIButtonTypeCustom]; - invisibleHeaderButton.frame = CGRectMake(0, 0, customView.frame.size.width, customView.frame.size.height); - invisibleHeaderButton.alpha = .1; - invisibleHeaderButton.tag = section; - [invisibleHeaderButton addTarget:self action:@selector(didSelectSectionHeader:) forControlEvents:UIControlEventTouchUpInside]; - [customView addSubview:invisibleHeaderButton]; - - [invisibleHeaderButton addTarget:self action:@selector(sectionTapped:) forControlEvents:UIControlEventTouchDown]; - [invisibleHeaderButton addTarget:self action:@selector(sectionUntapped:) forControlEvents:UIControlEventTouchUpInside]; - [invisibleHeaderButton addTarget:self action:@selector(sectionUntappedOutside:) forControlEvents:UIControlEventTouchUpOutside]; - - [customView setAutoresizingMask:UIViewAutoresizingNone]; - return customView; + return folderTitle; } - (IBAction)sectionTapped:(UIButton *)button { @@ -950,9 +885,9 @@ appDelegate.isSocialRiverView = YES; appDelegate.isRiverView = YES; // add all the feeds from every NON blurblog folder - [appDelegate setActiveFolder:@"All Blurblog Stories"]; + [appDelegate setActiveFolder:@"river_blurblogs"]; for (NSString *folderName in self.activeFeedLocations) { - if ([folderName isEqualToString:@""]) { // remove all blurblugs which is a blank folder name + if ([folderName isEqualToString:@"river_blurblogs"]) { // remove all blurblugs which is a blank folder name NSArray *originalFolder = [appDelegate.dictFolders objectForKey:folderName]; NSArray *folderFeeds = [self.activeFeedLocations objectForKey:folderName]; for (int l=0; l < [folderFeeds count]; l++) { @@ -964,9 +899,9 @@ appDelegate.isSocialRiverView = NO; appDelegate.isRiverView = YES; // add all the feeds from every NON blurblog folder - [appDelegate setActiveFolder:@"All Stories"]; + [appDelegate setActiveFolder:@"everything"]; for (NSString *folderName in self.activeFeedLocations) { - if (![folderName isEqualToString:@""]) { // remove all blurblugs which is a blank folder name + if (![folderName isEqualToString:@"river_blurblogs"]) { NSArray *originalFolder = [appDelegate.dictFolders objectForKey:folderName]; NSArray *folderFeeds = [self.activeFeedLocations objectForKey:folderName]; for (int l=0; l < [folderFeeds count]; l++) { @@ -992,6 +927,35 @@ [appDelegate loadRiverFeedDetailView]; } +- (void)didCollapseFolder:(UIButton *)button { + NSString *folderName; + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + + if (button.tag == 0) { + folderName = @"river_blurblogs"; + } else { + folderName = [appDelegate.dictFoldersArray objectAtIndex:button.tag]; + } + + NSString *collapseKey = [NSString stringWithFormat:@"folderCollapsed:%@", folderName]; + bool isFolderCollapsed = [userPreferences boolForKey:collapseKey]; + + if (isFolderCollapsed) { + // Expand folder + [userPreferences setBool:NO forKey:collapseKey]; + } else { + // Collapse folder + [userPreferences setBool:YES forKey:collapseKey]; + } + [userPreferences synchronize]; + + [self.feedTitlesTable reloadSections:[NSIndexSet indexSetWithIndex:button.tag] + withRowAnimation:UITableViewRowAnimationFade]; + [self.feedTitlesTable beginUpdates]; + [self.feedTitlesTable endUpdates]; + +} + - (void)changeToAllMode { [self.intelligenceControl setSelectedSegmentIndex:0]; NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; @@ -1007,7 +971,7 @@ int selectedSegmentIndex = [self.intelligenceControl selectedSegmentIndex]; - NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; + NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults]; if (selectedSegmentIndex == 0) { hud.labelText = @"All Stories"; [userPreferences setInteger:-1 forKey:@"selectedIntelligence"]; @@ -1017,7 +981,7 @@ int previousLevel = appDelegate.selectedIntelligence; [appDelegate setSelectedIntelligence:0]; [self updateFeedsWithIntelligence:previousLevel newLevel:0]; - [self redrawUnreadCounts]; + [self redrawUnreadCounts]; } self.viewShowingAllFeeds = YES; [self switchSitesUnread]; @@ -1049,7 +1013,7 @@ [self redrawUnreadCounts]; } - [hud hide:YES afterDelay:0.75]; + [hud hide:YES afterDelay:0.5]; // [self.feedTitlesTable reloadData]; } @@ -1147,16 +1111,22 @@ [self calculateFeedLocations:NO]; } - [self.feedTitlesTable beginUpdates]; - if ([deleteIndexPaths count] > 0) { - [self.feedTitlesTable deleteRowsAtIndexPaths:deleteIndexPaths - withRowAnimation:UITableViewRowAnimationNone]; + @try { + [self.feedTitlesTable beginUpdates]; + if ([deleteIndexPaths count] > 0) { + [self.feedTitlesTable deleteRowsAtIndexPaths:deleteIndexPaths + withRowAnimation:UITableViewRowAnimationNone]; + } + if ([insertIndexPaths count] > 0) { + [self.feedTitlesTable insertRowsAtIndexPaths:insertIndexPaths + withRowAnimation:UITableViewRowAnimationNone]; + } + [self.feedTitlesTable endUpdates]; } - if ([insertIndexPaths count] > 0) { - [self.feedTitlesTable insertRowsAtIndexPaths:insertIndexPaths - withRowAnimation:UITableViewRowAnimationNone]; + @catch (NSException *exception) { + NSLog(@"Exception: %@", exception); + [self.feedTitlesTable reloadData]; } - [self.feedTitlesTable endUpdates]; // scrolls to the top and fixes header rendering bug CGPoint offsetOne = CGPointMake(0, 1); @@ -1186,7 +1156,7 @@ id feedId = [folder objectAtIndex:f]; NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId]; - if ([folderName isEqualToString:@""]){ + if ([folderName isEqualToString:@"river_blurblogs"]){ feed = [appDelegate.dictSocialFeeds objectForKey:feedIdStr]; } else { feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; @@ -1201,7 +1171,7 @@ [feedLocations addObject:location]; } else { int maxScore = [NewsBlurViewController computeMaxScoreForFeed:feed]; -// if ([folderName isEqualToString:@""]){ +// if ([folderName isEqualToString:@"river_blurblogs"]){ // NSLog(@"Computing score for %@: %d in %d (markVisible: %d)", // [feed objectForKey:@"feed_title"], maxScore, appDelegate.selectedIntelligence, markVisible); // } @@ -1216,10 +1186,6 @@ } } - if ([folderName isEqualToString:@""]){ -// NSLog(@"feedLocations count is %i: ", [feedLocations count]); - } -// NSLog(@"feedLocations %@", feedLocations); [self.activeFeedLocations setObject:feedLocations forKey:folderName]; } diff --git a/media/ios/Classes/ProfileBadge.m b/media/ios/Classes/ProfileBadge.m index 656817edb..ea5ec967a 100644 --- a/media/ios/Classes/ProfileBadge.m +++ b/media/ios/Classes/ProfileBadge.m @@ -307,12 +307,10 @@ if ([self.followButton.currentTitle isEqualToString:@"Follow"]) { urlString = [NSString stringWithFormat:@"http://%@/social/follow", - NEWSBLUR_URL, - [self.activeProfile objectForKey:@"user_id"]]; + NEWSBLUR_URL]; } else { urlString = [NSString stringWithFormat:@"http://%@/social/unfollow", - NEWSBLUR_URL, - [self.activeProfile objectForKey:@"user_id"]]; + NEWSBLUR_URL]; } NSURL *url = [NSURL URLWithString:urlString]; diff --git a/media/ios/Classes/StoryDetailViewController.h b/media/ios/Classes/StoryDetailViewController.h index 33b09790e..ce87ad283 100644 --- a/media/ios/Classes/StoryDetailViewController.h +++ b/media/ios/Classes/StoryDetailViewController.h @@ -7,13 +7,15 @@ // #import +#import #import "WEPopoverController.h" @class NewsBlurAppDelegate; @class ASIHTTPRequest; @interface StoryDetailViewController : UIViewController - { + { NewsBlurAppDelegate *appDelegate; NSString *activeStoryId; @@ -29,7 +31,7 @@ UIToolbar *bottomPlaceholderToolbar; UIBarButtonItem *buttonBack; Class popoverClass; - + BOOL pullingScrollview; } @property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator; @@ -53,6 +55,7 @@ @property (nonatomic) IBOutlet UIBarButtonItem *originalStoryButton; @property (nonatomic, strong) IBOutlet UIBarButtonItem *subscribeButton; @property (nonatomic) IBOutlet UILabel *noStorySelectedLabel; +@property (nonatomic, assign) BOOL pullingScrollview; - (void)setNextPreviousButtons; diff --git a/media/ios/Classes/StoryDetailViewController.m b/media/ios/Classes/StoryDetailViewController.m index eeadcc59e..1f08b9a7f 100644 --- a/media/ios/Classes/StoryDetailViewController.m +++ b/media/ios/Classes/StoryDetailViewController.m @@ -52,6 +52,7 @@ @synthesize noStorySelectedLabel; @synthesize buttonBack; @synthesize bottomPlaceholderToolbar; +@synthesize pullingScrollview; // private @synthesize inTouchMove; @@ -799,36 +800,88 @@ feed = [appDelegate.dictFeeds objectForKey:feedIdStr]; } - self.feedTitleGradient = [appDelegate makeFeedTitleGradient:feed + self.feedTitleGradient = [appDelegate makeFeedTitleGradient:feed withRect:CGRectMake(0, -1, 1024, 21)]; // 1024 hack for self.webView.frame.size.width self.feedTitleGradient.tag = FEED_TITLE_GRADIENT_TAG; // Not attached yet. Remove old gradients, first. + [self.feedTitleGradient.layer setShadowColor:[[UIColor blackColor] CGColor]]; + [self.feedTitleGradient.layer setShadowOffset:CGSizeMake(0, 0)]; + [self.feedTitleGradient.layer setShadowOpacity:0]; + [self.feedTitleGradient.layer setShadowRadius:12.0]; + for (UIView *subview in self.webView.subviews) { if (subview.tag == FEED_TITLE_GRADIENT_TAG) { [subview removeFromSuperview]; } } - - for (NSObject *aSubView in [self.webView subviews]) { - if ([aSubView isKindOfClass:[UIScrollView class]]) { - UIScrollView * theScrollView = (UIScrollView *)aSubView; - if (appDelegate.isRiverView || appDelegate.isSocialView) { - theScrollView.contentInset = UIEdgeInsetsMake(19, 0, 0, 0); - theScrollView.scrollIndicatorInsets = UIEdgeInsetsMake(19, 0, 0, 0); - } else { - theScrollView.contentInset = UIEdgeInsetsMake(9, 0, 0, 0); - theScrollView.scrollIndicatorInsets = UIEdgeInsetsMake(9, 0, 0, 0); - } - [self.webView insertSubview:feedTitleGradient aboveSubview:theScrollView]; - [theScrollView setContentOffset:CGPointMake(0, (appDelegate.isRiverView || appDelegate.isSocialView) ? -19 : -9) animated:NO]; - - break; - } + + if (appDelegate.isRiverView || appDelegate.isSocialView) { + self.webView.scrollView.contentInset = UIEdgeInsetsMake(20, 0, 0, 0); + self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(20, 0, 0, 0); + } else { + self.webView.scrollView.contentInset = UIEdgeInsetsMake(9, 0, 0, 0); + self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(9, 0, 0, 0); } + [self.webView insertSubview:feedTitleGradient aboveSubview:self.webView.scrollView]; + [self.webView.scrollView setContentOffset:CGPointMake(0, (appDelegate.isRiverView || + appDelegate.isSocialView) ? -20 : -9) + animated:NO]; + [self.webView.scrollView addObserver:self forKeyPath:@"contentOffset" + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + context:nil]; + + [self setNextPreviousButtons]; } +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + + if (keyPath == @"contentOffset") { + if (self.webView.scrollView.contentOffset.y < -20) { + // Pulling + if (!pullingScrollview) { + pullingScrollview = YES; + [self.feedTitleGradient.layer setShadowOpacity:.5]; + [self.webView insertSubview:self.feedTitleGradient belowSubview:self.webView.scrollView]; + + for (id subview in self.webView.scrollView.subviews) { + UIImageView *imgView = [subview isKindOfClass:[UIImageView class]] ? + (UIImageView*)subview : nil; + // image views whose image is 1px wide are shadow images, hide them + if (imgView && imgView.image.size.width == 1) { + imgView.hidden = YES; + } + } + } + float y = -1 * self.webView.scrollView.contentOffset.y - 20 - 1; + self.feedTitleGradient.frame = CGRectMake(0, y, + self.feedTitleGradient.frame.size.width, + self.feedTitleGradient.frame.size.height); + } else { + // Normal reading + if (pullingScrollview) { + pullingScrollview = NO; + [self.feedTitleGradient.layer setShadowOpacity:0]; + [self.webView insertSubview:self.feedTitleGradient aboveSubview:self.webView.scrollView]; + + self.feedTitleGradient.frame = CGRectMake(0, -1, + self.feedTitleGradient.frame.size.width, + self.feedTitleGradient.frame.size.height); + + for (id subview in self.webView.scrollView.subviews) { + UIImageView *imgView = [subview isKindOfClass:[UIImageView class]] ? + (UIImageView*)subview : nil; + // image views whose image is 1px wide are shadow images, hide them + if (imgView && imgView.image.size.width == 1) { + imgView.hidden = NO; + } + } + } + } + } +} + - (void)setActiveStory { self.activeStoryId = [appDelegate.activeStory objectForKey:@"id"]; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { @@ -994,8 +1047,8 @@ shouldStartLoadWithRequest:(NSURLRequest *)request if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { // only adjust for the bar if user is scrolling if (appDelegate.isRiverView || appDelegate.isSocialView) { - if (self.webView.scrollView.contentOffset.y == -19) { - y = y + 19; + if (self.webView.scrollView.contentOffset.y == -20) { + y = y + 20; } } else { if (self.webView.scrollView.contentOffset.y == -9) { @@ -1231,11 +1284,11 @@ shouldStartLoadWithRequest:(NSURLRequest *)request } - (void)finishMarkAsRead:(ASIHTTPRequest *)request { - NSString *responseString = [request responseString]; - NSDictionary *results = [[NSDictionary alloc] - initWithDictionary:[responseString JSONValue]]; - NSLog(@"results in mark as read is %@", results); -} +// NSString *responseString = [request responseString]; +// NSDictionary *results = [[NSDictionary alloc] +// initWithDictionary:[responseString JSONValue]]; +// NSLog(@"results in mark as read is %@", results); +} # pragma mark # pragma mark Subscribing to blurblog diff --git a/media/ios/Classes/UnreadCountView.h b/media/ios/Classes/UnreadCountView.h new file mode 100644 index 000000000..cf921f3af --- /dev/null +++ b/media/ios/Classes/UnreadCountView.h @@ -0,0 +1,32 @@ +// +// UnreadCountView.h +// NewsBlur +// +// Created by Samuel Clay on 10/3/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import +#import "NewsBlurAppDelegate.h" + +@class NewsBlurAppDelegate; + +@interface UnreadCountView : UIView + +typedef enum { + NBFeedListFeed = 1, + NBFeedListSocial = 2, + NBFeedListFolder = 3 +} NBFeedListType; + +@property (nonatomic) NewsBlurAppDelegate *appDelegate; +@property (assign, nonatomic) int psWidth; +@property (assign, nonatomic) int psPadding; +@property (assign, nonatomic) int ntWidth; +@property (assign, nonatomic) int ntPadding; +@property (assign, nonatomic) CGRect rect; + +- (void)drawInRect:(CGRect)r ps:(int)ps nt:(int)nt listType:(NBFeedListType)listType; +- (int)offsetWidth; + +@end diff --git a/media/ios/Classes/UnreadCountView.m b/media/ios/Classes/UnreadCountView.m new file mode 100644 index 000000000..354b91487 --- /dev/null +++ b/media/ios/Classes/UnreadCountView.m @@ -0,0 +1,127 @@ +// +// UnreadCountView.m +// NewsBlur +// +// Created by Samuel Clay on 10/3/12. +// Copyright (c) 2012 NewsBlur. All rights reserved. +// + +#import "UnreadCountView.h" +#import "UIView+TKCategory.h" + +static UIFont *indicatorFont = nil; +static UIColor *indicatorWhiteColor = nil; +static UIColor *indicatorBlackColor = nil; +static UIColor *positiveBackgroundColor = nil; +static UIColor *neutralBackgroundColor = nil; +static UIColor *negativeBackgroundColor = nil; + +@implementation UnreadCountView + +@synthesize appDelegate; +@synthesize psWidth, psPadding, ntWidth, ntPadding; +@synthesize rect; + ++ (void) initialize { + if (self == [UnreadCountView class]) { + indicatorFont = [UIFont boldSystemFontOfSize:12]; + indicatorWhiteColor = [UIColor whiteColor]; + indicatorBlackColor = [UIColor blackColor]; + + UIColor *ps = UIColorFromRGB(0x3B7613); + UIColor *nt = UIColorFromRGB(0xF9C72A); + UIColor *ng = UIColorFromRGB(0xCC2A2E); + positiveBackgroundColor = ps; + neutralBackgroundColor = nt; + negativeBackgroundColor = ng; + // UIColor *psGrad = UIColorFromRGB(0x559F4D); + // UIColor *ntGrad = UIColorFromRGB(0xE4AB00); + // UIColor *ngGrad = UIColorFromRGB(0x9B181B); + // const CGFloat* psTop = CGColorGetComponents(ps.CGColor); + // const CGFloat* psBot = CGColorGetComponents(psGrad.CGColor); + // CGFloat psGradient[] = { + // psTop[0], psTop[1], psTop[2], psTop[3], + // psBot[0], psBot[1], psBot[2], psBot[3] + // }; + // psColors = psGradient; + } +} + +- (void)drawInRect:(CGRect)r ps:(int)ps nt:(int)nt listType:(NBFeedListType)listType { + rect = CGRectInset(r, 12, 12); + rect.size.width -= 18; // Scrollbar padding + + psWidth = ps == 0 ? 0 : ps < 10 ? 14 : ps < 100 ? 22 : 28; + ntWidth = nt == 0 ? 0 : nt < 10 ? 14 : nt < 100 ? 22 : 28; + + int psOffset = ps == 0 ? 0 : psWidth - 20; + int ntOffset = nt == 0 ? 0 : ntWidth - 20; + + psPadding = ps == 0 ? 0 : 2; + ntPadding = nt == 0 ? 0 : 2; + + if (ps > 0){ + [positiveBackgroundColor set]; + CGRect rr; + + if (listType == NBFeedListSocial) { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + rr = CGRectMake(rect.size.width + rect.origin.x - psOffset, 14, psWidth, 17); + } else { + rr = CGRectMake(rect.size.width + rect.origin.x - psOffset, 10, psWidth, 17); + } + } else { + rr = CGRectMake(rect.size.width + rect.origin.x - psOffset, 7, psWidth, 17); + } + + [UIView drawRoundRectangleInRect:rr withRadius:4]; + + [indicatorWhiteColor set]; + + NSString *psStr = [NSString stringWithFormat:@"%d", ps]; + CGSize size = [psStr sizeWithFont:indicatorFont]; + float x_pos = (rr.size.width - size.width) / 2; + float y_pos = (rr.size.height - size.height) / 2; + [psStr + drawAtPoint:CGPointMake(rr.origin.x + x_pos, rr.origin.y + y_pos) + withFont:indicatorFont]; + } + + if (nt > 0 && appDelegate.selectedIntelligence <= 0){ + [neutralBackgroundColor set]; + + CGRect rr; + if (listType == NBFeedListSocial) { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntOffset, 14, ntWidth, 17); + } else { + rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntOffset, 10, ntWidth, 17); + } + } else { + rr = CGRectMake(rect.size.width + rect.origin.x - psWidth - psPadding - ntOffset, 7, ntWidth, 17); + } + + [UIView drawRoundRectangleInRect:rr withRadius:4]; + // [UIView drawLinearGradientInRect:rr colors:ntColors]; + + [indicatorBlackColor set]; + + NSString *ntStr = [NSString stringWithFormat:@"%d", nt]; + CGSize size = [ntStr sizeWithFont:indicatorFont]; + float x_pos = (rr.size.width - size.width) / 2; + float y_pos = (rr.size.height - size.height) / 2; + [ntStr + drawAtPoint:CGPointMake(rr.origin.x + x_pos, rr.origin.y + y_pos) + withFont:indicatorFont]; + + if (listType == NBFeedListFolder) { + NSLog(@"Drawing: %@", NSStringFromCGRect(r)); + } + } +} + +- (int)offsetWidth { + return rect.size.width - psWidth - psPadding - ntWidth - ntPadding; +} + +@end diff --git a/media/ios/Entitlements.entitlements b/media/ios/Entitlements.entitlements index fe238e5c4..c468210c7 100644 --- a/media/ios/Entitlements.entitlements +++ b/media/ios/Entitlements.entitlements @@ -5,7 +5,7 @@ application-identifier $(AppIdentifierPrefix)$(CFBundleIdentifier) get-task-allow - + keychain-access-groups $(AppIdentifierPrefix)$(CFBundleIdentifier) diff --git a/media/ios/NewsBlur.xcodeproj/project.pbxproj b/media/ios/NewsBlur.xcodeproj/project.pbxproj index 0a26fcef5..8d4e6a3e2 100755 --- a/media/ios/NewsBlur.xcodeproj/project.pbxproj +++ b/media/ios/NewsBlur.xcodeproj/project.pbxproj @@ -102,7 +102,6 @@ 43A4C3D715B00966008787B5 /* ABTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A4C3BA15B00966008787B5 /* ABTableViewCell.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 43A4C3D815B00966008787B5 /* Base64.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A4C3BC15B00966008787B5 /* Base64.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 43A4C3DA15B00966008787B5 /* GTMNString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A4C3C015B00966008787B5 /* GTMNString+HTML.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 43A4C3DB15B00966008787B5 /* libTestFlight.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 43A4C3C115B00966008787B5 /* libTestFlight.a */; }; 43A4C3DC15B00966008787B5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A4C3C215B00966008787B5 /* main.m */; }; 43A4C3DD15B00966008787B5 /* MBProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A4C3C415B00966008787B5 /* MBProgressHUD.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 43A4C3E115B00966008787B5 /* NSString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = 43A4C3CD15B00966008787B5 /* NSString+HTML.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; @@ -367,6 +366,11 @@ FFAD4971144A386100BA6919 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FFAD4970144A386100BA6919 /* libz.dylib */; }; FFD1D7311459B63500E46F89 /* BaseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FFD1D7301459B63500E46F89 /* BaseViewController.m */; }; FFD887F01445F1E800385399 /* AddSiteAutocompleteCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FFD887EE1445F1E800385399 /* AddSiteAutocompleteCell.m */; }; + FFDE35CC161B8F870034BFDE /* FolderTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = FFDE35CB161B8F870034BFDE /* FolderTitleView.m */; }; + FFDE35D2161B9E600034BFDE /* disclosure_border.png in Resources */ = {isa = PBXBuildFile; fileRef = FFDE35D1161B9E600034BFDE /* disclosure_border.png */; }; + FFDE35D6161CCABC0034BFDE /* folder_collapsed.png in Resources */ = {isa = PBXBuildFile; fileRef = FFDE35D4161CCABC0034BFDE /* folder_collapsed.png */; }; + FFDE35D7161CCABC0034BFDE /* folder_collapsed@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FFDE35D5161CCABC0034BFDE /* folder_collapsed@2x.png */; }; + FFDE35DA161D12250034BFDE /* UnreadCountView.m in Sources */ = {isa = PBXBuildFile; fileRef = FFDE35D9161D12250034BFDE /* UnreadCountView.m */; }; FFE5322F144C8AC300ACFDE0 /* Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = FFE5322E144C8AC300ACFDE0 /* Utilities.m */; }; /* End PBXBuildFile section */ @@ -498,7 +502,6 @@ 43A4C3BE15B00966008787B5 /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMDefines.h; path = "Other Sources/GTMDefines.h"; sourceTree = ""; }; 43A4C3BF15B00966008787B5 /* GTMNString+HTML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "GTMNString+HTML.h"; path = "Other Sources/GTMNString+HTML.h"; sourceTree = ""; }; 43A4C3C015B00966008787B5 /* GTMNString+HTML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "GTMNString+HTML.m"; path = "Other Sources/GTMNString+HTML.m"; sourceTree = ""; }; - 43A4C3C115B00966008787B5 /* libTestFlight.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libTestFlight.a; path = "Other Sources/libTestFlight.a"; sourceTree = ""; }; 43A4C3C215B00966008787B5 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = "Other Sources/main.m"; sourceTree = ""; }; 43A4C3C315B00966008787B5 /* MBProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MBProgressHUD.h; path = "Other Sources/MBProgressHUD.h"; sourceTree = ""; }; 43A4C3C415B00966008787B5 /* MBProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MBProgressHUD.m; path = "Other Sources/MBProgressHUD.m"; sourceTree = ""; }; @@ -882,6 +885,13 @@ FFD1D7301459B63500E46F89 /* BaseViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseViewController.m; sourceTree = ""; }; FFD887ED1445F1E800385399 /* AddSiteAutocompleteCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddSiteAutocompleteCell.h; sourceTree = ""; }; FFD887EE1445F1E800385399 /* AddSiteAutocompleteCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddSiteAutocompleteCell.m; sourceTree = ""; }; + FFDE35CA161B8F870034BFDE /* FolderTitleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FolderTitleView.h; sourceTree = ""; }; + FFDE35CB161B8F870034BFDE /* FolderTitleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FolderTitleView.m; sourceTree = ""; }; + FFDE35D1161B9E600034BFDE /* disclosure_border.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = disclosure_border.png; sourceTree = ""; }; + FFDE35D4161CCABC0034BFDE /* folder_collapsed.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = folder_collapsed.png; sourceTree = ""; }; + FFDE35D5161CCABC0034BFDE /* folder_collapsed@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "folder_collapsed@2x.png"; sourceTree = ""; }; + FFDE35D8161D12250034BFDE /* UnreadCountView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnreadCountView.h; sourceTree = ""; }; + FFDE35D9161D12250034BFDE /* UnreadCountView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UnreadCountView.m; sourceTree = ""; }; FFE5322D144C8AC300ACFDE0 /* Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; FFE5322E144C8AC300ACFDE0 /* Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Utilities.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -903,7 +913,6 @@ 78095E3F128EF35400230C8E /* CFNetwork.framework in Frameworks */, 78095E43128EF37E00230C8E /* MobileCoreServices.framework in Frameworks */, 78095E45128EF37E00230C8E /* SystemConfiguration.framework in Frameworks */, - 43A4C3DB15B00966008787B5 /* libTestFlight.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -975,7 +984,6 @@ 43A4C3BE15B00966008787B5 /* GTMDefines.h */, 43A4C3BF15B00966008787B5 /* GTMNString+HTML.h */, 43A4C3C015B00966008787B5 /* GTMNString+HTML.m */, - 43A4C3C115B00966008787B5 /* libTestFlight.a */, 43A4C3C215B00966008787B5 /* main.m */, 43A4C3C315B00966008787B5 /* MBProgressHUD.h */, 43A4C3C415B00966008787B5 /* MBProgressHUD.m */, @@ -1119,6 +1127,10 @@ FFD887EE1445F1E800385399 /* AddSiteAutocompleteCell.m */, FF5EA47C143B691000B7563D /* AddSiteViewController.h */, FF5EA47D143B691000B7563D /* AddSiteViewController.m */, + FFDE35CA161B8F870034BFDE /* FolderTitleView.h */, + FFDE35CB161B8F870034BFDE /* FolderTitleView.m */, + FFDE35D8161D12250034BFDE /* UnreadCountView.h */, + FFDE35D9161D12250034BFDE /* UnreadCountView.m */, ); name = Feeds; sourceTree = ""; @@ -1139,6 +1151,9 @@ 431B857615A132B600DCE497 /* Images */ = { isa = PBXGroup; children = ( + FFDE35D4161CCABC0034BFDE /* folder_collapsed.png */, + FFDE35D5161CCABC0034BFDE /* folder_collapsed@2x.png */, + FFDE35D1161B9E600034BFDE /* disclosure_border.png */, 43BC458E15D9F75F00205B69 /* facebook_button_on.png */, 43BC458F15D9F75F00205B69 /* facebook_button_on@2x.png */, 43BC459015D9F75F00205B69 /* facebook_button.png */, @@ -2108,6 +2123,9 @@ 43BC459515D9F76000205B69 /* facebook_button@2x.png in Resources */, FF546DF71602930100948020 /* Default-568h@2x.png in Resources */, FF546DF9160298E500948020 /* fleuron@2x.png in Resources */, + FFDE35D2161B9E600034BFDE /* disclosure_border.png in Resources */, + FFDE35D6161CCABC0034BFDE /* folder_collapsed.png in Resources */, + FFDE35D7161CCABC0034BFDE /* folder_collapsed@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2253,6 +2271,8 @@ 438FEDE815D5B15F00E3B3C9 /* FollowGrid.m in Sources */, 43B232C015D5F61700D035B4 /* AuthorizeServicesViewController.m in Sources */, 43CE0F5F15DADB7F00608ED8 /* SiteCell.m in Sources */, + FFDE35CC161B8F870034BFDE /* FolderTitleView.m in Sources */, + FFDE35DA161D12250034BFDE /* UnreadCountView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2269,7 +2289,7 @@ ); CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_ENTITLEMENTS = Entitlements.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Developer: Samuel Clay (G9HFWP68T7)"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; @@ -2286,6 +2306,7 @@ "\"$(SRCROOT)\"", "\"$(SRCROOT)/Other Sources\"", ); + OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( "-all_load", "-ObjC", @@ -2308,7 +2329,7 @@ ); CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_ENTITLEMENTS = Entitlements.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution: NewsBlur, Inc."; + CODE_SIGN_IDENTITY = "iPhone Developer: Samuel Clay (G9HFWP68T7)"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: NewsBlur, Inc."; COPY_PHASE_STRIP = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -2328,7 +2349,7 @@ "-all_load", ); PRODUCT_NAME = NewsBlur; - PROVISIONING_PROFILE = "3674B4BA-9006-4099-A608-673EC3103906"; + PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = "3674B4BA-9006-4099-A608-673EC3103906"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/media/ios/NewsBlur.xcodeproj/xcuserdata/sclay.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist b/media/ios/NewsBlur.xcodeproj/xcuserdata/sclay.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist index 9021cf163..7392392a2 100644 --- a/media/ios/NewsBlur.xcodeproj/xcuserdata/sclay.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist +++ b/media/ios/NewsBlur.xcodeproj/xcuserdata/sclay.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist @@ -2,6 +2,21 @@ + + + + + + + + diff --git a/media/ios/Other Sources/libTestFlight.a b/media/ios/Other Sources/libTestFlight.a deleted file mode 100644 index c4657a423..000000000 Binary files a/media/ios/Other Sources/libTestFlight.a and /dev/null differ diff --git a/media/ios/Resources-iPhone/AddSiteViewController.xib b/media/ios/Resources-iPhone/AddSiteViewController.xib index 1259de313..8bf895909 100644 --- a/media/ios/Resources-iPhone/AddSiteViewController.xib +++ b/media/ios/Resources-iPhone/AddSiteViewController.xib @@ -1,30 +1,30 @@ - 1296 - 11E53 - 2182 - 1138.47 - 569.00 + 1536 + 12C54 + 2840 + 1187.34 + 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 1181 + 1926 YES - IBUIBarButtonItem - IBUISegmentedControl - IBUIPickerView - IBUILabel - IBUITextField IBProxyObject + IBUIActivityIndicatorView + IBUIBarButtonItem + IBUIImageView + IBUILabel IBUINavigationBar IBUINavigationItem + IBUIPickerView IBUIScrollView - IBUIView - IBUIImageView + IBUISegmentedControl IBUITableView - IBUIActivityIndicatorView + IBUITextField + IBUIView YES @@ -51,7 +51,7 @@ YES - 292 + 277 {{0, 44}, {320, 416}} @@ -70,7 +70,6 @@ {{111, 294}, {189, 52}} - _NS:311 NO YES @@ -86,6 +85,7 @@ 1 MCAwIDAAA + darkTextColor 1 10 @@ -127,6 +127,7 @@ 1 + 280 @@ -338,7 +339,7 @@ - 268 + 276 YES @@ -351,7 +352,8 @@ _NS:392 1 - MC42IDAuNiAwLjYgMC42AA + MSAxIDEgMC42AA + lightTextColor YES IBCocoaTouchFramework @@ -5733,10 +5735,10 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE YES YES - tapCategoryButton: - tapGoogleReaderButton - tapNewsBlurButton: + tapFacebookButton tapNextButton + tapTwitterButton + toggleAutoFollowFriends: YES @@ -5750,124 +5752,96 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE YES YES - tapCategoryButton: - tapGoogleReaderButton - tapNewsBlurButton: + tapFacebookButton tapNextButton + tapTwitterButton + toggleAutoFollowFriends: YES - tapCategoryButton: - id - - - tapGoogleReaderButton - id - - - tapNewsBlurButton: + tapFacebookButton id tapNextButton id + + tapTwitterButton + id + + + toggleAutoFollowFriends: + id + YES YES - addFriendsView - addNewsBlurView - addSitesView appDelegate - googleReaderButton - logo + facebookActivityIndicator + facebookButton + friendsLabel nextButton - previousButton - toolbar - toolbarTitle - welcomeView + twitterActivityIndicator + twitterButton YES - UIView - UIView - UIView NewsBlurAppDelegate + UIActivityIndicatorView UIButton - UIImageView + UILabel UIBarButtonItem - UIBarButtonItem - UIToolbar + UIActivityIndicatorView UIButton - UIView YES YES - addFriendsView - addNewsBlurView - addSitesView appDelegate - googleReaderButton - logo + facebookActivityIndicator + facebookButton + friendsLabel nextButton - previousButton - toolbar - toolbarTitle - welcomeView + twitterActivityIndicator + twitterButton YES - - addFriendsView - UIView - - - addNewsBlurView - UIView - - - addSitesView - UIView - appDelegate NewsBlurAppDelegate - googleReaderButton + facebookActivityIndicator + UIActivityIndicatorView + + + facebookButton UIButton - logo - UIImageView + friendsLabel + UILabel nextButton UIBarButtonItem - previousButton - UIBarButtonItem + twitterActivityIndicator + UIActivityIndicatorView - toolbar - UIToolbar - - - toolbarTitle + twitterButton UIButton - - welcomeView - UIView - @@ -5882,38 +5856,27 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE YES YES - tapCategoryButton: - tapGoogleReaderButton tapNewsBlurButton: tapNextButton + tapPopularButton: YES id id id - id YES YES - tapCategoryButton: - tapGoogleReaderButton tapNewsBlurButton: tapNextButton + tapPopularButton: YES - - tapCategoryButton: - id - - - tapGoogleReaderButton - id - tapNewsBlurButton: id @@ -5922,101 +5885,49 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE tapNextButton id + + tapPopularButton: + id + YES YES - addFriendsView - addNewsBlurView - addSitesView appDelegate - googleReaderButton - logo + instructionsLabel nextButton - previousButton - toolbar - toolbarTitle - welcomeView YES - UIView - UIView - UIView NewsBlurAppDelegate - UIButton - UIImageView + UILabel UIBarButtonItem - UIBarButtonItem - UIToolbar - UIButton - UIView YES YES - addFriendsView - addNewsBlurView - addSitesView appDelegate - googleReaderButton - logo + instructionsLabel nextButton - previousButton - toolbar - toolbarTitle - welcomeView YES - - addFriendsView - UIView - - - addNewsBlurView - UIView - - - addSitesView - UIView - appDelegate NewsBlurAppDelegate - googleReaderButton - UIButton - - - logo - UIImageView + instructionsLabel + UILabel nextButton UIBarButtonItem - - previousButton - UIBarButtonItem - - - toolbar - UIToolbar - - - toolbarTitle - UIButton - - - welcomeView - UIView - @@ -6028,143 +5939,87 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE FirstTimeUserAddSitesViewController UIViewController - YES - - YES - tapCategoryButton: - tapGoogleReaderButton - tapNewsBlurButton: - tapNextButton - - - YES - id - id - id - id - + tapNextButton + id - YES - - YES - tapCategoryButton: - tapGoogleReaderButton - tapNewsBlurButton: - tapNextButton - - - YES - - tapCategoryButton: - id - - - tapGoogleReaderButton - id - - - tapNewsBlurButton: - id - - - tapNextButton - id - + tapNextButton + + tapNextButton + id YES YES - addFriendsView - addNewsBlurView - addSitesView + activityIndicator appDelegate + categoriesTable googleReaderButton - logo + googleReaderButtonWrapper + instructionLabel nextButton - previousButton - toolbar - toolbarTitle - welcomeView + scrollView YES - UIView - UIView - UIView + UIActivityIndicatorView NewsBlurAppDelegate - UIButton - UIImageView - UIBarButtonItem - UIBarButtonItem - UIToolbar + UITableView UIButton UIView + UILabel + UIBarButtonItem + UIScrollView YES YES - addFriendsView - addNewsBlurView - addSitesView + activityIndicator appDelegate + categoriesTable googleReaderButton - logo + googleReaderButtonWrapper + instructionLabel nextButton - previousButton - toolbar - toolbarTitle - welcomeView + scrollView YES - addFriendsView - UIView - - - addNewsBlurView - UIView - - - addSitesView - UIView + activityIndicator + UIActivityIndicatorView appDelegate NewsBlurAppDelegate + + categoriesTable + UITableView + googleReaderButton UIButton - logo - UIImageView + googleReaderButtonWrapper + UIView + + + instructionLabel + UILabel nextButton UIBarButtonItem - previousButton - UIBarButtonItem - - - toolbar - UIToolbar - - - toolbarTitle - UIButton - - - welcomeView - UIView + scrollView + UIScrollView @@ -6192,12 +6047,16 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE YES appDelegate + footer + header logo nextButton YES NewsBlurAppDelegate + UILabel + UILabel UIImageView UIBarButtonItem @@ -6207,6 +6066,8 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE YES appDelegate + footer + header logo nextButton @@ -6216,6 +6077,14 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE appDelegate NewsBlurAppDelegate + + footer + UILabel + + + header + UILabel + logo UIImageView @@ -6371,57 +6240,6 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE ./Classes/FriendsListViewController.h - - GoogleReaderViewController - UIViewController - - tapCancelButton: - id - - - tapCancelButton: - - tapCancelButton: - id - - - - YES - - YES - appDelegate - webView - - - YES - NewsBlurAppDelegate - UIWebView - - - - YES - - YES - appDelegate - webView - - - YES - - appDelegate - NewsBlurAppDelegate - - - webView - UIWebView - - - - - IBProjectSource - ./Classes/GoogleReaderViewController.h - - InteractionsModule UIView @@ -6817,7 +6635,6 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE fontSettingsViewController friendsListViewController ftuxNavigationController - googleReaderViewController loginViewController masterContainerViewController moveSiteViewController @@ -6844,7 +6661,6 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE FontSettingsViewController FriendsListViewController UINavigationController - GoogleReaderViewController LoginViewController NBContainerViewController MoveSiteViewController @@ -6874,7 +6690,6 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE fontSettingsViewController friendsListViewController ftuxNavigationController - googleReaderViewController loginViewController masterContainerViewController moveSiteViewController @@ -6943,10 +6758,6 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE ftuxNavigationController UINavigationController - - googleReaderViewController - GoogleReaderViewController - loginViewController LoginViewController @@ -7057,6 +6868,8 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE homeButton innerView intelligenceControl + noFocusMessage + toolbarLeftMargin YES @@ -7067,6 +6880,8 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE UIBarButtonItem UIView UISegmentedControl + UIView + UIBarButtonItem @@ -7080,6 +6895,8 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE homeButton innerView intelligenceControl + noFocusMessage + toolbarLeftMargin YES @@ -7111,6 +6928,14 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE intelligenceControl UISegmentedControl + + noFocusMessage + UIView + + + toolbarLeftMargin + UIBarButtonItem + @@ -7579,7 +7404,7 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE IBCocoaTouchFramework com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 @@ -7591,6 +7416,6 @@ AAgAAAAIAAIACAACAAAAAgAAAAEAAQABAAE Background.png {320, 480} - 1181 + 1926 diff --git a/media/ios/Resources-iPhone/FeedDetailViewController.xib b/media/ios/Resources-iPhone/FeedDetailViewController.xib index 3144e63d2..66ad7b0b6 100644 --- a/media/ios/Resources-iPhone/FeedDetailViewController.xib +++ b/media/ios/Resources-iPhone/FeedDetailViewController.xib @@ -1,22 +1,22 @@ - 1296 - 11E53 - 2182 - 1138.47 - 569.00 + 1536 + 12C54 + 2840 + 1187.34 + 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 1181 + 1926 YES + IBProxyObject IBUIBarButtonItem IBUITableView IBUIToolbar IBUIView - IBProxyObject YES @@ -38,7 +38,7 @@ - 256 + 274 YES @@ -46,7 +46,6 @@ 274 {320, 392} - 3 @@ -72,7 +71,6 @@ 266 {{0, 392}, {320, 44}} - 1 @@ -113,7 +111,6 @@ {{0, 44}, {320, 436}} - @@ -309,2201 +306,12 @@ 63 - - - YES - - ActivityModule - UIView - - IBProjectSource - ./Classes/ActivityModule.h - - - - AddSiteViewController - UIViewController - - YES - - YES - addFolder - addSite - checkSiteAddress - doAddButton - doCancelButton - selectAddTypeSignup - - - YES - id - id - id - id - id - id - - - - YES - - YES - addFolder - addSite - checkSiteAddress - doAddButton - doCancelButton - selectAddTypeSignup - - - YES - - addFolder - id - - - addSite - id - - - checkSiteAddress - id - - - doAddButton - id - - - doCancelButton - id - - - selectAddTypeSignup - id - - - - - YES - - YES - activityIndicator - addButton - addFolderInput - addTypeControl - addingLabel - appDelegate - cancelButton - errorLabel - folderPicker - inFolderInput - navBar - siteActivityIndicator - siteAddressInput - siteScrollView - siteTable - - - YES - UIActivityIndicatorView - UIBarButtonItem - UITextField - UISegmentedControl - UILabel - NewsBlurAppDelegate - UIBarButtonItem - UILabel - UIPickerView - UITextField - UINavigationBar - UIActivityIndicatorView - UITextField - UIScrollView - UITableView - - - - YES - - YES - activityIndicator - addButton - addFolderInput - addTypeControl - addingLabel - appDelegate - cancelButton - errorLabel - folderPicker - inFolderInput - navBar - siteActivityIndicator - siteAddressInput - siteScrollView - siteTable - - - YES - - activityIndicator - UIActivityIndicatorView - - - addButton - UIBarButtonItem - - - addFolderInput - UITextField - - - addTypeControl - UISegmentedControl - - - addingLabel - UILabel - - - appDelegate - NewsBlurAppDelegate - - - cancelButton - UIBarButtonItem - - - errorLabel - UILabel - - - folderPicker - UIPickerView - - - inFolderInput - UITextField - - - navBar - UINavigationBar - - - siteActivityIndicator - UIActivityIndicatorView - - - siteAddressInput - UITextField - - - siteScrollView - UIScrollView - - - siteTable - UITableView - - - - - IBProjectSource - ./Classes/AddSiteViewController.h - - - - BaseViewController - UIViewController - - IBProjectSource - ./Classes/BaseViewController.h - - - - DashboardViewController - UIViewController - - YES - - YES - doLogout: - tapSegmentedButton: - - - YES - id - id - - - - YES - - YES - doLogout: - tapSegmentedButton: - - - YES - - doLogout: - id - - - tapSegmentedButton: - id - - - - - YES - - YES - activitiesModule - appDelegate - feedbackWebView - interactionsModule - segmentedButton - toolbar - topToolbar - - - YES - ActivityModule - NewsBlurAppDelegate - UIWebView - InteractionsModule - UISegmentedControl - UIToolbar - UIToolbar - - - - YES - - YES - activitiesModule - appDelegate - feedbackWebView - interactionsModule - segmentedButton - toolbar - topToolbar - - - YES - - activitiesModule - ActivityModule - - - appDelegate - NewsBlurAppDelegate - - - feedbackWebView - UIWebView - - - interactionsModule - InteractionsModule - - - segmentedButton - UISegmentedControl - - - toolbar - UIToolbar - - - topToolbar - UIToolbar - - - - - IBProjectSource - ./Classes/DashboardViewController.h - - - - FeedDetailViewController - BaseViewController - - YES - - YES - doOpenMarkReadActionSheet: - doOpenSettingsActionSheet - selectIntelligence - - - YES - id - id - id - - - - YES - - YES - doOpenMarkReadActionSheet: - doOpenSettingsActionSheet - selectIntelligence - - - YES - - doOpenMarkReadActionSheet: - id - - - doOpenSettingsActionSheet - id - - - selectIntelligence - id - - - - - YES - - YES - appDelegate - feedMarkReadButton - feedScoreSlider - feedViewToolbar - intelligenceControl - settingsButton - storyTitlesTable - - - YES - NewsBlurAppDelegate - UIBarButtonItem - UISlider - UIToolbar - UISegmentedControl - UIBarButtonItem - UITableView - - - - YES - - YES - appDelegate - feedMarkReadButton - feedScoreSlider - feedViewToolbar - intelligenceControl - settingsButton - storyTitlesTable - - - YES - - appDelegate - NewsBlurAppDelegate - - - feedMarkReadButton - UIBarButtonItem - - - feedScoreSlider - UISlider - - - feedViewToolbar - UIToolbar - - - intelligenceControl - UISegmentedControl - - - settingsButton - UIBarButtonItem - - - storyTitlesTable - UITableView - - - - - IBProjectSource - ./Classes/FeedDetailViewController.h - - - - FeedsMenuViewController - UIViewController - - YES - - YES - appDelegate - menuTableView - - - YES - NewsBlurAppDelegate - UITableView - - - - YES - - YES - appDelegate - menuTableView - - - YES - - appDelegate - NewsBlurAppDelegate - - - menuTableView - UITableView - - - - - IBProjectSource - ./Classes/FeedsMenuViewController.h - - - - FindSitesViewController - UIViewController - - YES - - YES - appDelegate - sitesSearchBar - sitesTable - - - YES - NewsBlurAppDelegate - UISearchBar - UITableView - - - - YES - - YES - appDelegate - sitesSearchBar - sitesTable - - - YES - - appDelegate - NewsBlurAppDelegate - - - sitesSearchBar - UISearchBar - - - sitesTable - UITableView - - - - - IBProjectSource - ./Classes/FindSitesViewController.h - - - - FirstTimeUserAddFriendsViewController - UIViewController - - YES - - YES - tapFacebookButton - tapNextButton - tapTwitterButton - toggleAutoFollowFriends: - - - YES - id - id - id - id - - - - YES - - YES - tapFacebookButton - tapNextButton - tapTwitterButton - toggleAutoFollowFriends: - - - YES - - tapFacebookButton - id - - - tapNextButton - id - - - tapTwitterButton - id - - - toggleAutoFollowFriends: - id - - - - - YES - - YES - appDelegate - facebookActivityIndicator - facebookButton - friendsLabel - nextButton - twitterActivityIndicator - twitterButton - - - YES - NewsBlurAppDelegate - UIActivityIndicatorView - UIButton - UILabel - UIBarButtonItem - UIActivityIndicatorView - UIButton - - - - YES - - YES - appDelegate - facebookActivityIndicator - facebookButton - friendsLabel - nextButton - twitterActivityIndicator - twitterButton - - - YES - - appDelegate - NewsBlurAppDelegate - - - facebookActivityIndicator - UIActivityIndicatorView - - - facebookButton - UIButton - - - friendsLabel - UILabel - - - nextButton - UIBarButtonItem - - - twitterActivityIndicator - UIActivityIndicatorView - - - twitterButton - UIButton - - - - - IBProjectSource - ./Classes/FirstTimeUserAddFriendsViewController.h - - - - FirstTimeUserAddNewsBlurViewController - UIViewController - - YES - - YES - tapNewsBlurButton: - tapNextButton - tapPopularButton: - - - YES - id - id - id - - - - YES - - YES - tapNewsBlurButton: - tapNextButton - tapPopularButton: - - - YES - - tapNewsBlurButton: - id - - - tapNextButton - id - - - tapPopularButton: - id - - - - - YES - - YES - appDelegate - instructionsLabel - nextButton - - - YES - NewsBlurAppDelegate - UIButton - UIBarButtonItem - - - - YES - - YES - appDelegate - instructionsLabel - nextButton - - - YES - - appDelegate - NewsBlurAppDelegate - - - instructionsLabel - UIButton - - - nextButton - UIBarButtonItem - - - - - IBProjectSource - ./Classes/FirstTimeUserAddNewsBlurViewController.h - - - - FirstTimeUserAddSitesViewController - UIViewController - - YES - - YES - tapCategoryButton: - tapGoogleReaderButton - tapNextButton - - - YES - id - id - id - - - - YES - - YES - tapCategoryButton: - tapGoogleReaderButton - tapNextButton - - - YES - - tapCategoryButton: - id - - - tapGoogleReaderButton - id - - - tapNextButton - id - - - - - YES - - YES - activityIndicator - appDelegate - googleReaderButton - googleReaderButtonWrapper - instructionLabel - nextButton - - - YES - UIActivityIndicatorView - NewsBlurAppDelegate - UIButton - UIView - UILabel - UIBarButtonItem - - - - YES - - YES - activityIndicator - appDelegate - googleReaderButton - googleReaderButtonWrapper - instructionLabel - nextButton - - - YES - - activityIndicator - UIActivityIndicatorView - - - appDelegate - NewsBlurAppDelegate - - - googleReaderButton - UIButton - - - googleReaderButtonWrapper - UIView - - - instructionLabel - UILabel - - - nextButton - UIBarButtonItem - - - - - IBProjectSource - ./Classes/FirstTimeUserAddSitesViewController.h - - - - FirstTimeUserViewController - UIViewController - - tapNextButton - id - - - tapNextButton - - tapNextButton - id - - - - YES - - YES - appDelegate - footer - header - logo - nextButton - - - YES - NewsBlurAppDelegate - UILabel - UILabel - UIImageView - UIBarButtonItem - - - - YES - - YES - appDelegate - footer - header - logo - nextButton - - - YES - - appDelegate - NewsBlurAppDelegate - - - footer - UILabel - - - header - UILabel - - - logo - UIImageView - - - nextButton - UIBarButtonItem - - - - - IBProjectSource - ./Classes/FirstTimeUserViewController.h - - - - FontSettingsViewController - UIViewController - - YES - - YES - changeFontSize: - changeFontStyle: - - - YES - id - id - - - - YES - - YES - changeFontSize: - changeFontStyle: - - - YES - - changeFontSize: - id - - - changeFontStyle: - id - - - - - YES - - YES - appDelegate - fontSizeSegment - fontStyleSegment - largeFontSizeLabel - smallFontSizeLabel - - - YES - NewsBlurAppDelegate - UISegmentedControl - UISegmentedControl - UILabel - UILabel - - - - YES - - YES - appDelegate - fontSizeSegment - fontStyleSegment - largeFontSizeLabel - smallFontSizeLabel - - - YES - - appDelegate - NewsBlurAppDelegate - - - fontSizeSegment - UISegmentedControl - - - fontStyleSegment - UISegmentedControl - - - largeFontSizeLabel - UILabel - - - smallFontSizeLabel - UILabel - - - - - IBProjectSource - ./Classes/FontSettingsViewController.h - - - - FriendsListViewController - UIViewController - - YES - - YES - appDelegate - friendSearchBar - friendsTable - - - YES - NewsBlurAppDelegate - UISearchBar - UITableView - - - - YES - - YES - appDelegate - friendSearchBar - friendsTable - - - YES - - appDelegate - NewsBlurAppDelegate - - - friendSearchBar - UISearchBar - - - friendsTable - UITableView - - - - - IBProjectSource - ./Classes/FriendsListViewController.h - - - - InteractionsModule - UIView - - IBProjectSource - ./Classes/InteractionsModule.h - - - - LoginViewController - UIViewController - - YES - - YES - selectLogin - selectLoginSignup - selectSignUp - tapLoginButton - tapSignUpButton - - - YES - id - id - id - id - id - - - - YES - - YES - selectLogin - selectLoginSignup - selectSignUp - tapLoginButton - tapSignUpButton - - - YES - - selectLogin - id - - - selectLoginSignup - id - - - selectSignUp - id - - - tapLoginButton - id - - - tapSignUpButton - id - - - - - YES - - YES - appDelegate - emailInput - emailLabel - errorLabel - logInView - loginControl - passwordInput - passwordLabel - passwordOptionalLabel - selectLoginButton - selectSignUpButton - signUpPasswordInput - signUpUsernameInput - signUpView - usernameInput - usernameLabel - usernameOrEmailLabel - - - YES - NewsBlurAppDelegate - UITextField - UILabel - UILabel - UIView - UISegmentedControl - UITextField - UILabel - UILabel - UIButton - UIButton - UITextField - UITextField - UIView - UITextField - UILabel - UILabel - - - - YES - - YES - appDelegate - emailInput - emailLabel - errorLabel - logInView - loginControl - passwordInput - passwordLabel - passwordOptionalLabel - selectLoginButton - selectSignUpButton - signUpPasswordInput - signUpUsernameInput - signUpView - usernameInput - usernameLabel - usernameOrEmailLabel - - - YES - - appDelegate - NewsBlurAppDelegate - - - emailInput - UITextField - - - emailLabel - UILabel - - - errorLabel - UILabel - - - logInView - UIView - - - loginControl - UISegmentedControl - - - passwordInput - UITextField - - - passwordLabel - UILabel - - - passwordOptionalLabel - UILabel - - - selectLoginButton - UIButton - - - selectSignUpButton - UIButton - - - signUpPasswordInput - UITextField - - - signUpUsernameInput - UITextField - - - signUpView - UIView - - - usernameInput - UITextField - - - usernameLabel - UILabel - - - usernameOrEmailLabel - UILabel - - - - - IBProjectSource - ./Classes/LoginViewController.h - - - - MoveSiteViewController - UIViewController - - YES - - YES - doCancelButton - doMoveButton - moveFolder - moveSite - - - YES - id - id - id - id - - - - YES - - YES - doCancelButton - doMoveButton - moveFolder - moveSite - - - YES - - doCancelButton - id - - - doMoveButton - id - - - moveFolder - id - - - moveSite - id - - - - - YES - - YES - activityIndicator - appDelegate - cancelButton - errorLabel - folderPicker - fromFolderInput - moveButton - movingLabel - navBar - titleLabel - toFolderInput - - - YES - UIActivityIndicatorView - NewsBlurAppDelegate - UIBarButtonItem - UILabel - UIPickerView - UITextField - UIBarButtonItem - UILabel - UINavigationBar - UILabel - UITextField - - - - YES - - YES - activityIndicator - appDelegate - cancelButton - errorLabel - folderPicker - fromFolderInput - moveButton - movingLabel - navBar - titleLabel - toFolderInput - - - YES - - activityIndicator - UIActivityIndicatorView - - - appDelegate - NewsBlurAppDelegate - - - cancelButton - UIBarButtonItem - - - errorLabel - UILabel - - - folderPicker - UIPickerView - - - fromFolderInput - UITextField - - - moveButton - UIBarButtonItem - - - movingLabel - UILabel - - - navBar - UINavigationBar - - - titleLabel - UILabel - - - toFolderInput - UITextField - - - - - IBProjectSource - ./Classes/MoveSiteViewController.h - - - - NBContainerViewController - UIViewController - - appDelegate - NewsBlurAppDelegate - - - appDelegate - - appDelegate - NewsBlurAppDelegate - - - - IBProjectSource - ./Classes/NBContainerViewController.h - - - - NewsBlurAppDelegate - BaseViewController - - YES - - YES - addSiteViewController - dashboardViewController - feedDashboardViewController - feedDetailViewController - feedsMenuViewController - feedsViewController - findSitesViewController - firstTimeUserAddFriendsViewController - firstTimeUserAddNewsBlurViewController - firstTimeUserAddSitesViewController - firstTimeUserViewController - fontSettingsViewController - friendsListViewController - ftuxNavigationController - loginViewController - masterContainerViewController - moveSiteViewController - navigationController - originalStoryViewController - shareViewController - storyDetailViewController - userProfileViewController - window - - - YES - AddSiteViewController - DashboardViewController - FeedDashboardViewController - FeedDetailViewController - FeedsMenuViewController - NewsBlurViewController - FindSitesViewController - FirstTimeUserAddFriendsViewController - FirstTimeUserAddNewsBlurViewController - FirstTimeUserAddSitesViewController - FirstTimeUserViewController - FontSettingsViewController - FriendsListViewController - UINavigationController - LoginViewController - NBContainerViewController - MoveSiteViewController - UINavigationController - OriginalStoryViewController - ShareViewController - StoryDetailViewController - UserProfileViewController - UIWindow - - - - YES - - YES - addSiteViewController - dashboardViewController - feedDashboardViewController - feedDetailViewController - feedsMenuViewController - feedsViewController - findSitesViewController - firstTimeUserAddFriendsViewController - firstTimeUserAddNewsBlurViewController - firstTimeUserAddSitesViewController - firstTimeUserViewController - fontSettingsViewController - friendsListViewController - ftuxNavigationController - loginViewController - masterContainerViewController - moveSiteViewController - navigationController - originalStoryViewController - shareViewController - storyDetailViewController - userProfileViewController - window - - - YES - - addSiteViewController - AddSiteViewController - - - dashboardViewController - DashboardViewController - - - feedDashboardViewController - FeedDashboardViewController - - - feedDetailViewController - FeedDetailViewController - - - feedsMenuViewController - FeedsMenuViewController - - - feedsViewController - NewsBlurViewController - - - findSitesViewController - FindSitesViewController - - - firstTimeUserAddFriendsViewController - FirstTimeUserAddFriendsViewController - - - firstTimeUserAddNewsBlurViewController - FirstTimeUserAddNewsBlurViewController - - - firstTimeUserAddSitesViewController - FirstTimeUserAddSitesViewController - - - firstTimeUserViewController - FirstTimeUserViewController - - - fontSettingsViewController - FontSettingsViewController - - - friendsListViewController - FriendsListViewController - - - ftuxNavigationController - UINavigationController - - - loginViewController - LoginViewController - - - masterContainerViewController - NBContainerViewController - - - moveSiteViewController - MoveSiteViewController - - - navigationController - UINavigationController - - - originalStoryViewController - OriginalStoryViewController - - - shareViewController - ShareViewController - - - storyDetailViewController - StoryDetailViewController - - - userProfileViewController - UserProfileViewController - - - window - UIWindow - - - - - IBProjectSource - ./Classes/NewsBlurAppDelegate.h - - - - NewsBlurViewController - BaseViewController - - YES - - YES - sectionTapped: - sectionUntapped: - sectionUntappedOutside: - selectIntelligence - tapAddSite: - - - YES - UIButton - UIButton - UIButton - id - id - - - - YES - - YES - sectionTapped: - sectionUntapped: - sectionUntappedOutside: - selectIntelligence - tapAddSite: - - - YES - - sectionTapped: - UIButton - - - sectionUntapped: - UIButton - - - sectionUntappedOutside: - UIButton - - - selectIntelligence - id - - - tapAddSite: - id - - - - - YES - - YES - appDelegate - feedScoreSlider - feedTitlesTable - feedViewToolbar - homeButton - innerView - intelligenceControl - - - YES - NewsBlurAppDelegate - UISlider - UITableView - UIToolbar - UIBarButtonItem - UIView - UISegmentedControl - - - - YES - - YES - appDelegate - feedScoreSlider - feedTitlesTable - feedViewToolbar - homeButton - innerView - intelligenceControl - - - YES - - appDelegate - NewsBlurAppDelegate - - - feedScoreSlider - UISlider - - - feedTitlesTable - UITableView - - - feedViewToolbar - UIToolbar - - - homeButton - UIBarButtonItem - - - innerView - UIView - - - intelligenceControl - UISegmentedControl - - - - - IBProjectSource - ./Classes/NewsBlurViewController.h - - - - OriginalStoryViewController - BaseViewController - - YES - - YES - doCloseOriginalStoryViewController - doOpenActionSheet - loadAddress: - - - YES - id - id - id - - - - YES - - YES - doCloseOriginalStoryViewController - doOpenActionSheet - loadAddress: - - - YES - - doCloseOriginalStoryViewController - id - - - doOpenActionSheet - id - - - loadAddress: - id - - - - - YES - - YES - appDelegate - back - closeButton - forward - pageAction - pageTitle - pageUrl - refresh - toolbar - webView - - - YES - NewsBlurAppDelegate - UIBarButtonItem - UIBarButtonItem - UIBarButtonItem - UIBarButtonItem - UILabel - UITextField - UIBarButtonItem - UIToolbar - UIWebView - - - - YES - - YES - appDelegate - back - closeButton - forward - pageAction - pageTitle - pageUrl - refresh - toolbar - webView - - - YES - - appDelegate - NewsBlurAppDelegate - - - back - UIBarButtonItem - - - closeButton - UIBarButtonItem - - - forward - UIBarButtonItem - - - pageAction - UIBarButtonItem - - - pageTitle - UILabel - - - pageUrl - UITextField - - - refresh - UIBarButtonItem - - - toolbar - UIToolbar - - - webView - UIWebView - - - - - IBProjectSource - ./Classes/OriginalStoryViewController.h - - - - ShareViewController - UIViewController - - YES - - YES - doCancelButton: - doReplyToComment: - doShareThisStory: - doToggleButton: - - - YES - id - id - id - id - - - - YES - - YES - doCancelButton: - doReplyToComment: - doShareThisStory: - doToggleButton: - - - YES - - doCancelButton: - id - - - doReplyToComment: - id - - - doShareThisStory: - id - - - doToggleButton: - id - - - - - YES - - YES - appDelegate - commentField - facebookButton - submitButton - twitterButton - - - YES - NewsBlurAppDelegate - UITextView - UIButton - UIBarButtonItem - UIButton - - - - YES - - YES - appDelegate - commentField - facebookButton - submitButton - twitterButton - - - YES - - appDelegate - NewsBlurAppDelegate - - - commentField - UITextView - - - facebookButton - UIButton - - - submitButton - UIBarButtonItem - - - twitterButton - UIButton - - - - - IBProjectSource - ./Classes/ShareViewController.h - - - - StoryDetailViewController - UIViewController - - YES - - YES - doNextStory - doNextUnreadStory - doPreviousStory - showOriginalSubview: - tapProgressBar: - toggleFontSize: - - - YES - id - id - id - id - id - id - - - - YES - - YES - doNextStory - doNextUnreadStory - doPreviousStory - showOriginalSubview: - tapProgressBar: - toggleFontSize: - - - YES - - doNextStory - id - - - doNextUnreadStory - id - - - doPreviousStory - id - - - showOriginalSubview: - id - - - tapProgressBar: - id - - - toggleFontSize: - id - - - - - YES - - YES - activity - appDelegate - bottomPlaceholderToolbar - buttonAction - buttonNext - buttonNextStory - buttonPrevious - feedTitleGradient - fontSettingsButton - innerView - noStorySelectedLabel - originalStoryButton - progressView - progressViewContainer - subscribeButton - toolbar - webView - - - YES - UIBarButtonItem - NewsBlurAppDelegate - UIToolbar - UIBarButtonItem - UIBarButtonItem - UIBarButtonItem - UIBarButtonItem - UIView - UIBarButtonItem - UIView - UILabel - UIBarButtonItem - UIProgressView - UIView - UIBarButtonItem - UIToolbar - UIWebView - - - - YES - - YES - activity - appDelegate - bottomPlaceholderToolbar - buttonAction - buttonNext - buttonNextStory - buttonPrevious - feedTitleGradient - fontSettingsButton - innerView - noStorySelectedLabel - originalStoryButton - progressView - progressViewContainer - subscribeButton - toolbar - webView - - - YES - - activity - UIBarButtonItem - - - appDelegate - NewsBlurAppDelegate - - - bottomPlaceholderToolbar - UIToolbar - - - buttonAction - UIBarButtonItem - - - buttonNext - UIBarButtonItem - - - buttonNextStory - UIBarButtonItem - - - buttonPrevious - UIBarButtonItem - - - feedTitleGradient - UIView - - - fontSettingsButton - UIBarButtonItem - - - innerView - UIView - - - noStorySelectedLabel - UILabel - - - originalStoryButton - UIBarButtonItem - - - progressView - UIProgressView - - - progressViewContainer - UIView - - - subscribeButton - UIBarButtonItem - - - toolbar - UIToolbar - - - webView - UIWebView - - - - - IBProjectSource - ./Classes/StoryDetailViewController.h - - - - UserProfileViewController - UIViewController - - IBProjectSource - ./Classes/UserProfileViewController.h - - - - + 0 IBCocoaTouchFramework com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS - + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 @@ -2515,6 +323,6 @@ settings.png {16, 16} - 1181 + 1926 diff --git a/media/ios/Resources-iPhone/LoginViewController.xib b/media/ios/Resources-iPhone/LoginViewController.xib index a3fa63aac..3351a2abe 100644 --- a/media/ios/Resources-iPhone/LoginViewController.xib +++ b/media/ios/Resources-iPhone/LoginViewController.xib @@ -1,24 +1,24 @@ - 1280 - 11E53 - 2182 - 1138.47 - 569.00 + 1536 + 12C54 + 2840 + 1187.34 + 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 1181 + 1926 YES IBProxyObject + IBUIActivityIndicatorView IBUIImageView IBUILabel - IBUIActivityIndicatorView - IBUITextField - IBUISegmentedControl IBUIScrollView + IBUISegmentedControl + IBUITextField IBUIView @@ -46,9 +46,10 @@ YES - 292 + 277 {{0, 65}, {320, 395}} + _NS:541 NO @@ -63,11 +64,13 @@ 292 {320, 65} + _NS:541 1 MCAwIDAAA + darkTextColor NO IBCocoaTouchFramework @@ -81,6 +84,7 @@ 292 {{15, 9}, {285, 48}} + _NS:541 2 @@ -94,8 +98,9 @@ -2147483356 - {{20, 294}, {280, 52}} + {{20, 248}, {280, 52}} + _NS:311 NO @@ -128,8 +133,9 @@ -2147483356 - {{20, 294}, {280, 52}} + {{20, 248}, {280, 52}} + _NS:311 NO @@ -150,20 +156,21 @@ 1 + 280 -2147483356 - {{71, 310}, {20, 20}} + {{71, 264}, {20, 20}} - + _NS:824 NO IBCocoaTouchFramework - 268 + 260 YES @@ -171,6 +178,7 @@ 292 {{20, 67}, {280, 31}} + NO NO @@ -203,6 +211,7 @@ 292 {{21, 44}, {212, 22}} + NO YES @@ -230,6 +239,7 @@ 292 {{21, 44}, {212, 22}} + NO YES @@ -254,6 +264,7 @@ 292 {{20, 129}, {280, 31}} + NO NO @@ -286,6 +297,7 @@ 292 {{21, 106}, {212, 22}} + NO YES @@ -306,6 +318,7 @@ 292 {{199, 112}, {101, 16}} + NO YES @@ -337,6 +350,7 @@ 292 {{20, 129}, {280, 31}} + NO NO @@ -366,6 +380,7 @@ 292 {{21, 106}, {212, 22}} + NO YES @@ -391,6 +406,7 @@ 292 {{-3, 0}, {329, 30}} + _NS:262 IBCocoaTouchFramework @@ -428,8 +444,9 @@ - {{0, 65}, {320, 227}} + {{0, 65}, {320, 180}} + _NS:174 NO @@ -444,6 +461,7 @@ {{0, 20}, {320, 460}} + 1 @@ -620,11 +638,11 @@ YES - - - + + + @@ -803,7 +821,2196 @@ 51 - + + + YES + + ActivityModule + UIView + + IBProjectSource + ./Classes/ActivityModule.h + + + + AddSiteViewController + UIViewController + + YES + + YES + addFolder + addSite + checkSiteAddress + doAddButton + doCancelButton + selectAddTypeSignup + + + YES + id + id + id + id + id + id + + + + YES + + YES + addFolder + addSite + checkSiteAddress + doAddButton + doCancelButton + selectAddTypeSignup + + + YES + + addFolder + id + + + addSite + id + + + checkSiteAddress + id + + + doAddButton + id + + + doCancelButton + id + + + selectAddTypeSignup + id + + + + + YES + + YES + activityIndicator + addButton + addFolderInput + addTypeControl + addingLabel + appDelegate + cancelButton + errorLabel + folderPicker + inFolderInput + navBar + siteActivityIndicator + siteAddressInput + siteScrollView + siteTable + + + YES + UIActivityIndicatorView + UIBarButtonItem + UITextField + UISegmentedControl + UILabel + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UINavigationBar + UIActivityIndicatorView + UITextField + UIScrollView + UITableView + + + + YES + + YES + activityIndicator + addButton + addFolderInput + addTypeControl + addingLabel + appDelegate + cancelButton + errorLabel + folderPicker + inFolderInput + navBar + siteActivityIndicator + siteAddressInput + siteScrollView + siteTable + + + YES + + activityIndicator + UIActivityIndicatorView + + + addButton + UIBarButtonItem + + + addFolderInput + UITextField + + + addTypeControl + UISegmentedControl + + + addingLabel + UILabel + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + inFolderInput + UITextField + + + navBar + UINavigationBar + + + siteActivityIndicator + UIActivityIndicatorView + + + siteAddressInput + UITextField + + + siteScrollView + UIScrollView + + + siteTable + UITableView + + + + + IBProjectSource + ./Classes/AddSiteViewController.h + + + + BaseViewController + UIViewController + + IBProjectSource + ./Classes/BaseViewController.h + + + + DashboardViewController + UIViewController + + YES + + YES + doLogout: + tapSegmentedButton: + + + YES + id + id + + + + YES + + YES + doLogout: + tapSegmentedButton: + + + YES + + doLogout: + id + + + tapSegmentedButton: + id + + + + + YES + + YES + activitiesModule + appDelegate + feedbackWebView + interactionsModule + segmentedButton + toolbar + topToolbar + + + YES + ActivityModule + NewsBlurAppDelegate + UIWebView + InteractionsModule + UISegmentedControl + UIToolbar + UIToolbar + + + + YES + + YES + activitiesModule + appDelegate + feedbackWebView + interactionsModule + segmentedButton + toolbar + topToolbar + + + YES + + activitiesModule + ActivityModule + + + appDelegate + NewsBlurAppDelegate + + + feedbackWebView + UIWebView + + + interactionsModule + InteractionsModule + + + segmentedButton + UISegmentedControl + + + toolbar + UIToolbar + + + topToolbar + UIToolbar + + + + + IBProjectSource + ./Classes/DashboardViewController.h + + + + FeedDetailViewController + BaseViewController + + YES + + YES + doOpenMarkReadActionSheet: + doOpenSettingsActionSheet + selectIntelligence + + + YES + id + id + id + + + + YES + + YES + doOpenMarkReadActionSheet: + doOpenSettingsActionSheet + selectIntelligence + + + YES + + doOpenMarkReadActionSheet: + id + + + doOpenSettingsActionSheet + id + + + selectIntelligence + id + + + + + YES + + YES + appDelegate + feedMarkReadButton + feedScoreSlider + feedViewToolbar + intelligenceControl + settingsButton + storyTitlesTable + + + YES + NewsBlurAppDelegate + UIBarButtonItem + UISlider + UIToolbar + UISegmentedControl + UIBarButtonItem + UITableView + + + + YES + + YES + appDelegate + feedMarkReadButton + feedScoreSlider + feedViewToolbar + intelligenceControl + settingsButton + storyTitlesTable + + + YES + + appDelegate + NewsBlurAppDelegate + + + feedMarkReadButton + UIBarButtonItem + + + feedScoreSlider + UISlider + + + feedViewToolbar + UIToolbar + + + intelligenceControl + UISegmentedControl + + + settingsButton + UIBarButtonItem + + + storyTitlesTable + UITableView + + + + + IBProjectSource + ./Classes/FeedDetailViewController.h + + + + FeedsMenuViewController + UIViewController + + YES + + YES + appDelegate + menuTableView + + + YES + NewsBlurAppDelegate + UITableView + + + + YES + + YES + appDelegate + menuTableView + + + YES + + appDelegate + NewsBlurAppDelegate + + + menuTableView + UITableView + + + + + IBProjectSource + ./Classes/FeedsMenuViewController.h + + + + FindSitesViewController + UIViewController + + YES + + YES + appDelegate + sitesSearchBar + sitesTable + + + YES + NewsBlurAppDelegate + UISearchBar + UITableView + + + + YES + + YES + appDelegate + sitesSearchBar + sitesTable + + + YES + + appDelegate + NewsBlurAppDelegate + + + sitesSearchBar + UISearchBar + + + sitesTable + UITableView + + + + + IBProjectSource + ./Classes/FindSitesViewController.h + + + + FirstTimeUserAddFriendsViewController + UIViewController + + YES + + YES + tapFacebookButton + tapNextButton + tapTwitterButton + toggleAutoFollowFriends: + + + YES + id + id + id + id + + + + YES + + YES + tapFacebookButton + tapNextButton + tapTwitterButton + toggleAutoFollowFriends: + + + YES + + tapFacebookButton + id + + + tapNextButton + id + + + tapTwitterButton + id + + + toggleAutoFollowFriends: + id + + + + + YES + + YES + appDelegate + facebookActivityIndicator + facebookButton + friendsLabel + nextButton + twitterActivityIndicator + twitterButton + + + YES + NewsBlurAppDelegate + UIActivityIndicatorView + UIButton + UILabel + UIBarButtonItem + UIActivityIndicatorView + UIButton + + + + YES + + YES + appDelegate + facebookActivityIndicator + facebookButton + friendsLabel + nextButton + twitterActivityIndicator + twitterButton + + + YES + + appDelegate + NewsBlurAppDelegate + + + facebookActivityIndicator + UIActivityIndicatorView + + + facebookButton + UIButton + + + friendsLabel + UILabel + + + nextButton + UIBarButtonItem + + + twitterActivityIndicator + UIActivityIndicatorView + + + twitterButton + UIButton + + + + + IBProjectSource + ./Classes/FirstTimeUserAddFriendsViewController.h + + + + FirstTimeUserAddNewsBlurViewController + UIViewController + + YES + + YES + tapNewsBlurButton: + tapNextButton + tapPopularButton: + + + YES + id + id + id + + + + YES + + YES + tapNewsBlurButton: + tapNextButton + tapPopularButton: + + + YES + + tapNewsBlurButton: + id + + + tapNextButton + id + + + tapPopularButton: + id + + + + + YES + + YES + appDelegate + instructionsLabel + nextButton + + + YES + NewsBlurAppDelegate + UILabel + UIBarButtonItem + + + + YES + + YES + appDelegate + instructionsLabel + nextButton + + + YES + + appDelegate + NewsBlurAppDelegate + + + instructionsLabel + UILabel + + + nextButton + UIBarButtonItem + + + + + IBProjectSource + ./Classes/FirstTimeUserAddNewsBlurViewController.h + + + + FirstTimeUserAddSitesViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + YES + + YES + activityIndicator + appDelegate + categoriesTable + googleReaderButton + googleReaderButtonWrapper + instructionLabel + nextButton + scrollView + + + YES + UIActivityIndicatorView + NewsBlurAppDelegate + UITableView + UIButton + UIView + UILabel + UIBarButtonItem + UIScrollView + + + + YES + + YES + activityIndicator + appDelegate + categoriesTable + googleReaderButton + googleReaderButtonWrapper + instructionLabel + nextButton + scrollView + + + YES + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + categoriesTable + UITableView + + + googleReaderButton + UIButton + + + googleReaderButtonWrapper + UIView + + + instructionLabel + UILabel + + + nextButton + UIBarButtonItem + + + scrollView + UIScrollView + + + + + IBProjectSource + ./Classes/FirstTimeUserAddSitesViewController.h + + + + FirstTimeUserViewController + UIViewController + + tapNextButton + id + + + tapNextButton + + tapNextButton + id + + + + YES + + YES + appDelegate + footer + header + logo + nextButton + + + YES + NewsBlurAppDelegate + UILabel + UILabel + UIImageView + UIBarButtonItem + + + + YES + + YES + appDelegate + footer + header + logo + nextButton + + + YES + + appDelegate + NewsBlurAppDelegate + + + footer + UILabel + + + header + UILabel + + + logo + UIImageView + + + nextButton + UIBarButtonItem + + + + + IBProjectSource + ./Classes/FirstTimeUserViewController.h + + + + FontSettingsViewController + UIViewController + + YES + + YES + changeFontSize: + changeFontStyle: + + + YES + id + id + + + + YES + + YES + changeFontSize: + changeFontStyle: + + + YES + + changeFontSize: + id + + + changeFontStyle: + id + + + + + YES + + YES + appDelegate + fontSizeSegment + fontStyleSegment + largeFontSizeLabel + smallFontSizeLabel + + + YES + NewsBlurAppDelegate + UISegmentedControl + UISegmentedControl + UILabel + UILabel + + + + YES + + YES + appDelegate + fontSizeSegment + fontStyleSegment + largeFontSizeLabel + smallFontSizeLabel + + + YES + + appDelegate + NewsBlurAppDelegate + + + fontSizeSegment + UISegmentedControl + + + fontStyleSegment + UISegmentedControl + + + largeFontSizeLabel + UILabel + + + smallFontSizeLabel + UILabel + + + + + IBProjectSource + ./Classes/FontSettingsViewController.h + + + + FriendsListViewController + UIViewController + + YES + + YES + appDelegate + friendSearchBar + friendsTable + + + YES + NewsBlurAppDelegate + UISearchBar + UITableView + + + + YES + + YES + appDelegate + friendSearchBar + friendsTable + + + YES + + appDelegate + NewsBlurAppDelegate + + + friendSearchBar + UISearchBar + + + friendsTable + UITableView + + + + + IBProjectSource + ./Classes/FriendsListViewController.h + + + + InteractionsModule + UIView + + IBProjectSource + ./Classes/InteractionsModule.h + + + + LoginViewController + UIViewController + + YES + + YES + selectLogin + selectLoginSignup + selectSignUp + tapLoginButton + tapSignUpButton + + + YES + id + id + id + id + id + + + + YES + + YES + selectLogin + selectLoginSignup + selectSignUp + tapLoginButton + tapSignUpButton + + + YES + + selectLogin + id + + + selectLoginSignup + id + + + selectSignUp + id + + + tapLoginButton + id + + + tapSignUpButton + id + + + + + YES + + YES + appDelegate + emailInput + emailLabel + errorLabel + logInView + loginControl + passwordInput + passwordLabel + passwordOptionalLabel + selectLoginButton + selectSignUpButton + signUpPasswordInput + signUpUsernameInput + signUpView + usernameInput + usernameLabel + usernameOrEmailLabel + + + YES + NewsBlurAppDelegate + UITextField + UILabel + UILabel + UIView + UISegmentedControl + UITextField + UILabel + UILabel + UIButton + UIButton + UITextField + UITextField + UIView + UITextField + UILabel + UILabel + + + + YES + + YES + appDelegate + emailInput + emailLabel + errorLabel + logInView + loginControl + passwordInput + passwordLabel + passwordOptionalLabel + selectLoginButton + selectSignUpButton + signUpPasswordInput + signUpUsernameInput + signUpView + usernameInput + usernameLabel + usernameOrEmailLabel + + + YES + + appDelegate + NewsBlurAppDelegate + + + emailInput + UITextField + + + emailLabel + UILabel + + + errorLabel + UILabel + + + logInView + UIView + + + loginControl + UISegmentedControl + + + passwordInput + UITextField + + + passwordLabel + UILabel + + + passwordOptionalLabel + UILabel + + + selectLoginButton + UIButton + + + selectSignUpButton + UIButton + + + signUpPasswordInput + UITextField + + + signUpUsernameInput + UITextField + + + signUpView + UIView + + + usernameInput + UITextField + + + usernameLabel + UILabel + + + usernameOrEmailLabel + UILabel + + + + + IBProjectSource + ./Classes/LoginViewController.h + + + + MoveSiteViewController + UIViewController + + YES + + YES + doCancelButton + doMoveButton + moveFolder + moveSite + + + YES + id + id + id + id + + + + YES + + YES + doCancelButton + doMoveButton + moveFolder + moveSite + + + YES + + doCancelButton + id + + + doMoveButton + id + + + moveFolder + id + + + moveSite + id + + + + + YES + + YES + activityIndicator + appDelegate + cancelButton + errorLabel + folderPicker + fromFolderInput + moveButton + movingLabel + navBar + titleLabel + toFolderInput + + + YES + UIActivityIndicatorView + NewsBlurAppDelegate + UIBarButtonItem + UILabel + UIPickerView + UITextField + UIBarButtonItem + UILabel + UINavigationBar + UILabel + UITextField + + + + YES + + YES + activityIndicator + appDelegate + cancelButton + errorLabel + folderPicker + fromFolderInput + moveButton + movingLabel + navBar + titleLabel + toFolderInput + + + YES + + activityIndicator + UIActivityIndicatorView + + + appDelegate + NewsBlurAppDelegate + + + cancelButton + UIBarButtonItem + + + errorLabel + UILabel + + + folderPicker + UIPickerView + + + fromFolderInput + UITextField + + + moveButton + UIBarButtonItem + + + movingLabel + UILabel + + + navBar + UINavigationBar + + + titleLabel + UILabel + + + toFolderInput + UITextField + + + + + IBProjectSource + ./Classes/MoveSiteViewController.h + + + + NBContainerViewController + UIViewController + + appDelegate + NewsBlurAppDelegate + + + appDelegate + + appDelegate + NewsBlurAppDelegate + + + + IBProjectSource + ./Classes/NBContainerViewController.h + + + + NewsBlurAppDelegate + BaseViewController + + YES + + YES + addSiteViewController + dashboardViewController + feedDashboardViewController + feedDetailViewController + feedsMenuViewController + feedsViewController + findSitesViewController + firstTimeUserAddFriendsViewController + firstTimeUserAddNewsBlurViewController + firstTimeUserAddSitesViewController + firstTimeUserViewController + fontSettingsViewController + friendsListViewController + ftuxNavigationController + loginViewController + masterContainerViewController + moveSiteViewController + navigationController + originalStoryViewController + shareViewController + storyDetailViewController + userProfileViewController + window + + + YES + AddSiteViewController + DashboardViewController + FeedDashboardViewController + FeedDetailViewController + FeedsMenuViewController + NewsBlurViewController + FindSitesViewController + FirstTimeUserAddFriendsViewController + FirstTimeUserAddNewsBlurViewController + FirstTimeUserAddSitesViewController + FirstTimeUserViewController + FontSettingsViewController + FriendsListViewController + UINavigationController + LoginViewController + NBContainerViewController + MoveSiteViewController + UINavigationController + OriginalStoryViewController + ShareViewController + StoryDetailViewController + UserProfileViewController + UIWindow + + + + YES + + YES + addSiteViewController + dashboardViewController + feedDashboardViewController + feedDetailViewController + feedsMenuViewController + feedsViewController + findSitesViewController + firstTimeUserAddFriendsViewController + firstTimeUserAddNewsBlurViewController + firstTimeUserAddSitesViewController + firstTimeUserViewController + fontSettingsViewController + friendsListViewController + ftuxNavigationController + loginViewController + masterContainerViewController + moveSiteViewController + navigationController + originalStoryViewController + shareViewController + storyDetailViewController + userProfileViewController + window + + + YES + + addSiteViewController + AddSiteViewController + + + dashboardViewController + DashboardViewController + + + feedDashboardViewController + FeedDashboardViewController + + + feedDetailViewController + FeedDetailViewController + + + feedsMenuViewController + FeedsMenuViewController + + + feedsViewController + NewsBlurViewController + + + findSitesViewController + FindSitesViewController + + + firstTimeUserAddFriendsViewController + FirstTimeUserAddFriendsViewController + + + firstTimeUserAddNewsBlurViewController + FirstTimeUserAddNewsBlurViewController + + + firstTimeUserAddSitesViewController + FirstTimeUserAddSitesViewController + + + firstTimeUserViewController + FirstTimeUserViewController + + + fontSettingsViewController + FontSettingsViewController + + + friendsListViewController + FriendsListViewController + + + ftuxNavigationController + UINavigationController + + + loginViewController + LoginViewController + + + masterContainerViewController + NBContainerViewController + + + moveSiteViewController + MoveSiteViewController + + + navigationController + UINavigationController + + + originalStoryViewController + OriginalStoryViewController + + + shareViewController + ShareViewController + + + storyDetailViewController + StoryDetailViewController + + + userProfileViewController + UserProfileViewController + + + window + UIWindow + + + + + IBProjectSource + ./Classes/NewsBlurAppDelegate.h + + + + NewsBlurViewController + BaseViewController + + YES + + YES + sectionTapped: + sectionUntapped: + sectionUntappedOutside: + selectIntelligence + tapAddSite: + + + YES + UIButton + UIButton + UIButton + id + id + + + + YES + + YES + sectionTapped: + sectionUntapped: + sectionUntappedOutside: + selectIntelligence + tapAddSite: + + + YES + + sectionTapped: + UIButton + + + sectionUntapped: + UIButton + + + sectionUntappedOutside: + UIButton + + + selectIntelligence + id + + + tapAddSite: + id + + + + + YES + + YES + appDelegate + feedScoreSlider + feedTitlesTable + feedViewToolbar + homeButton + innerView + intelligenceControl + noFocusMessage + toolbarLeftMargin + + + YES + NewsBlurAppDelegate + UISlider + UITableView + UIToolbar + UIBarButtonItem + UIView + UISegmentedControl + UIView + UIBarButtonItem + + + + YES + + YES + appDelegate + feedScoreSlider + feedTitlesTable + feedViewToolbar + homeButton + innerView + intelligenceControl + noFocusMessage + toolbarLeftMargin + + + YES + + appDelegate + NewsBlurAppDelegate + + + feedScoreSlider + UISlider + + + feedTitlesTable + UITableView + + + feedViewToolbar + UIToolbar + + + homeButton + UIBarButtonItem + + + innerView + UIView + + + intelligenceControl + UISegmentedControl + + + noFocusMessage + UIView + + + toolbarLeftMargin + UIBarButtonItem + + + + + IBProjectSource + ./Classes/NewsBlurViewController.h + + + + OriginalStoryViewController + BaseViewController + + YES + + YES + doCloseOriginalStoryViewController + doOpenActionSheet + loadAddress: + + + YES + id + id + id + + + + YES + + YES + doCloseOriginalStoryViewController + doOpenActionSheet + loadAddress: + + + YES + + doCloseOriginalStoryViewController + id + + + doOpenActionSheet + id + + + loadAddress: + id + + + + + YES + + YES + appDelegate + back + closeButton + forward + pageAction + pageTitle + pageUrl + refresh + toolbar + webView + + + YES + NewsBlurAppDelegate + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UILabel + UITextField + UIBarButtonItem + UIToolbar + UIWebView + + + + YES + + YES + appDelegate + back + closeButton + forward + pageAction + pageTitle + pageUrl + refresh + toolbar + webView + + + YES + + appDelegate + NewsBlurAppDelegate + + + back + UIBarButtonItem + + + closeButton + UIBarButtonItem + + + forward + UIBarButtonItem + + + pageAction + UIBarButtonItem + + + pageTitle + UILabel + + + pageUrl + UITextField + + + refresh + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + + IBProjectSource + ./Classes/OriginalStoryViewController.h + + + + ShareViewController + UIViewController + + YES + + YES + doCancelButton: + doReplyToComment: + doShareThisStory: + doToggleButton: + + + YES + id + id + id + id + + + + YES + + YES + doCancelButton: + doReplyToComment: + doShareThisStory: + doToggleButton: + + + YES + + doCancelButton: + id + + + doReplyToComment: + id + + + doShareThisStory: + id + + + doToggleButton: + id + + + + + YES + + YES + appDelegate + commentField + facebookButton + submitButton + twitterButton + + + YES + NewsBlurAppDelegate + UITextView + UIButton + UIBarButtonItem + UIButton + + + + YES + + YES + appDelegate + commentField + facebookButton + submitButton + twitterButton + + + YES + + appDelegate + NewsBlurAppDelegate + + + commentField + UITextView + + + facebookButton + UIButton + + + submitButton + UIBarButtonItem + + + twitterButton + UIButton + + + + + IBProjectSource + ./Classes/ShareViewController.h + + + + StoryDetailViewController + UIViewController + + YES + + YES + doNextStory + doNextUnreadStory + doPreviousStory + showOriginalSubview: + tapProgressBar: + toggleFontSize: + + + YES + id + id + id + id + id + id + + + + YES + + YES + doNextStory + doNextUnreadStory + doPreviousStory + showOriginalSubview: + tapProgressBar: + toggleFontSize: + + + YES + + doNextStory + id + + + doNextUnreadStory + id + + + doPreviousStory + id + + + showOriginalSubview: + id + + + tapProgressBar: + id + + + toggleFontSize: + id + + + + + YES + + YES + activity + appDelegate + bottomPlaceholderToolbar + buttonAction + buttonNext + buttonNextStory + buttonPrevious + feedTitleGradient + fontSettingsButton + innerView + noStorySelectedLabel + originalStoryButton + progressView + progressViewContainer + subscribeButton + toolbar + webView + + + YES + UIBarButtonItem + NewsBlurAppDelegate + UIToolbar + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIBarButtonItem + UIView + UIBarButtonItem + UIView + UILabel + UIBarButtonItem + UIProgressView + UIView + UIBarButtonItem + UIToolbar + UIWebView + + + + YES + + YES + activity + appDelegate + bottomPlaceholderToolbar + buttonAction + buttonNext + buttonNextStory + buttonPrevious + feedTitleGradient + fontSettingsButton + innerView + noStorySelectedLabel + originalStoryButton + progressView + progressViewContainer + subscribeButton + toolbar + webView + + + YES + + activity + UIBarButtonItem + + + appDelegate + NewsBlurAppDelegate + + + bottomPlaceholderToolbar + UIToolbar + + + buttonAction + UIBarButtonItem + + + buttonNext + UIBarButtonItem + + + buttonNextStory + UIBarButtonItem + + + buttonPrevious + UIBarButtonItem + + + feedTitleGradient + UIView + + + fontSettingsButton + UIBarButtonItem + + + innerView + UIView + + + noStorySelectedLabel + UILabel + + + originalStoryButton + UIBarButtonItem + + + progressView + UIProgressView + + + progressViewContainer + UIView + + + subscribeButton + UIBarButtonItem + + + toolbar + UIToolbar + + + webView + UIWebView + + + + + IBProjectSource + ./Classes/StoryDetailViewController.h + + + + UserProfileViewController + UIViewController + + IBProjectSource + ./Classes/UserProfileViewController.h + + + + 0 IBCocoaTouchFramework @@ -827,6 +3034,6 @@ {312, 55} - 1181 + 1926 diff --git a/media/ios/Resources-iPhone/MainWindow.xib b/media/ios/Resources-iPhone/MainWindow.xib index 8cec5f167..3abc02c15 100644 --- a/media/ios/Resources-iPhone/MainWindow.xib +++ b/media/ios/Resources-iPhone/MainWindow.xib @@ -2,10 +2,10 @@ 1536 - 12B19 + 12C54 2840 - 1187 - 624.00 + 1187.34 + 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin 1926 @@ -51,10 +51,6 @@ 1 1 - - IBUISimulatedFreeformSizeMetricsSentinel - Freeform - IBCocoaTouchFramework NO @@ -214,7 +210,7 @@ 256 - {320, 568} + {320, 480} @@ -224,8 +220,8 @@ NO NO - IBCocoaTouchFramework + YES @@ -248,7 +244,6 @@ YES - Title IBCocoaTouchFramework diff --git a/media/ios/Resources-iPhone/NewsBlurViewController.xib b/media/ios/Resources-iPhone/NewsBlurViewController.xib index 2f53aa2e1..48bd511e8 100644 --- a/media/ios/Resources-iPhone/NewsBlurViewController.xib +++ b/media/ios/Resources-iPhone/NewsBlurViewController.xib @@ -2,10 +2,10 @@ 1536 - 12B19 + 12C54 2840 - 1187 - 624.00 + 1187.34 + 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin 1926 @@ -52,7 +52,6 @@ 274 {320, 416} - 3 @@ -72,7 +71,6 @@ {320, 416} - _NS:9 @@ -94,7 +92,6 @@ 292 {{61, 8}, {180, 30}} - IBCocoaTouchFramework 2 3 @@ -146,7 +143,6 @@ {{0, 416}, {320, 44}} - 1 @@ -193,15 +189,10 @@ {{0, 20}, {320, 460}} - NO - - IBUISimulatedFreeformSizeMetricsSentinel - Freeform - IBCocoaTouchFramework @@ -439,2196 +430,7 @@ 105 - - - YES - - ActivityModule - UIView - - IBProjectSource - ./Classes/ActivityModule.h - - - - AddSiteViewController - UIViewController - - YES - - YES - addFolder - addSite - checkSiteAddress - doAddButton - doCancelButton - selectAddTypeSignup - - - YES - id - id - id - id - id - id - - - - YES - - YES - addFolder - addSite - checkSiteAddress - doAddButton - doCancelButton - selectAddTypeSignup - - - YES - - addFolder - id - - - addSite - id - - - checkSiteAddress - id - - - doAddButton - id - - - doCancelButton - id - - - selectAddTypeSignup - id - - - - - YES - - YES - activityIndicator - addButton - addFolderInput - addTypeControl - addingLabel - appDelegate - cancelButton - errorLabel - folderPicker - inFolderInput - navBar - siteActivityIndicator - siteAddressInput - siteScrollView - siteTable - - - YES - UIActivityIndicatorView - UIBarButtonItem - UITextField - UISegmentedControl - UILabel - NewsBlurAppDelegate - UIBarButtonItem - UILabel - UIPickerView - UITextField - UINavigationBar - UIActivityIndicatorView - UITextField - UIScrollView - UITableView - - - - YES - - YES - activityIndicator - addButton - addFolderInput - addTypeControl - addingLabel - appDelegate - cancelButton - errorLabel - folderPicker - inFolderInput - navBar - siteActivityIndicator - siteAddressInput - siteScrollView - siteTable - - - YES - - activityIndicator - UIActivityIndicatorView - - - addButton - UIBarButtonItem - - - addFolderInput - UITextField - - - addTypeControl - UISegmentedControl - - - addingLabel - UILabel - - - appDelegate - NewsBlurAppDelegate - - - cancelButton - UIBarButtonItem - - - errorLabel - UILabel - - - folderPicker - UIPickerView - - - inFolderInput - UITextField - - - navBar - UINavigationBar - - - siteActivityIndicator - UIActivityIndicatorView - - - siteAddressInput - UITextField - - - siteScrollView - UIScrollView - - - siteTable - UITableView - - - - - IBProjectSource - ./Classes/AddSiteViewController.h - - - - BaseViewController - UIViewController - - IBProjectSource - ./Classes/BaseViewController.h - - - - DashboardViewController - UIViewController - - YES - - YES - doLogout: - tapSegmentedButton: - - - YES - id - id - - - - YES - - YES - doLogout: - tapSegmentedButton: - - - YES - - doLogout: - id - - - tapSegmentedButton: - id - - - - - YES - - YES - activitiesModule - appDelegate - feedbackWebView - interactionsModule - segmentedButton - toolbar - topToolbar - - - YES - ActivityModule - NewsBlurAppDelegate - UIWebView - InteractionsModule - UISegmentedControl - UIToolbar - UIToolbar - - - - YES - - YES - activitiesModule - appDelegate - feedbackWebView - interactionsModule - segmentedButton - toolbar - topToolbar - - - YES - - activitiesModule - ActivityModule - - - appDelegate - NewsBlurAppDelegate - - - feedbackWebView - UIWebView - - - interactionsModule - InteractionsModule - - - segmentedButton - UISegmentedControl - - - toolbar - UIToolbar - - - topToolbar - UIToolbar - - - - - IBProjectSource - ./Classes/DashboardViewController.h - - - - FeedDetailViewController - BaseViewController - - YES - - YES - doOpenMarkReadActionSheet: - doOpenSettingsActionSheet - selectIntelligence - - - YES - id - id - id - - - - YES - - YES - doOpenMarkReadActionSheet: - doOpenSettingsActionSheet - selectIntelligence - - - YES - - doOpenMarkReadActionSheet: - id - - - doOpenSettingsActionSheet - id - - - selectIntelligence - id - - - - - YES - - YES - appDelegate - feedMarkReadButton - feedScoreSlider - feedViewToolbar - intelligenceControl - settingsButton - storyTitlesTable - - - YES - NewsBlurAppDelegate - UIBarButtonItem - UISlider - UIToolbar - UISegmentedControl - UIBarButtonItem - UITableView - - - - YES - - YES - appDelegate - feedMarkReadButton - feedScoreSlider - feedViewToolbar - intelligenceControl - settingsButton - storyTitlesTable - - - YES - - appDelegate - NewsBlurAppDelegate - - - feedMarkReadButton - UIBarButtonItem - - - feedScoreSlider - UISlider - - - feedViewToolbar - UIToolbar - - - intelligenceControl - UISegmentedControl - - - settingsButton - UIBarButtonItem - - - storyTitlesTable - UITableView - - - - - IBProjectSource - ./Classes/FeedDetailViewController.h - - - - FeedsMenuViewController - UIViewController - - YES - - YES - appDelegate - menuTableView - - - YES - NewsBlurAppDelegate - UITableView - - - - YES - - YES - appDelegate - menuTableView - - - YES - - appDelegate - NewsBlurAppDelegate - - - menuTableView - UITableView - - - - - IBProjectSource - ./Classes/FeedsMenuViewController.h - - - - FindSitesViewController - UIViewController - - YES - - YES - appDelegate - sitesSearchBar - sitesTable - - - YES - NewsBlurAppDelegate - UISearchBar - UITableView - - - - YES - - YES - appDelegate - sitesSearchBar - sitesTable - - - YES - - appDelegate - NewsBlurAppDelegate - - - sitesSearchBar - UISearchBar - - - sitesTable - UITableView - - - - - IBProjectSource - ./Classes/FindSitesViewController.h - - - - FirstTimeUserAddFriendsViewController - UIViewController - - YES - - YES - tapFacebookButton - tapNextButton - tapTwitterButton - toggleAutoFollowFriends: - - - YES - id - id - id - id - - - - YES - - YES - tapFacebookButton - tapNextButton - tapTwitterButton - toggleAutoFollowFriends: - - - YES - - tapFacebookButton - id - - - tapNextButton - id - - - tapTwitterButton - id - - - toggleAutoFollowFriends: - id - - - - - YES - - YES - appDelegate - facebookActivityIndicator - facebookButton - friendsLabel - nextButton - twitterActivityIndicator - twitterButton - - - YES - NewsBlurAppDelegate - UIActivityIndicatorView - UIButton - UILabel - UIBarButtonItem - UIActivityIndicatorView - UIButton - - - - YES - - YES - appDelegate - facebookActivityIndicator - facebookButton - friendsLabel - nextButton - twitterActivityIndicator - twitterButton - - - YES - - appDelegate - NewsBlurAppDelegate - - - facebookActivityIndicator - UIActivityIndicatorView - - - facebookButton - UIButton - - - friendsLabel - UILabel - - - nextButton - UIBarButtonItem - - - twitterActivityIndicator - UIActivityIndicatorView - - - twitterButton - UIButton - - - - - IBProjectSource - ./Classes/FirstTimeUserAddFriendsViewController.h - - - - FirstTimeUserAddNewsBlurViewController - UIViewController - - YES - - YES - tapNewsBlurButton: - tapNextButton - tapPopularButton: - - - YES - id - id - id - - - - YES - - YES - tapNewsBlurButton: - tapNextButton - tapPopularButton: - - - YES - - tapNewsBlurButton: - id - - - tapNextButton - id - - - tapPopularButton: - id - - - - - YES - - YES - appDelegate - instructionsLabel - nextButton - - - YES - NewsBlurAppDelegate - UILabel - UIBarButtonItem - - - - YES - - YES - appDelegate - instructionsLabel - nextButton - - - YES - - appDelegate - NewsBlurAppDelegate - - - instructionsLabel - UILabel - - - nextButton - UIBarButtonItem - - - - - IBProjectSource - ./Classes/FirstTimeUserAddNewsBlurViewController.h - - - - FirstTimeUserAddSitesViewController - UIViewController - - tapNextButton - id - - - tapNextButton - - tapNextButton - id - - - - YES - - YES - activityIndicator - appDelegate - categoriesTable - googleReaderButton - googleReaderButtonWrapper - instructionLabel - nextButton - scrollView - - - YES - UIActivityIndicatorView - NewsBlurAppDelegate - UITableView - UIButton - UIView - UILabel - UIBarButtonItem - UIScrollView - - - - YES - - YES - activityIndicator - appDelegate - categoriesTable - googleReaderButton - googleReaderButtonWrapper - instructionLabel - nextButton - scrollView - - - YES - - activityIndicator - UIActivityIndicatorView - - - appDelegate - NewsBlurAppDelegate - - - categoriesTable - UITableView - - - googleReaderButton - UIButton - - - googleReaderButtonWrapper - UIView - - - instructionLabel - UILabel - - - nextButton - UIBarButtonItem - - - scrollView - UIScrollView - - - - - IBProjectSource - ./Classes/FirstTimeUserAddSitesViewController.h - - - - FirstTimeUserViewController - UIViewController - - tapNextButton - id - - - tapNextButton - - tapNextButton - id - - - - YES - - YES - appDelegate - footer - header - logo - nextButton - - - YES - NewsBlurAppDelegate - UILabel - UILabel - UIImageView - UIBarButtonItem - - - - YES - - YES - appDelegate - footer - header - logo - nextButton - - - YES - - appDelegate - NewsBlurAppDelegate - - - footer - UILabel - - - header - UILabel - - - logo - UIImageView - - - nextButton - UIBarButtonItem - - - - - IBProjectSource - ./Classes/FirstTimeUserViewController.h - - - - FontSettingsViewController - UIViewController - - YES - - YES - changeFontSize: - changeFontStyle: - - - YES - id - id - - - - YES - - YES - changeFontSize: - changeFontStyle: - - - YES - - changeFontSize: - id - - - changeFontStyle: - id - - - - - YES - - YES - appDelegate - fontSizeSegment - fontStyleSegment - largeFontSizeLabel - smallFontSizeLabel - - - YES - NewsBlurAppDelegate - UISegmentedControl - UISegmentedControl - UILabel - UILabel - - - - YES - - YES - appDelegate - fontSizeSegment - fontStyleSegment - largeFontSizeLabel - smallFontSizeLabel - - - YES - - appDelegate - NewsBlurAppDelegate - - - fontSizeSegment - UISegmentedControl - - - fontStyleSegment - UISegmentedControl - - - largeFontSizeLabel - UILabel - - - smallFontSizeLabel - UILabel - - - - - IBProjectSource - ./Classes/FontSettingsViewController.h - - - - FriendsListViewController - UIViewController - - YES - - YES - appDelegate - friendSearchBar - friendsTable - - - YES - NewsBlurAppDelegate - UISearchBar - UITableView - - - - YES - - YES - appDelegate - friendSearchBar - friendsTable - - - YES - - appDelegate - NewsBlurAppDelegate - - - friendSearchBar - UISearchBar - - - friendsTable - UITableView - - - - - IBProjectSource - ./Classes/FriendsListViewController.h - - - - InteractionsModule - UIView - - IBProjectSource - ./Classes/InteractionsModule.h - - - - LoginViewController - UIViewController - - YES - - YES - selectLogin - selectLoginSignup - selectSignUp - tapLoginButton - tapSignUpButton - - - YES - id - id - id - id - id - - - - YES - - YES - selectLogin - selectLoginSignup - selectSignUp - tapLoginButton - tapSignUpButton - - - YES - - selectLogin - id - - - selectLoginSignup - id - - - selectSignUp - id - - - tapLoginButton - id - - - tapSignUpButton - id - - - - - YES - - YES - appDelegate - emailInput - emailLabel - errorLabel - logInView - loginControl - passwordInput - passwordLabel - passwordOptionalLabel - selectLoginButton - selectSignUpButton - signUpPasswordInput - signUpUsernameInput - signUpView - usernameInput - usernameLabel - usernameOrEmailLabel - - - YES - NewsBlurAppDelegate - UITextField - UILabel - UILabel - UIView - UISegmentedControl - UITextField - UILabel - UILabel - UIButton - UIButton - UITextField - UITextField - UIView - UITextField - UILabel - UILabel - - - - YES - - YES - appDelegate - emailInput - emailLabel - errorLabel - logInView - loginControl - passwordInput - passwordLabel - passwordOptionalLabel - selectLoginButton - selectSignUpButton - signUpPasswordInput - signUpUsernameInput - signUpView - usernameInput - usernameLabel - usernameOrEmailLabel - - - YES - - appDelegate - NewsBlurAppDelegate - - - emailInput - UITextField - - - emailLabel - UILabel - - - errorLabel - UILabel - - - logInView - UIView - - - loginControl - UISegmentedControl - - - passwordInput - UITextField - - - passwordLabel - UILabel - - - passwordOptionalLabel - UILabel - - - selectLoginButton - UIButton - - - selectSignUpButton - UIButton - - - signUpPasswordInput - UITextField - - - signUpUsernameInput - UITextField - - - signUpView - UIView - - - usernameInput - UITextField - - - usernameLabel - UILabel - - - usernameOrEmailLabel - UILabel - - - - - IBProjectSource - ./Classes/LoginViewController.h - - - - MoveSiteViewController - UIViewController - - YES - - YES - doCancelButton - doMoveButton - moveFolder - moveSite - - - YES - id - id - id - id - - - - YES - - YES - doCancelButton - doMoveButton - moveFolder - moveSite - - - YES - - doCancelButton - id - - - doMoveButton - id - - - moveFolder - id - - - moveSite - id - - - - - YES - - YES - activityIndicator - appDelegate - cancelButton - errorLabel - folderPicker - fromFolderInput - moveButton - movingLabel - navBar - titleLabel - toFolderInput - - - YES - UIActivityIndicatorView - NewsBlurAppDelegate - UIBarButtonItem - UILabel - UIPickerView - UITextField - UIBarButtonItem - UILabel - UINavigationBar - UILabel - UITextField - - - - YES - - YES - activityIndicator - appDelegate - cancelButton - errorLabel - folderPicker - fromFolderInput - moveButton - movingLabel - navBar - titleLabel - toFolderInput - - - YES - - activityIndicator - UIActivityIndicatorView - - - appDelegate - NewsBlurAppDelegate - - - cancelButton - UIBarButtonItem - - - errorLabel - UILabel - - - folderPicker - UIPickerView - - - fromFolderInput - UITextField - - - moveButton - UIBarButtonItem - - - movingLabel - UILabel - - - navBar - UINavigationBar - - - titleLabel - UILabel - - - toFolderInput - UITextField - - - - - IBProjectSource - ./Classes/MoveSiteViewController.h - - - - NBContainerViewController - UIViewController - - appDelegate - NewsBlurAppDelegate - - - appDelegate - - appDelegate - NewsBlurAppDelegate - - - - IBProjectSource - ./Classes/NBContainerViewController.h - - - - NewsBlurAppDelegate - BaseViewController - - YES - - YES - addSiteViewController - dashboardViewController - feedDashboardViewController - feedDetailViewController - feedsMenuViewController - feedsViewController - findSitesViewController - firstTimeUserAddFriendsViewController - firstTimeUserAddNewsBlurViewController - firstTimeUserAddSitesViewController - firstTimeUserViewController - fontSettingsViewController - friendsListViewController - ftuxNavigationController - loginViewController - masterContainerViewController - moveSiteViewController - navigationController - originalStoryViewController - shareViewController - storyDetailViewController - userProfileViewController - window - - - YES - AddSiteViewController - DashboardViewController - FeedDashboardViewController - FeedDetailViewController - FeedsMenuViewController - NewsBlurViewController - FindSitesViewController - FirstTimeUserAddFriendsViewController - FirstTimeUserAddNewsBlurViewController - FirstTimeUserAddSitesViewController - FirstTimeUserViewController - FontSettingsViewController - FriendsListViewController - UINavigationController - LoginViewController - NBContainerViewController - MoveSiteViewController - UINavigationController - OriginalStoryViewController - ShareViewController - StoryDetailViewController - UserProfileViewController - UIWindow - - - - YES - - YES - addSiteViewController - dashboardViewController - feedDashboardViewController - feedDetailViewController - feedsMenuViewController - feedsViewController - findSitesViewController - firstTimeUserAddFriendsViewController - firstTimeUserAddNewsBlurViewController - firstTimeUserAddSitesViewController - firstTimeUserViewController - fontSettingsViewController - friendsListViewController - ftuxNavigationController - loginViewController - masterContainerViewController - moveSiteViewController - navigationController - originalStoryViewController - shareViewController - storyDetailViewController - userProfileViewController - window - - - YES - - addSiteViewController - AddSiteViewController - - - dashboardViewController - DashboardViewController - - - feedDashboardViewController - FeedDashboardViewController - - - feedDetailViewController - FeedDetailViewController - - - feedsMenuViewController - FeedsMenuViewController - - - feedsViewController - NewsBlurViewController - - - findSitesViewController - FindSitesViewController - - - firstTimeUserAddFriendsViewController - FirstTimeUserAddFriendsViewController - - - firstTimeUserAddNewsBlurViewController - FirstTimeUserAddNewsBlurViewController - - - firstTimeUserAddSitesViewController - FirstTimeUserAddSitesViewController - - - firstTimeUserViewController - FirstTimeUserViewController - - - fontSettingsViewController - FontSettingsViewController - - - friendsListViewController - FriendsListViewController - - - ftuxNavigationController - UINavigationController - - - loginViewController - LoginViewController - - - masterContainerViewController - NBContainerViewController - - - moveSiteViewController - MoveSiteViewController - - - navigationController - UINavigationController - - - originalStoryViewController - OriginalStoryViewController - - - shareViewController - ShareViewController - - - storyDetailViewController - StoryDetailViewController - - - userProfileViewController - UserProfileViewController - - - window - UIWindow - - - - - IBProjectSource - ./Classes/NewsBlurAppDelegate.h - - - - NewsBlurViewController - BaseViewController - - YES - - YES - sectionTapped: - sectionUntapped: - sectionUntappedOutside: - selectIntelligence - tapAddSite: - - - YES - UIButton - UIButton - UIButton - id - id - - - - YES - - YES - sectionTapped: - sectionUntapped: - sectionUntappedOutside: - selectIntelligence - tapAddSite: - - - YES - - sectionTapped: - UIButton - - - sectionUntapped: - UIButton - - - sectionUntappedOutside: - UIButton - - - selectIntelligence - id - - - tapAddSite: - id - - - - - YES - - YES - appDelegate - feedScoreSlider - feedTitlesTable - feedViewToolbar - homeButton - innerView - intelligenceControl - noFocusMessage - toolbarLeftMargin - - - YES - NewsBlurAppDelegate - UISlider - UITableView - UIToolbar - UIBarButtonItem - UIView - UISegmentedControl - UIView - UIBarButtonItem - - - - YES - - YES - appDelegate - feedScoreSlider - feedTitlesTable - feedViewToolbar - homeButton - innerView - intelligenceControl - noFocusMessage - toolbarLeftMargin - - - YES - - appDelegate - NewsBlurAppDelegate - - - feedScoreSlider - UISlider - - - feedTitlesTable - UITableView - - - feedViewToolbar - UIToolbar - - - homeButton - UIBarButtonItem - - - innerView - UIView - - - intelligenceControl - UISegmentedControl - - - noFocusMessage - UIView - - - toolbarLeftMargin - UIBarButtonItem - - - - - IBProjectSource - ./Classes/NewsBlurViewController.h - - - - OriginalStoryViewController - BaseViewController - - YES - - YES - doCloseOriginalStoryViewController - doOpenActionSheet - loadAddress: - - - YES - id - id - id - - - - YES - - YES - doCloseOriginalStoryViewController - doOpenActionSheet - loadAddress: - - - YES - - doCloseOriginalStoryViewController - id - - - doOpenActionSheet - id - - - loadAddress: - id - - - - - YES - - YES - appDelegate - back - closeButton - forward - pageAction - pageTitle - pageUrl - refresh - toolbar - webView - - - YES - NewsBlurAppDelegate - UIBarButtonItem - UIBarButtonItem - UIBarButtonItem - UIBarButtonItem - UILabel - UITextField - UIBarButtonItem - UIToolbar - UIWebView - - - - YES - - YES - appDelegate - back - closeButton - forward - pageAction - pageTitle - pageUrl - refresh - toolbar - webView - - - YES - - appDelegate - NewsBlurAppDelegate - - - back - UIBarButtonItem - - - closeButton - UIBarButtonItem - - - forward - UIBarButtonItem - - - pageAction - UIBarButtonItem - - - pageTitle - UILabel - - - pageUrl - UITextField - - - refresh - UIBarButtonItem - - - toolbar - UIToolbar - - - webView - UIWebView - - - - - IBProjectSource - ./Classes/OriginalStoryViewController.h - - - - ShareViewController - UIViewController - - YES - - YES - doCancelButton: - doReplyToComment: - doShareThisStory: - doToggleButton: - - - YES - id - id - id - id - - - - YES - - YES - doCancelButton: - doReplyToComment: - doShareThisStory: - doToggleButton: - - - YES - - doCancelButton: - id - - - doReplyToComment: - id - - - doShareThisStory: - id - - - doToggleButton: - id - - - - - YES - - YES - appDelegate - commentField - facebookButton - submitButton - twitterButton - - - YES - NewsBlurAppDelegate - UITextView - UIButton - UIBarButtonItem - UIButton - - - - YES - - YES - appDelegate - commentField - facebookButton - submitButton - twitterButton - - - YES - - appDelegate - NewsBlurAppDelegate - - - commentField - UITextView - - - facebookButton - UIButton - - - submitButton - UIBarButtonItem - - - twitterButton - UIButton - - - - - IBProjectSource - ./Classes/ShareViewController.h - - - - StoryDetailViewController - UIViewController - - YES - - YES - doNextStory - doNextUnreadStory - doPreviousStory - showOriginalSubview: - tapProgressBar: - toggleFontSize: - - - YES - id - id - id - id - id - id - - - - YES - - YES - doNextStory - doNextUnreadStory - doPreviousStory - showOriginalSubview: - tapProgressBar: - toggleFontSize: - - - YES - - doNextStory - id - - - doNextUnreadStory - id - - - doPreviousStory - id - - - showOriginalSubview: - id - - - tapProgressBar: - id - - - toggleFontSize: - id - - - - - YES - - YES - activity - appDelegate - bottomPlaceholderToolbar - buttonAction - buttonNext - buttonNextStory - buttonPrevious - feedTitleGradient - fontSettingsButton - innerView - noStorySelectedLabel - originalStoryButton - progressView - progressViewContainer - subscribeButton - toolbar - webView - - - YES - UIBarButtonItem - NewsBlurAppDelegate - UIToolbar - UIBarButtonItem - UIBarButtonItem - UIBarButtonItem - UIBarButtonItem - UIView - UIBarButtonItem - UIView - UILabel - UIBarButtonItem - UIProgressView - UIView - UIBarButtonItem - UIToolbar - UIWebView - - - - YES - - YES - activity - appDelegate - bottomPlaceholderToolbar - buttonAction - buttonNext - buttonNextStory - buttonPrevious - feedTitleGradient - fontSettingsButton - innerView - noStorySelectedLabel - originalStoryButton - progressView - progressViewContainer - subscribeButton - toolbar - webView - - - YES - - activity - UIBarButtonItem - - - appDelegate - NewsBlurAppDelegate - - - bottomPlaceholderToolbar - UIToolbar - - - buttonAction - UIBarButtonItem - - - buttonNext - UIBarButtonItem - - - buttonNextStory - UIBarButtonItem - - - buttonPrevious - UIBarButtonItem - - - feedTitleGradient - UIView - - - fontSettingsButton - UIBarButtonItem - - - innerView - UIView - - - noStorySelectedLabel - UILabel - - - originalStoryButton - UIBarButtonItem - - - progressView - UIProgressView - - - progressViewContainer - UIView - - - subscribeButton - UIBarButtonItem - - - toolbar - UIToolbar - - - webView - UIWebView - - - - - IBProjectSource - ./Classes/StoryDetailViewController.h - - - - UserProfileViewController - UIViewController - - IBProjectSource - ./Classes/UserProfileViewController.h - - - - + 0 IBCocoaTouchFramework diff --git a/media/ios/Resources/disclosure.png b/media/ios/Resources/disclosure.png index 16a456c07..1248912e7 100644 Binary files a/media/ios/Resources/disclosure.png and b/media/ios/Resources/disclosure.png differ diff --git a/media/ios/Resources/disclosure_border.png b/media/ios/Resources/disclosure_border.png new file mode 100644 index 000000000..9466d325b Binary files /dev/null and b/media/ios/Resources/disclosure_border.png differ diff --git a/media/js/newsblur/common/router.js b/media/js/newsblur/common/router.js index f1ff0b824..cd4f018ec 100644 --- a/media/js/newsblur/common/router.js +++ b/media/js/newsblur/common/router.js @@ -34,25 +34,30 @@ NEWSBLUR.Router = Backbone.Router.extend({ site_id = parseInt(site_id, 10); var feed = NEWSBLUR.assets.get_feed(site_id); if (feed) { - NEWSBLUR.reader.open_feed(site_id, {force: true}); + NEWSBLUR.reader.open_feed(site_id, {router: true, force: true}); } else { - NEWSBLUR.reader.load_feed_in_tryfeed_view(site_id, {force: true, feed: { - feed_title: _.string.humanize(slug || "") - }}); + NEWSBLUR.reader.load_feed_in_tryfeed_view(site_id, { + router: true, + force: true, + feed: { + feed_title: _.string.humanize(slug || "") + } + }); } }, folder: function(folder_name) { folder_name = folder_name.replace(/-/g, ' '); // NEWSBLUR.log(["folder", folder_name]); + var options = {router: true}; if (folder_name == "everything") { - NEWSBLUR.reader.open_river_stories(); + NEWSBLUR.reader.open_river_stories(null, null, options); } else if (folder_name == "blurblogs") { - NEWSBLUR.reader.open_river_blurblogs_stories(); + NEWSBLUR.reader.open_river_blurblogs_stories(options); } else { var folder = NEWSBLUR.assets.get_folder(folder_name); if (folder) { - NEWSBLUR.reader.open_river_stories(folder.folder_view.$el, folder); + NEWSBLUR.reader.open_river_stories(folder.folder_view.$el, folder, options); } } }, @@ -61,14 +66,18 @@ NEWSBLUR.Router = Backbone.Router.extend({ // NEWSBLUR.log(["router:social", user_id, slug]); var feed_id = "social:" + user_id; if (NEWSBLUR.assets.get_feed(feed_id)) { - NEWSBLUR.reader.open_social_stories(feed_id, {force: true}); + NEWSBLUR.reader.open_social_stories(feed_id, {router: true, force: true}); } else { - NEWSBLUR.reader.load_social_feed_in_tryfeed_view(feed_id, {force: true, feed: { - username: _.string.humanize(slug), - id: feed_id, - user_id: parseInt(user_id, 10), - feed_title: _.string.humanize(slug) - }}); + NEWSBLUR.reader.load_social_feed_in_tryfeed_view(feed_id, { + router: true, + force: true, + feed: { + username: _.string.humanize(slug), + id: feed_id, + user_id: parseInt(user_id, 10), + feed_title: _.string.humanize(slug) + } + }); } }, diff --git a/media/js/newsblur/models/social_subscription.js b/media/js/newsblur/models/social_subscription.js index ede1a25d0..e89d3554f 100644 --- a/media/js/newsblur/models/social_subscription.js +++ b/media/js/newsblur/models/social_subscription.js @@ -5,9 +5,14 @@ NEWSBLUR.Models.SocialSubscription = Backbone.Model.extend({ this.set('page_url', '/social/page/' + this.get('user_id')); } - _.bindAll(this, 'on_change', 'on_remove'); + _.bindAll(this, 'on_change', 'on_remove', 'update_counts'); // this.bind('change', this.on_change); this.bind('remove', this.on_remove); + + this.bind('change:ps', this.update_counts); + this.bind('change:nt', this.update_counts); + this.bind('change:ng', this.update_counts); + this.views = []; }, @@ -19,6 +24,10 @@ NEWSBLUR.Models.SocialSubscription = Backbone.Model.extend({ NEWSBLUR.log(["Remove Feed", this, this.views]); _.each(this.views, function(view) { view.remove(); }); }, + + update_counts: function() { + NEWSBLUR.assets.social_feeds.trigger('change:counts'); + }, is_social: function() { return true; @@ -79,6 +88,22 @@ NEWSBLUR.Collections.SocialSubscriptions = Backbone.Collection.extend({ }).each(function(feed){ feed.set('selected', false); }); + }, + + unread_counts: function() { + var counts = this.reduce(function(counts, item) { + var feed_counts = item.unread_counts(); + counts['ps'] += feed_counts['ps']; + counts['nt'] += feed_counts['nt']; + counts['ng'] += feed_counts['ng']; + return counts; + }, { + ps: 0, + nt: 0, + ng: 0 + }); + + return counts; } }); \ No newline at end of file diff --git a/media/js/newsblur/models/stories.js b/media/js/newsblur/models/stories.js index 08e17bdaf..4d19c36af 100644 --- a/media/js/newsblur/models/stories.js +++ b/media/js/newsblur/models/stories.js @@ -32,10 +32,8 @@ NEWSBLUR.Models.Story = Backbone.Model.extend({ }, has_modifications: function() { - if (this.get('story_content').indexOf(' 0) { var active_count = Math.max(active_feed.get('ps') + (options.unread?1:-1), 0); - var story_count = Math.max(story_feed.get('ps') + (options.unread?1:-1), 0); + var story_count = story_feed && Math.max(story_feed.get('ps') + (options.unread?1:-1), 0); active_feed.set('ps', active_count, {instant: true}); - story_feed.set('ps', story_count, {instant: true}); + if (story_feed) story_feed.set('ps', story_count, {instant: true}); _.each(friend_feeds, function(socialsub) { var socialsub_count = Math.max(socialsub.get('ps') + (options.unread?1:-1), 0); socialsub.set('ps', socialsub_count, {instant: true}); }); } else if (story.score() == 0) { var active_count = Math.max(active_feed.get('nt') + (options.unread?1:-1), 0); - var story_count = Math.max(story_feed.get('nt') + (options.unread?1:-1), 0); + var story_count = story_feed && Math.max(story_feed.get('nt') + (options.unread?1:-1), 0); active_feed.set('nt', active_count, {instant: true}); - story_feed.set('nt', story_count, {instant: true}); + if (story_feed) story_feed.set('nt', story_count, {instant: true}); _.each(friend_feeds, function(socialsub) { var socialsub_count = Math.max(socialsub.get('nt') + (options.unread?1:-1), 0); socialsub.set('nt', socialsub_count, {instant: true}); }); } else if (story.score() < 0) { var active_count = Math.max(active_feed.get('ng') + (options.unread?1:-1), 0); - var story_count = Math.max(story_feed.get('ng') + (options.unread?1:-1), 0); + var story_count = story_feed && Math.max(story_feed.get('ng') + (options.unread?1:-1), 0); active_feed.set('ng', active_count, {instant: true}); - story_feed.set('ng', story_count, {instant: true}); + if (story_feed) story_feed.set('ng', story_count, {instant: true}); _.each(friend_feeds, function(socialsub) { var socialsub_count = Math.max(socialsub.get('ng') + (options.unread?1:-1), 0); socialsub.set('ng', socialsub_count, {instant: true}); @@ -304,7 +302,7 @@ NEWSBLUR.Collections.Stories = Backbone.Collection.extend({ detect_selected_story: function(selected_story, selected) { if (selected) { - this.deselect(selected_story); + this.deselect_other_stories(selected_story); this.active_story = selected_story; NEWSBLUR.reader.active_story = selected_story; this.previous_stories_stack.push(selected_story); diff --git a/media/js/newsblur/reader/reader.js b/media/js/newsblur/reader/reader.js index bc3fd0728..331d43139 100644 --- a/media/js/newsblur/reader/reader.js +++ b/media/js/newsblur/reader/reader.js @@ -2,7 +2,9 @@ NEWSBLUR.Reader = Backbone.Router.extend({ - init: function() { + init: function(options) { + + var defaults = {}; // =========== // = Globals = @@ -11,11 +13,14 @@ NEWSBLUR.assets = new NEWSBLUR.AssetModel(); this.model = NEWSBLUR.assets; this.story_view = 'page'; + this.options = _.extend({}, defaults, options); this.$s = { $body: $('body'), + $layout: $('.NB-layout'), + $sidebar: $('.NB-sidebar'), $feed_lists: $('.NB-feedlists'), $feed_list: $('#feed_list'), - $social_feeds: $('.NB-socialfeeds'), + $social_feeds: $('.NB-socialfeeds-folder'), $story_titles: $('#story_titles'), $content_pane: $('.content-pane'), $story_taskbar: $('#story_taskbar'), @@ -41,7 +46,8 @@ 'bouncing_callout': false, 'has_unfetched_feeds': false, 'count_unreads_after_import_working': false, - 'import_from_google_reader_working': false + 'import_from_google_reader_working': false, + 'sidebar_closed': this.options.hide_sidebar }; this.locks = {}; this.counts = { @@ -99,12 +105,14 @@ if (NEWSBLUR.Flags['start_import_from_google_reader']) { this.start_import_from_google_reader(); } - NEWSBLUR.app.feed_list_header = new NEWSBLUR.Views.FeedListHeader({collection: NEWSBLUR.assets.feeds}); + NEWSBLUR.app.sidebar_header = new NEWSBLUR.Views.SidebarHeader({collection: NEWSBLUR.assets.feeds}); + NEWSBLUR.app.sidebar = new NEWSBLUR.Views.Sidebar(); NEWSBLUR.app.feed_list = new NEWSBLUR.Views.FeedList({el: this.$s.$feed_list[0]}); NEWSBLUR.app.story_titles = new NEWSBLUR.Views.StoryTitlesView({collection: NEWSBLUR.assets.stories}); NEWSBLUR.app.story_list = new NEWSBLUR.Views.StoryListView({collection: NEWSBLUR.assets.stories}); NEWSBLUR.app.original_tab_view = new NEWSBLUR.Views.OriginalTabView({collection: NEWSBLUR.assets.stories}); NEWSBLUR.app.story_tab_view = new NEWSBLUR.Views.StoryTabView({collection: NEWSBLUR.assets.stories}); + NEWSBLUR.app.feed_selector = new NEWSBLUR.Views.FeedSelector(); this.load_intelligence_slider(); this.handle_mouse_indicator_hover(); @@ -197,17 +205,17 @@ .removeClass('NB-story-pane-south') .addClass('NB-story-pane-'+story_anchor); - this.layout.outerLayout = this.$s.$body.layout({ - closable: true, - zIndex: 1, - fxName: "slide", + this.layout.outerLayout = this.$s.$layout.layout({ + zIndex: 2, + fxName: "slideOffscreen", fxSettings: { duration: 560, easing: "easeInOutQuint" }, center__paneSelector: ".right-pane", west__paneSelector: ".left-pane", west__size: this.model.preference('feed_pane_size'), west__minSize: this.constants.MIN_FEED_LIST_SIZE, west__onresize_end: $.rescope(this.save_feed_pane_size, this), - spacing_open: 4, + // west__initHidden: this.options.hide_sidebar, + west__spacing_open: this.options.hide_sidebar ? 1 : 6, resizerDragOpacity: 0.6, resizeWhileDragging: true, enableCursorHotkey: false @@ -220,8 +228,9 @@ this.layout.leftLayout = $('.left-pane').layout({ closable: false, resizeWhileDragging: true, - fxName: "slide", + fxName: "slideOffscreen", fxSettings: { duration: 560, easing: "easeInOutQuint" }, + animatePaneSizing: true, north__paneSelector: ".left-north", north__size: 18, north__resizeable: false, @@ -249,7 +258,7 @@ south__spacing_closed: 0, south__closable: true, south__initClosed: true, - fxName: "slide", + fxName: "slideOffscreen", fxSettings: { duration: 560, easing: "easeInOutQuint" }, enableCursorHotkey: false }); @@ -260,7 +269,7 @@ spacing_open: story_anchor == 'west' ? 4 : 10, resizerDragOpacity: 0.6, enableCursorHotkey: false, - fxName: "slide", + fxName: "slideOffscreen", fxSettings: { duration: 560, easing: "easeInOutQuint" } }; rightLayoutOptions[story_anchor+'__paneSelector'] = '.right-north'; @@ -385,13 +394,13 @@ resize = true; } $('.right-pane').show(); - $('#NB-splash').hide(); + $('#NB-splash,.NB-splash').hide(); $('.NB-splash-info').hide(); $('#NB-splash-overlay').hide(); this.$s.$dashboard.addClass('NB-active'); if (resize) { - this.$s.$body.layout().resizeAll(); + this.$s.$layout.layout().resizeAll(); } if (NEWSBLUR.Globals.is_anonymous) { this.setup_ftux_signup_callout(); @@ -400,11 +409,9 @@ show_splash_page: function(skip_router) { this.reset_feed(); - NEWSBLUR.app.original_tab_view.unload_feed_iframe(); - NEWSBLUR.app.story_tab_view.unload_story_iframe(); $('.right-pane').hide(); $('.NB-splash-info').show(); - $('#NB-splash').show(); + $('#NB-splash,.NB-splash').show(); $('#NB-splash-overlay').show(); this.$s.$dashboard.removeClass('NB-active'); if (!skip_router) { @@ -489,7 +496,6 @@ if (unread_count) { var next_story = NEWSBLUR.assets.stories.get_next_unread_story(); - if (next_story) { this.counts['find_next_unread_on_page_of_feed_stories_load'] = 0; next_story.set('selected', true); @@ -584,7 +590,7 @@ return this.show_next_folder(direction, $current_feed); } - var $next_feed = this.get_next_feed(direction, $current_feed); + var $next_feed = this.get_next_feed(direction, $current_feed, {include_selected: true}); var next_feed_id = $next_feed.data('id'); if (next_feed_id && next_feed_id == this.active_feed) { @@ -604,15 +610,19 @@ this.open_river_stories($next_folder, folder && folder.model); }, - get_next_feed: function(direction, $current_feed) { + get_next_feed: function(direction, $current_feed, options) { + options = options || {}; var self = this; var $feed_list = this.$s.$feed_list.add(this.$s.$social_feeds); var $current_feed = $current_feed || $('.selected', $feed_list); var $next_feed, scroll; - var $feeds = $('.feed:visible:not(.NB-empty)', $feed_list).add('.NB-feedlists .feed.NB-selected'); + var $feeds = $('.feed:visible:not(.NB-empty)', $feed_list); if (!$current_feed.length) { - $current_feed = $('.feed:visible:not(.NB-empty)', $feed_list)[direction==1?'first':'last'](); + if (options.include_selected) { + $feeds = $feeds.add('.NB-feedlists .feed.NB-selected'); + } + $current_feed = $('.feed:visible:not(.NB-empty)', $feed_list)[direction==-1?'last':'first'](); $next_feed = $current_feed; } else { var current_feed = 0; @@ -746,9 +756,7 @@ find_story_with_action_preference_on_open_feed: function() { var open_feed_action = this.model.preference('open_feed_action'); - if (this.counts['page'] != 1) return; - - if (open_feed_action == 'newest') { + if (!this.active_story && open_feed_action == 'newest') { this.show_next_unread_story(); } }, @@ -965,7 +973,9 @@ // = Feed bar - Individual Feeds = // =============================== - reset_feed: function() { + reset_feed: function(options) { + options = options || {}; + $.extend(this.flags, { 'scrolling_by_selecting_story_title': false, 'page_view_showing_feed_view': false, @@ -1025,12 +1035,22 @@ if (this.flags['showing_feed_in_tryfeed_view'] || this.flags['showing_social_feed_in_tryfeed_view']) { this.hide_tryfeed_view(); } + if (NEWSBLUR.Globals.is_anonymous) { + if (options.router) { + this.$s.$layout.layout().show('west', true); + this.$s.$layout.show(); + } + this.hide_tryout_signup_button(); + } this.active_folder = null; this.active_feed = null; this.active_story = null; NEWSBLUR.assets.stories.reset(); + NEWSBLUR.app.feed_selector.hide_feed_selector(); + NEWSBLUR.app.original_tab_view.unload_feed_iframe(); + NEWSBLUR.app.story_tab_view.unload_story_iframe(); }, open_feed: function(feed_id, options) { @@ -1049,7 +1069,7 @@ this.flags['opening_feed'] = true; if (options.try_feed || feed) { - this.reset_feed(); + this.reset_feed(options); this.hide_splash_page(); if (options.story_id) { this.flags['select_story_in_feed'] = options.story_id; @@ -1086,6 +1106,7 @@ }, this), options.delay || 0); } else { NEWSBLUR.app.original_tab_view.unload_feed_iframe(); + NEWSBLUR.app.story_tab_view.unload_story_iframe(); this.flags['iframe_prevented_from_loading'] = true; } this.setup_mousemove_on_views(); @@ -1115,9 +1136,7 @@ this.active_feed = data.feed_id; } - // NEWSBLUR.log(['post_open_feed', data.stories, this.flags]); this.flags['opening_feed'] = false; - this.find_story_with_action_preference_on_open_feed(); NEWSBLUR.app.story_titles_header.show_feed_hidden_story_title_indicator(true); this.show_story_titles_above_intelligence_level({'animate': false}); if (this.counts['find_next_unread_on_page_of_feed_stories_load']) { @@ -1129,15 +1148,20 @@ } this.flags['story_titles_loaded'] = true; if (first_load) { + this.make_story_titles_pane_counter(); + this.find_story_with_action_preference_on_open_feed(); + if (this.story_view == 'story' && + !this.active_story && !this.counts['find_next_unread_on_page_of_feed_stories_load']) { this.show_next_story(1); } - - this.make_story_titles_pane_counter(); } + this.hide_stories_progress_bar(); - if (this.flags['showing_feed_in_tryfeed_view']) { + if (NEWSBLUR.Globals.is_anonymous) { + this.show_tryout_signup_button(); + } else if (this.flags['showing_feed_in_tryfeed_view']) { this.show_tryfeed_add_button(); this.correct_tryfeed_title(); } @@ -1258,10 +1282,12 @@ if (this.active_feed == 'starred') { // NEWSBLUR.log(['post_open_starred_stories', data.stories.length, first_load]); this.flags['opening_feed'] = false; - this.find_story_with_action_preference_on_open_feed(); if (this.counts['select_story_in_feed'] || this.flags['select_story_in_feed']) { this.select_story_in_feed(); } + if (first_load) { + this.find_story_with_action_preference_on_open_feed(); + } this.show_story_titles_above_intelligence_level({'animate': false}); this.flags['story_titles_loaded'] = true; } @@ -1279,7 +1305,7 @@ this.active_folder && this.active_folder.folder_view; var folder_title = folder && folder.get('folder_title') || "Everything"; - this.reset_feed(); + this.reset_feed(options); this.hide_splash_page(); this.active_folder = folder || new Backbone.Model({ folder_title: folder_title, @@ -1341,7 +1367,6 @@ } this.flags['opening_feed'] = false; NEWSBLUR.app.story_titles_header.show_feed_hidden_story_title_indicator(true); - this.find_story_with_action_preference_on_open_feed(); this.show_story_titles_above_intelligence_level({'animate': false}); this.flags['story_titles_loaded'] = true; if (this.counts['find_next_unread_on_page_of_feed_stories_load']) { @@ -1351,8 +1376,13 @@ } else if (this.counts['select_story_in_feed'] || this.flags['select_story_in_feed']) { this.select_story_in_feed(); } - this.hide_stories_progress_bar(); if (first_load) { + this.find_story_with_action_preference_on_open_feed(); + } + this.hide_stories_progress_bar(); + if (NEWSBLUR.Globals.is_anonymous) { + this.show_tryout_signup_button(); + } else if (first_load) { this.make_story_titles_pane_counter(); } } @@ -1396,7 +1426,7 @@ var $story_titles = this.$s.$story_titles; var folder_title = "Blurblogs"; - this.reset_feed(); + this.reset_feed(options); this.hide_splash_page(); this.active_folder = new Backbone.Model({ @@ -1454,7 +1484,6 @@ } this.flags['opening_feed'] = false; NEWSBLUR.app.story_titles_header.show_feed_hidden_story_title_indicator(true); - this.find_story_with_action_preference_on_open_feed(); this.show_story_titles_above_intelligence_level({'animate': false}); this.flags['story_titles_loaded'] = true; if (this.counts['find_next_unread_on_page_of_feed_stories_load']) { @@ -1464,7 +1493,13 @@ } else if (this.counts['select_story_in_feed'] || this.flags['select_story_in_feed']) { this.select_story_in_feed(); } + if (first_load) { + this.find_story_with_action_preference_on_open_feed(); + } this.hide_stories_progress_bar(); + if (NEWSBLUR.Globals.is_anonymous) { + this.show_tryout_signup_button(); + } } }, @@ -1490,7 +1525,7 @@ return this.load_social_feed_in_tryfeed_view(socialsub, options); } - this.reset_feed(); + this.reset_feed(options); this.hide_splash_page(); this.active_feed = feed.id; @@ -1553,7 +1588,6 @@ if (this.active_feed && NEWSBLUR.utils.is_feed_social(this.active_feed)) { this.flags['opening_feed'] = false; - this.find_story_with_action_preference_on_open_feed(); this.show_story_titles_above_intelligence_level({'animate': false}); NEWSBLUR.app.story_titles_header.show_feed_hidden_story_title_indicator(true); this.flags['story_titles_loaded'] = true; @@ -1564,9 +1598,13 @@ } else if (this.counts['find_last_unread_on_page_of_feed_stories_load']) { this.show_last_unread_story(true); } + if (first_load) { + this.find_story_with_action_preference_on_open_feed(); + } this.hide_stories_progress_bar(); - - if (this.flags['showing_social_feed_in_tryfeed_view']) { + if (NEWSBLUR.Globals.is_anonymous) { + this.show_tryout_signup_button(); + } else if (this.flags['showing_social_feed_in_tryfeed_view']) { this.show_tryfeed_follow_button(); this.correct_tryfeed_title(); } @@ -1711,6 +1749,17 @@ } }, + mark_active_story_read: function() { + if (!this.active_story) return; + var story_id = this.active_story.id; + var story = this.model.get_story(story_id); + if (this.active_story && !this.active_story.get('read_status')) { + NEWSBLUR.assets.stories.mark_read(story, {skip_delay: true}); + } else if (this.active_story && this.active_story.get('read_status')) { + NEWSBLUR.assets.stories.mark_unread(story); + } + }, + mark_feed_as_read: function(feed_id) { feed_id = feed_id || this.active_feed; @@ -1753,14 +1802,16 @@ open_story_trainer: function(story_id, feed_id, options) { options = options || {}; - console.log(["open_story_trainer", story_id, feed_id, options]); story_id = story_id || this.active_story && this.active_story.id; feed_id = feed_id || (story_id && this.model.get_story(story_id).get('story_feed_id')); + // console.log(["open_story_trainer", story_id, feed_id, options]); if (story_id && feed_id) { options['feed_loaded'] = !this.flags['river_view']; - if (this.flags['social_view']) { + if (this.flags['social_view'] && !_.string.contains(this.active_feed, 'river:')) { options['social_feed_id'] = this.active_feed; + } else if (this.flags['social_view'] && this.active_story.get('friend_user_ids')) { + options['social_feed_id'] = 'social:' + this.active_story.get('friend_user_ids')[0]; } NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifierStory(story_id, feed_id, options); } @@ -2347,8 +2398,18 @@ } }, + toggle_sidebar: function() { + if (this.flags['sidebar_closed']) { + this.open_sidebar(); + return true; + } else { + this.close_sidebar(); + return false; + } + }, + close_sidebar: function() { - this.$s.$body.layout().close('west'); + this.$s.$layout.layout().hide('west'); this.resize_window(); this.flags['sidebar_closed'] = true; $('.NB-taskbar-sidebar-toggle-open').stop().animate({ @@ -2361,7 +2422,7 @@ }, open_sidebar: function() { - this.$s.$body.layout().open('west'); + this.$s.$layout.layout().open('west'); this.resize_window(); this.flags['sidebar_closed'] = false; $('.NB-taskbar-sidebar-toggle-open').stop().css({ @@ -3020,7 +3081,6 @@ this.flags['feed_list_showing_manage_menu'] = false; $(document).unbind('click.menu'); $(document).unbind('mouseup.menu'); - $manage_menu_container.uncorner(); if (this.model.preference('show_tooltips')) { $('.NB-task-manage').tipsy('enable'); } @@ -3403,7 +3463,7 @@ NEWSBLUR.app.story_titles_header.show_feed_hidden_story_title_indicator(true); } this.show_story_titles_above_intelligence_level({'animate': true, 'follow': true}); - NEWSBLUR.app.feed_list_header.toggle_hide_read_preference(); + NEWSBLUR.app.sidebar_header.toggle_hide_read_preference(); NEWSBLUR.app.feed_list.scroll_to_show_selected_feed(); NEWSBLUR.app.feed_list.scroll_to_show_selected_folder(); @@ -3424,20 +3484,15 @@ switch_feed_view_unread_view: function(unread_view) { if (!_.isNumber(unread_view)) unread_view = this.get_unread_view_score(); - var $feed_list = this.$s.$feed_list; - var $social_feeds = this.$s.$social_feeds; + var $sidebar = this.$s.$sidebar; var unread_view_name = this.get_unread_view_name(unread_view); var $next_story_button = $('.task_story_next_unread'); var $story_title_indicator = $('.NB-story-title-indicator', this.$story_titles); - $feed_list.removeClass('unread_view_positive') - .removeClass('unread_view_neutral') - .removeClass('unread_view_negative') - .addClass('unread_view_'+unread_view_name); - $social_feeds.removeClass('unread_view_positive') - .removeClass('unread_view_neutral') - .removeClass('unread_view_negative') - .addClass('unread_view_'+unread_view_name); + $sidebar.removeClass('unread_view_positive') + .removeClass('unread_view_neutral') + .removeClass('unread_view_negative') + .addClass('unread_view_'+unread_view_name); $next_story_button.removeClass('task_story_next_positive') .removeClass('task_story_next_neutral') @@ -4224,7 +4279,7 @@ }, options.feed && options.feed.attributes); var $tryfeed_container = this.$s.$tryfeed_header.closest('.NB-feeds-header-container'); - this.reset_feed(); + this.reset_feed(options); feed = this.model.set_feed(feed_id, feed); $('.NB-feeds-header-title', this.$s.$tryfeed_header).text(feed.get('feed_title')); @@ -4276,7 +4331,6 @@ }, show_tryfeed_add_button: function() { - NEWSBLUR.log(["show_tryfeed_add_button", this.$s.$story_taskbar.find('.NB-tryfeed-add:visible').length]); if (this.$s.$story_taskbar.find('.NB-tryfeed-add:visible').length) return; var $add = $.make('div', { className: 'NB-modal-submit' }, [ @@ -4302,6 +4356,20 @@ $add.animate({'opacity': 1}, {'duration': 600}); }, + show_tryout_signup_button: function() { + if (this.$s.$story_taskbar.find('.NB-tryout-signup:visible').length) return; + + var $add = $.make('div', { className: 'NB-modal-submit' }, [ + $.make('div', { className: 'NB-tryout-signup NB-modal-submit-green NB-modal-submit-button' }, 'Sign Up') + ]).css({'opacity': 0}); + this.$s.$story_taskbar.find('.NB-taskbar').append($add); + $add.animate({'opacity': 1}, {'duration': 600}); + }, + + hide_tryout_signup_button: function() { + this.$s.$story_taskbar.find('.NB-tryout-signup:visible').remove(); + }, + add_recommended_feed: function(feed_id) { feed_id = feed_id || this.active_feed; var feed_address = this.model.get_feed(feed_id).get('feed_address'); @@ -4444,25 +4512,7 @@ var self = this; var stopPropagation = false; - // NEWSBLUR.log(['click', e, e.button]); - - // = Feed Header ================================================== - - $.targetIs(e, { tagSelector: '.NB-feeds-header-starred' }, function($t, $p){ - e.preventDefault(); - self.open_starred_stories(); - }); - $.targetIs(e, { tagSelector: '.NB-feeds-header-river-sites' }, function($t, $p){ - e.preventDefault(); - self.open_river_stories(); - }); - $.targetIs(e, { tagSelector: '.NB-feeds-header-river-blurblogs' }, function($t, $p){ - e.preventDefault(); - self.open_river_blurblogs_stories(); - }); - - // = Stories ====================================================== - + // NEWSBLUR.log(['click', e, e.button]); // = Taskbar ====================================================== @@ -4894,10 +4944,6 @@ e.preventDefault(); self.show_previous_story(); }); - $.targetIs(e, { tagSelector: '.task_button_signup' }, function($t, $p){ - e.preventDefault(); - self.show_splash_page(); - }); $.targetIs(e, { tagSelector: '.NB-intelligence-slider-control' }, function($t, $p){ e.preventDefault(); var unread_value; @@ -4995,6 +5041,13 @@ var feed_id = self.active_feed; self.follow_user_in_tryfeed(feed_id); }); + $.targetIs(e, { tagSelector: '.NB-tryout-signup' }, function($t, $p){ + e.preventDefault(); + self.show_splash_page(); + if (NEWSBLUR.welcome) { + NEWSBLUR.welcome.show_signin_form(); + } + }); // = Interactions Module ========================================== @@ -5222,11 +5275,7 @@ }); $document.bind('keydown', 'shift+u', function(e) { e.preventDefault(); - if (self.flags['sidebar_closed']) { - self.open_sidebar(); - } else { - self.close_sidebar(); - } + self.toggle_sidebar(); }); $document.bind('keydown', 'shift+t', function(e) { e.preventDefault(); @@ -5326,14 +5375,15 @@ }); $document.bind('keydown', 'u', function(e) { e.preventDefault(); - if (!self.active_story) return; - var story_id = self.active_story.id; - var story = self.model.get_story(story_id); - if (self.active_story && !self.active_story.get('read_status')) { - NEWSBLUR.assets.stories.mark_read(story, {skip_delay: true}); - } else if (self.active_story && self.active_story.get('read_status')) { - NEWSBLUR.assets.stories.mark_unread(story); - } + self.mark_active_story_read(); + }); + $document.bind('keydown', 'm', function(e) { + e.preventDefault(); + self.mark_active_story_read(); + }); + $document.bind('keydown', 'g', function(e) { + e.preventDefault(); + NEWSBLUR.app.feed_selector.toggle(); }); $document.bind('keydown', 'shift+s', function(e) { e.preventDefault(); diff --git a/media/js/newsblur/reader/reader_classifier.js b/media/js/newsblur/reader/reader_classifier.js index 92c8f9a22..db78a0759 100644 --- a/media/js/newsblur/reader/reader_classifier.js +++ b/media/js/newsblur/reader/reader_classifier.js @@ -57,7 +57,6 @@ NEWSBLUR.ReaderClassifierStory = function(story_id, feed_id, options) { this.options = $.extend({}, defaults, options); this.model = NEWSBLUR.assets; this.runner_story(); - console.log(["init story", feed_id, this.feed_id, options, this.options]); }; var classifier_prototype = { @@ -121,7 +120,7 @@ var classifier_prototype = { this.handle_cancel(); this.open_modal(); this.$modal.parent().bind('click.reader_classifer', $.rescope(this.handle_clicks, this)); - console.log(["runner story", this.options, this.feed_id]); + if (!this.options.feed_loaded) { _.defer(_.bind(function() { this.load_single_feed_trainer(); diff --git a/media/js/newsblur/static/about.js b/media/js/newsblur/static/about.js deleted file mode 100644 index 41d170460..000000000 --- a/media/js/newsblur/static/about.js +++ /dev/null @@ -1,197 +0,0 @@ -NEWSBLUR.About = function(options) { - var defaults = {}; - - this.options = $.extend({}, defaults, options); - this.runner(); -}; - -NEWSBLUR.About.prototype = { - - runner: function() { - this.make_modal(); - this.open_modal(); - - this.$modal.bind('click', $.rescope(this.handle_click, this)); - }, - - make_modal: function() { - var self = this; - - this.$modal = $.make('div', { className: 'NB-modal-about NB-modal-exception NB-modal' }, [ - $.make('a', { href: '#faq', className: 'NB-link-about-faq' }, 'FAQ'), - $.make('h2', { className: 'NB-modal-title' }, 'About NewsBlur'), - $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ - $.make('h5', [ - $.make('div', { className: 'NB-exception-option-meta' }), - $.make('span', { className: 'NB-exception-option-option' }, 'What:'), - 'A Feed Reader with Intelligence' - ]), - $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('ul', { className: 'NB-about-what' }, [ - $.make('li', 'Read the original site or the RSS feed.'), - $.make('li', 'Automatically highlight stories you want to read.'), - $.make('li', { className: 'last' }, 'Filter out stories you don\'t want to read.') - ]) - ]) - ]), - $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ - $.make('h5', [ - $.make('div', { className: 'NB-exception-option-meta' }), - $.make('span', { className: 'NB-exception-option-option' }, 'Who:'), - 'A Labor of Love' - ]), - $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('ul', { className: 'NB-about-who' }, [ - $.make('li', [ - 'Hand-crafted by: ', - $.make('a', { href: 'http://www.samuelclay.com' }, 'Samuel Clay') - ]), - $.make('li', [ - 'Find him on Twitter: ', - $.make('a', { href: 'http://twitter.com/samuelclay' }, '@samuelclay') - ]), - $.make('li', [ - 'E-mail: ', - $.make('a', { href: 'mailto:samuel@newsblur.com' }, 'samuel@newsblur.com') - ]), - $.make('li', { className: 'last' }, [ - 'Made in: ', - $.make('a', { href: 'http://www.newyorkfieldguide.com' }, 'New York City') - ]) - ]) - ]) - ]), - $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ - $.make('h5', [ - $.make('div', { className: 'NB-exception-option-meta' }), - $.make('span', { className: 'NB-exception-option-option' }, 'How:'), - 'Server-side technologies' - ]), - $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('ul', { className: 'NB-about-server' }, [ - $.make('li', [ - $.make('a', { href: 'http://www.djangoproject.com' }, 'Django'), - ': Web framework written in Python, used to serve all pages.' - ]), - $.make('li', [ - $.make('a', { href: 'http://ask.github.com/celery' }, 'Celery'), - ' & ', - $.make('a', { href: 'http://www.rabbitmq.com' }, 'RabbitMQ'), - ': Asynchronous queueing server, used to fetch and parse RSS feeds.' - ]), - $.make('li', [ - $.make('a', { href: 'http://www.mongodb.com' }, 'MongoDB'), - ', ', - $.make('a', { href: 'http://www.mongodb.com/pymongo' }, 'Pymongo'), - ', & ', - $.make('a', { href: 'http://www.github.com/hmarr/mongoengine' }, 'Mongoengine'), - ': Non-relational database, used to store stories, read stories, feed/page fetch histories, and proxied sites.' - ]), - $.make('li', { className: 'last' }, [ - $.make('a', { href: 'http://www.postgresql.com' }, 'PostgreSQL'), - ': Relational database, used to store feeds, subscriptions, and user accounts.' - ]) - ]) - ]) - ]), - $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ - $.make('h5', [ - $.make('div', { className: 'NB-exception-option-meta' }), - $.make('span', { className: 'NB-exception-option-option' }, 'How:'), - 'Client-side and design' - ]), - $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('ul', { className: 'NB-about-client' }, [ - $.make('li', [ - $.make('a', { href: 'http://www.jquery.com' }, 'jQuery'), - ': Cross-browser compliant JavaScript code. IE works without effort.' - ]), - $.make('li', [ - $.make('a', { href: 'http://documentcloud.github.com/underscore/' }, 'Underscore.js'), - ': Functional programming for JavaScript. Indispensible.' - ]), - $.make('li', [ - $.make('b', 'Miscellaneous jQuery Plugins:'), - ' Everything from resizable layouts, to progress bars, sortables, date handling, colors, corners, JSON, animations. See the complete list on ', - $.make('a', { href: 'http://github.com/samuelclay/NewsBlur/' }, 'NewsBlur\'s GitHub repository') - ]) - ]) - ]) - ]), - $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ - $.make('h5', [ - $.make('div', { className: 'NB-exception-option-meta' }), - $.make('span', { className: 'NB-exception-option-option' }, 'Why:'), - 'What\'s the point of another RSS feed reader?' - ]), - $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('ul', { className: 'NB-about-why' }, [ - $.make('li', [ - 'To learn how to build and scale the entire web stack: front-end JavaScript, HTML5 layout, back-end view processing, large dataset schema migrations, relational and non-relational database clusters across multiple servers, and getting multiple machines to talk to each other.' - ]), - $.make('li', [ - 'All of this just to prove that I could do it.' - ]), - $.make('li', [ - 'But most importantly, to meet future co-founders.' - ]) - ]) - ]) - ]) - ]); - }, - - open_modal: function() { - var self = this; - - this.$modal.modal({ - 'minWidth': 600, - 'maxWidth': 600, - 'overlayClose': true, - 'onOpen': function (dialog) { - dialog.overlay.fadeIn(200, function () { - dialog.container.fadeIn(200); - dialog.data.fadeIn(200); - }); - }, - 'onShow': function(dialog) { - $('#simplemodal-container').corner('6px'); - }, - 'onClose': function(dialog, callback) { - dialog.data.hide().empty().remove(); - dialog.container.hide().empty().remove(); - dialog.overlay.fadeOut(200, function() { - dialog.overlay.empty().remove(); - $.modal.close(callback); - }); - $('.NB-modal-holder').empty().remove(); - } - }); - }, - - handle_cancel: function() { - var $cancel = $('.NB-modal-cancel', this.$modal); - - $cancel.click(function(e) { - e.preventDefault(); - $.modal.close(); - }); - }, - - // =========== - // = Actions = - // =========== - - handle_click: function(elem, e) { - var self = this; - - $.targetIs(e, { tagSelector: '.NB-link-about-faq' }, function($t, $p) { - e.preventDefault(); - - $.modal.close(function() { - NEWSBLUR.faq = new NEWSBLUR.Faq(); - }); - }); - } - -}; \ No newline at end of file diff --git a/media/js/newsblur/static/faq.js b/media/js/newsblur/static/faq.js deleted file mode 100644 index d58719ad2..000000000 --- a/media/js/newsblur/static/faq.js +++ /dev/null @@ -1,184 +0,0 @@ -NEWSBLUR.Faq = function(options) { - var defaults = {}; - - this.options = $.extend({}, defaults, options); - this.runner(); -}; - -NEWSBLUR.Faq.prototype = { - - runner: function() { - $.modal.close(); - this.make_modal(); - this.open_modal(); - - this.$modal.bind('click', $.rescope(this.handle_click, this)); - }, - - make_modal: function() { - var self = this; - - this.$modal = $.make('div', { className: 'NB-modal-about NB-modal-exception NB-modal' }, [ - $.make('a', { href: '#faq', className: 'NB-link-about-faq' }, 'About NewsBlur'), - $.make('h2', { className: 'NB-modal-title' }, 'Frequently Asked Questions'), - $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ - $.make('h5', [ - 'The Reader' - ]), - $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('ul', { className: 'NB-about-what' }, [ - $.make('li', [ - $.make('div', { className: 'NB-faq-question' }, 'What is the difference between the three views: Original, Feed, and Story?'), - $.make('div', { className: 'NB-faq-answer' }, 'Original view is the original site. Feed view is the RSS feed from the site. And Story view is the original site for one story at a time. Original view is the blog site, whereas Story view is an individual blog post. It\'s all personal preference, really.'), - $.make('div', { className: 'NB-faq-answer' }, 'You can double-click a story to temporarily open it up in the Story view. The next story you open will transport you back to whichever view you were on before. Double-clicking a Feed will open up the feed in a new tab.') - ]), - $.make('li', [ - $.make('div', { className: 'NB-faq-question' }, 'Am I actually at the original site? Can NewsBlur see what I see?'), - $.make('div', { className: 'NB-faq-answer' }, 'In order to show you the original site, NewsBlur takes a snapshot of the page. You may have noticed that if you are logged into the original site, you are not logged into NewsBlur\'s snapshot of the page. This is because NewsBlur fetched the site for you.') - ]), - $.make('li', { className: 'last' }, [ - $.make('div', { className: 'NB-faq-question' }, 'Why doesn\'t NewsBlur follow me when I click on links on the page?'), - $.make('div', { className: 'NB-faq-answer' }, 'When you click on a link, you are technically leaving NewsBlur, although only for a portion of the page in an iframe. In order to track what you\'re reading, you need to read NewsBlur\'s snapshot of the page, or switch to the Feed view.'), - $.make('div', { className: 'NB-faq-answer last' }, 'This may change one day. There is a way to fix this behavior so it works like you would expect. It is not easy to do, however. One day.') - ]) - ]) - ]) - ]), - $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ - $.make('h5', [ - 'The Intelligence' - ]), - $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('ul', { className: 'NB-about-server' }, [ - $.make('li', [ - $.make('div', { className: 'NB-faq-question' }, [ - 'What does the three-color slider do?' - ]), - $.make('div', { className: 'NB-faq-answer' }, [ - $.make('img', { src: NEWSBLUR.Globals.MEDIA_URL + '/img/reader/intelligence_slider_positive.png', className: 'NB-faq-image', width: 114, height: 29 }), - 'This is called the intelligence slider. Slide it to the right to only show stories you like.' - ]), - $.make('div', { className: 'NB-faq-answer last' }, [ - $.make('img', { src: NEWSBLUR.Globals.MEDIA_URL + '/img/reader/intelligence_slider_negative.png', className: 'NB-faq-image', width: 114, height: 29 }), - 'Slide it to the left to show stories you dislike. Stories all start off neutral, in the center of the slider.' - ]), - $.make('div', { className: 'NB-faq-answer' }, [ - $.make('br'), - $.make('img', { className: 'NB-trainer-bullet', src: NEWSBLUR.Globals.MEDIA_URL + '/img/icons/silk/bullet_red.png'}), - ' are stories you don\'t like', - $.make('br'), - $.make('img', { className: 'NB-trainer-bullet', src: NEWSBLUR.Globals.MEDIA_URL + '/img/icons/silk/bullet_yellow.png'}), - ' are stories you have not yet rated', - $.make('br'), - $.make('img', { className: 'NB-trainer-bullet', src: NEWSBLUR.Globals.MEDIA_URL + '/img/icons/silk/bullet_green.png'}), - ' are stories you like' - ]) - ]), - $.make('li', { className: 'last' }, [ - $.make('div', { className: 'NB-faq-question' }, 'How does NewsBlur know whether I like or dislike a story?'), - $.make('div', { className: 'NB-faq-answer' }, 'When you like or dislike a story, you mark a facet of that story by checking a tag, author, part of the title, or entire publisher. When these facets are found in future stories, the stories are then weighted with your preferences. It is a very simple, explicit process where you tell NewsBlur what you like and don\'t like.'), - $.make('div', { className: 'NB-faq-answer' }, 'The idea is that by explicitly telling NewsBlur what your story preferences are, there is increased likelihood that you will like what the intelligence slider does for you.'), - $.make('div', { className: 'NB-faq-answer last'}, 'Currently, there is not an automated way of detecting stories you like or dislike without having to train NewsBlur. This implicit, automatic intelligence will come in the near-term future, but it will require an evolution to the interface that has not been easy to figure out how to make in a simple, clear, and effective manner. Soon.') - ]) - ]) - ]) - ]), - $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ - $.make('h5', [ - // $.make('span', { className: 'NB-exception-option-option', style: 'float:right' }, 'November - December 2010'), - 'What\'s Coming' - ]), - $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('ul', { className: 'NB-about-who' }, [ - $.make('li', [ - $.make('div', { className: 'NB-faq-answer' }, 'An iPhone app.') - ]), - $.make('li', { className: 'last' }, [ - $.make('div', { className: 'NB-faq-answer last' }, 'Sort sites alphabetically, by popularity, use, unread counts.') - ]) - ]) - ]) - ]), - $.make('div', { className: 'NB-fieldset NB-modal-submit' }, [ - $.make('h5', [ - 'Something\'s Wrong' - ]), - $.make('div', { className: 'NB-fieldset-fields' }, [ - $.make('ul', { className: 'NB-about-client' }, [ - $.make('li', [ - $.make('div', { className: 'NB-faq-question' }, 'Help! All of the stories are several days old and new stories are not showing up.'), - $.make('div', { className: 'NB-faq-answer' }, 'Sites that only have a single subscriber tend to get updated much less often than popular sites. Additionally, the frequency that a site publishes stories (once per month or several per day) has an impact on how often the site is refreshed.') - ]), - $.make('li', [ - $.make('div', { className: 'NB-faq-question' }, 'Help! A bunch of my sites are misbehaving and they work in Google Reader.'), - $.make('div', { className: 'NB-faq-answer' }, 'This is a known issue that is being addressed in a number of ways. About half of these misbehaving errors are errors that you really do need to address (like 404 Not Found errors). The other half are various edge cases, parser errors, uncaught exceptions, and bad code on NewsBlur\'s part.'), - $.make('div', { className: 'NB-faq-answer' }, 'But because this problem is so severe, various measures are taken every few weeks that fix a huge swath of misbheaving sites at once. You might find that this happens and it\'s quite nice when it does.') - ]), - $.make('li', { className: 'last' }, [ - $.make('div', { className: 'NB-faq-question' }, 'Help! I have an issue and it\'s not mentioned here.'), - $.make('div', { className: 'NB-faq-answer last' }, [ - 'Please, please, please e-mail ', - $.make('a', { href: 'mailto:samuel@newsblur.com' }, 'samuel@newsblur.com'), - '. If you have an issue it is entirely possible that other people do, too.' - ]) - ]) - ]) - ]) - ]) - ]); - }, - - open_modal: function() { - var self = this; - - this.$modal.modal({ - 'minWidth': 600, - 'maxWidth': 600, - 'overlayClose': true, - 'onOpen': function (dialog) { - dialog.overlay.fadeIn(200, function () { - dialog.container.fadeIn(200); - dialog.data.fadeIn(200); - }); - }, - 'onShow': function(dialog) { - $('#simplemodal-container').corner('6px'); - }, - 'onClose': function(dialog, callback) { - dialog.data.hide().empty().remove(); - dialog.container.hide().empty().remove(); - dialog.overlay.fadeOut(200, function() { - dialog.overlay.empty().remove(); - $.modal.close(callback); - }); - $('.NB-modal-holder').empty().remove(); - } - }); - }, - - handle_cancel: function() { - var $cancel = $('.NB-modal-cancel', this.$modal); - - $cancel.click(function(e) { - e.preventDefault(); - $.modal.close(); - }); - }, - - // =========== - // = Actions = - // =========== - - handle_click: function(elem, e) { - var self = this; - - $.targetIs(e, { tagSelector: '.NB-link-about-faq' }, function($t, $p) { - e.preventDefault(); - - $.modal.close(function() { - NEWSBLUR.about = new NEWSBLUR.About(); - }); - }); - } - -}; \ No newline at end of file diff --git a/media/js/newsblur/views/feed_list_view.js b/media/js/newsblur/views/feed_list_view.js index 28389fbe9..f9706ebc1 100644 --- a/media/js/newsblur/views/feed_list_view.js +++ b/media/js/newsblur/views/feed_list_view.js @@ -59,7 +59,9 @@ NEWSBLUR.Views.FeedList = Backbone.View.extend({ this.$el.html($feeds); this.$el.animate({'opacity': 1}, {'duration': 700}); // this.count_collapsed_unread_stories(); - this.$s.$feed_link_loader.fadeOut(250); + this.$s.$feed_link_loader.fadeOut(250, _.bind(function() { + this.$s.$feed_link_loader.css({'display': 'none'}); + }, this)); if (!this.options.feed_chooser && NEWSBLUR.Globals.is_authenticated && @@ -105,7 +107,7 @@ NEWSBLUR.Views.FeedList = Backbone.View.extend({ }, make_social_feeds: function() { - var $social_feeds = this.$s.$social_feeds; + var $social_feeds = $('.NB-socialfeeds', this.$s.$social_feeds); var profile = NEWSBLUR.assets.user_profile; var $feeds = NEWSBLUR.assets.social_feeds.map(function(feed) { var feed_view = new NEWSBLUR.Views.FeedTitleView({ @@ -122,7 +124,9 @@ NEWSBLUR.Views.FeedList = Backbone.View.extend({ 'opacity': 0 }); $social_feeds.html($feeds); - $social_feeds.animate({'opacity': 1}, {'duration': 700}); + + var collapsed = NEWSBLUR.app.sidebar.check_river_blurblog_collapsed({skip_animation: true}); + $social_feeds.animate({'opacity': 1}, {'duration': collapsed ? 0 : 700}); // if (this.socket) { // this.send_socket_active_feeds(); @@ -217,6 +221,22 @@ NEWSBLUR.Views.FeedList = Backbone.View.extend({ } }, + scroll_to_show_highlighted_feed: function() { + var $feed_lists = this.$s.$feed_lists; + var $feed = $('.NB-feed-selector-selected'); + + if (!$feed.length) return; + + var is_feed_visible = $feed_lists.isScrollVisible($feed); + + if (!is_feed_visible) { + var scroll = $feed.position().top; + var container = $feed_lists.scrollTop(); + var height = $feed_lists.outerHeight(); + $feed_lists.scrollTop(scroll+container-height/5); + } + }, + scroll_to_show_selected_folder: function(folder_view) { var $feed_lists = this.$s.$feed_lists; diff --git a/media/js/newsblur/views/feed_selector.js b/media/js/newsblur/views/feed_selector.js new file mode 100644 index 000000000..c1616357e --- /dev/null +++ b/media/js/newsblur/views/feed_selector.js @@ -0,0 +1,166 @@ +NEWSBLUR.Views.FeedSelector = Backbone.View.extend({ + + el: '.NB-feeds-selector', + + flags: {}, + + events: { + "keyup .NB-feeds-selector-input" : "keyup", + "keydown .NB-feeds-selector-input" : "keydown" + }, + + selected_index: 0, + + initialize: function() { + this.selected_feeds = new NEWSBLUR.Collections.Feeds(); + }, + + toggle: function() { + if (this.flags.showing_feed_selector) { + this.hide_feed_selector(); + } else { + this.show_feed_selector(); + } + }, + + show_feed_selector: function() { + var $input = this.$(".NB-feeds-selector-input"); + var $feed_list = NEWSBLUR.reader.$s.$feed_list; + var $social_feeds = NEWSBLUR.reader.$s.$social_feeds; + + this.$el.show(); + $input.val(''); + $input.focus(); + $feed_list.addClass('NB-selector-active'); + $social_feeds.addClass('NB-selector-active'); + + this.flags.showing_feed_selector = true; + NEWSBLUR.reader.layout.leftLayout.sizePane('north'); + }, + + hide_feed_selector: function() { + if (!this.flags.showing_feed_selector) return; + + var $input = this.$(".NB-feeds-selector-input"); + var $feed_list = NEWSBLUR.reader.$s.$feed_list; + var $social_feeds = NEWSBLUR.reader.$s.$social_feeds; + + $input.blur(); + this.$el.hide(); + this.$next_feed = null; + $feed_list.removeClass('NB-selector-active'); + $social_feeds.removeClass('NB-selector-active'); + $('.NB-feed-selector-selected').removeClass('NB-feed-selector-selected'); + + this.flags.showing_feed_selector = false; + NEWSBLUR.reader.layout.leftLayout.sizePane('north'); + }, + + filter_feed_selector: function(e) { + var $input = this.$(".NB-feeds-selector-input"); + var input = $input.val().toLowerCase(); + if (input == this.last_input) return; + this.last_input = input; + + this.selected_feeds.each(function(feed) { + _.each(feed.views, function(view) { + view.$el.removeClass('NB-feed-selector-active'); + }); + }); + + var feeds = NEWSBLUR.assets.feeds.filter(function(feed){ + return _.string.contains(feed.get('feed_title').toLowerCase(), input); + }); + var socialsubs = NEWSBLUR.assets.social_feeds.filter(function(feed){ + return _.string.contains(feed.get('feed_title').toLowerCase(), input) || + _.string.contains(feed.get('username').toLowerCase(), input); + }); + feeds = socialsubs.concat(feeds); + + // Clear out shown feeds on empty input + if (input.length == 0) { + this.selected_feeds.reset(); + } + + if (feeds.length) { + this.selected_feeds.reset(feeds); + } + + this.selected_feeds.each(function(feed) { + _.each(feed.views, function(view) { + view.$el.addClass('NB-feed-selector-active'); + }); + }); + + this.select(0); + }, + + // ============== + // = Navigation = + // ============== + + keyup: function(e) { + var arrow = {left: 37, up: 38, right: 39, down: 40, enter: 13}; + + if (e.which == arrow.up || e.which == arrow.down) { + // return this.navigate(e); + } else if (e.which == arrow.enter) { + // return this.open(e); + } + + return this.filter_feed_selector(e); + }, + + keydown: function(e) { + var arrow = {left: 37, up: 38, right: 39, down: 40, enter: 13, esc: 27}; + + if (e.which == arrow.esc) { + this.hide_feed_selector(); + } else if (e.which == arrow.up || e.which == arrow.down) { + return this.navigate(e); + } else if (e.which == arrow.enter) { + return this.open(e); + } + + // return this.filter_feed_selector(e); + }, + + navigate: function(e) { + var arrow = {left: 37, up: 38, right: 39, down: 40, esc: 27}; + + if (e.which == arrow.down) { + this.select(1); + } else if (e.which == arrow.up) { + this.select(-1); + } + + e.preventDefault(); + return false; + }, + + select: function(direction) { + var off, on; + + var $current_feed = $('.NB-feed-selector-selected.NB-feed-selector-active'); + this.$next_feed = NEWSBLUR.reader.get_next_feed(direction, $current_feed); + + $('.NB-feed-selector-selected').removeClass('NB-feed-selector-selected'); + this.$next_feed.addClass('NB-feed-selector-selected'); + NEWSBLUR.app.feed_list.scroll_to_show_highlighted_feed(); + }, + + open: function(e) { + var feed_id = this.$next_feed.data('id'); + if (_.string.include(feed_id, 'social:')) { + NEWSBLUR.reader.open_social_stories(this.$next_feed.data('id'), { + $feed_link: this.$next_feed + }); + } else { + NEWSBLUR.reader.open_feed(this.$next_feed.data('id'), this.$next_feed); + } + + e.preventDefault(); + return false; + } + +}); \ No newline at end of file diff --git a/media/js/newsblur/views/sidebar.js b/media/js/newsblur/views/sidebar.js new file mode 100644 index 000000000..edb8c9d36 --- /dev/null +++ b/media/js/newsblur/views/sidebar.js @@ -0,0 +1,146 @@ +NEWSBLUR.Views.Sidebar = Backbone.View.extend({ + + el: '.NB-sidebar', + + events: { + "click .NB-feeds-header-starred": "open_starred_stories", + "click .NB-feeds-header-river-sites": "open_river_stories", + "click .NB-feeds-header-river-blurblogs .NB-feedlist-collapse-icon": "collapse_river_blurblog", + "click .NB-feeds-header-river-blurblogs": "open_river_blurblogs_stories" + }, + + initialize: function() {}, + + // =========== + // = Actions = + // =========== + + check_river_blurblog_collapsed: function(options) { + options = options || {}; + var show_folder_counts = NEWSBLUR.assets.preference('folder_counts'); + var collapsed = _.contains(NEWSBLUR.Preferences.collapsed_folders, 'river_blurblog'); + + if (collapsed || show_folder_counts) { + this.show_collapsed_river_blurblog_count(options); + } + + return collapsed; + }, + + show_collapsed_river_blurblog_count: function(options) { + options = options || {}; + var $header = this.$('.NB-feeds-header-river-blurblogs'); + var $counts = $('.feed_counts_floater', $header); + var $river = $('.NB-feedlist-collapse-icon', $header); + var $folder = this.$('.NB-socialfeeds-folder'); + + $header.addClass('NB-folder-collapsed'); + $counts.remove(); + + if (!options.skip_animation) { + // $river.animate({'opacity': 0}, {'duration': options.skip_animation ? 0 : 100}); + $header.addClass('NB-feedlist-folder-title-recently-collapsed'); + $header.one('mouseover', function() { + $river.css({'opacity': ''}); + $header.removeClass('NB-feedlist-folder-title-recently-collapsed'); + }); + } else { + $folder.css({ + display: 'none', + opacity: 0 + }); + } + + var $counts = new NEWSBLUR.Views.FolderCount({ + collection: NEWSBLUR.assets.social_feeds + }).render().$el; + + if (this.options.feedbar) { + this.$('.NB-story-title-indicator-count').html($counts.clone()); + } else { + $header.prepend($counts.css({ + 'opacity': 0 + })); + } + $counts.animate({'opacity': 1}, {'duration': options.skip_animation ? 0 : 400}); + }, + + hide_collapsed_river_blurblog_count: function() { + var $header = this.$('.NB-feeds-header-river-blurblogs'); + var $counts = $('.feed_counts_floater', $header); + var $river = $('.NB-feedlist-collapse-icon', $header); + + $counts.animate({'opacity': 0}, { + 'duration': 300 + }); + + $river.animate({'opacity': .6}, {'duration': 400}); + $header.removeClass('NB-feedlist-folder-title-recently-collapsed'); + $header.one('mouseover', function() { + $river.css({'opacity': ''}); + $header.removeClass('NB-feedlist-folder-title-recently-collapsed'); + }); + }, + + // ========== + // = Events = + // ========== + + open_starred_stories: function() { + return NEWSBLUR.reader.open_starred_stories(); + }, + + open_river_stories: function() { + return NEWSBLUR.reader.open_river_stories(); + }, + + collapse_river_blurblog: function(e, options) { + e.stopPropagation(); + options = options || {}; + + var $header = this.$('.NB-feeds-header-river-blurblogs'); + var $folder = this.$('.NB-socialfeeds-folder'); + + // Hiding / Collapsing + if (options.force_collapse || + ($folder.length && + $folder.eq(0).is(':visible'))) { + NEWSBLUR.assets.collapsed_folders('river_blurblog', true); + $header.addClass('NB-folder-collapsed'); + $folder.animate({'opacity': 0}, { + 'queue': false, + 'duration': options.force_collapse ? 0 : 200, + 'complete': _.bind(function() { + this.show_collapsed_river_blurblog_count(); + $folder.slideUp({ + 'duration': 270, + 'easing': 'easeOutQuart' + }); + }, this) + }); + } + // Showing / Expanding + else if ($folder.length && + (!$folder.eq(0).is(':visible'))) { + NEWSBLUR.assets.collapsed_folders('river_blurblog', false); + $header.removeClass('NB-folder-collapsed'); + if (!NEWSBLUR.assets.preference('folder_counts')) { + this.hide_collapsed_river_blurblog_count(); + } + $folder.css({'opacity': 0}).slideDown({ + 'duration': 240, + 'easing': 'easeInOutCubic', + 'complete': function() { + $folder.animate({'opacity': 1}, {'queue': false, 'duration': 200}); + } + }); + } + + return false; + }, + + open_river_blurblogs_stories: function() { + return NEWSBLUR.reader.open_river_blurblogs_stories(); + } + +}); \ No newline at end of file diff --git a/media/js/newsblur/views/feed_list_header_view.js b/media/js/newsblur/views/sidebar_header_view.js similarity index 98% rename from media/js/newsblur/views/feed_list_header_view.js rename to media/js/newsblur/views/sidebar_header_view.js index 61614eadd..1f5a5e9f6 100644 --- a/media/js/newsblur/views/feed_list_header_view.js +++ b/media/js/newsblur/views/sidebar_header_view.js @@ -1,4 +1,4 @@ -NEWSBLUR.Views.FeedListHeader = Backbone.View.extend({ +NEWSBLUR.Views.SidebarHeader = Backbone.View.extend({ options: { el: '.NB-feeds-header-dashboard' diff --git a/media/js/newsblur/views/story_detail_view.js b/media/js/newsblur/views/story_detail_view.js index 0ff72a8cc..aa59fb4fb 100644 --- a/media/js/newsblur/views/story_detail_view.js +++ b/media/js/newsblur/views/story_detail_view.js @@ -407,11 +407,13 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({ var $button = this.$('.NB-feed-story-hide-changes'); if (NEWSBLUR.assets.preference('hide_story_changes')) { - this.$('ins').css({'text-decoration': 'underline'}); - this.$('del').css({'display': 'inline'}); + this.$el.addClass('NB-story-show-changes'); + // this.$('ins').css({'text-decoration': 'underline'}); + // this.$('del').css({'display': 'inline'}); } else { - this.$('ins').css({'text-decoration': 'none'}); - this.$('del').css({'display': 'none'}); + this.$el.addClass('NB-story-hide-changes'); + // this.$('ins').css({'text-decoration': 'none'}); + // this.$('del').css({'display': 'none'}); } $button.css('opacity', 1).fadeOut(400); $button.tipsy('hide').tipsy('disable'); diff --git a/media/js/newsblur/views/story_list_view.js b/media/js/newsblur/views/story_list_view.js index 3a202f525..0899357a0 100644 --- a/media/js/newsblur/views/story_list_view.js +++ b/media/js/newsblur/views/story_list_view.js @@ -111,7 +111,8 @@ NEWSBLUR.Views.StoryListView = Backbone.View.extend({ var $feed_floater = NEWSBLUR.reader.$s.$feed_floater; story = story || NEWSBLUR.reader.active_story; - if (story && this.cache.feed_title_floater_feed_id != story.get('story_feed_id')) { + if (story && story.get('story_feed_id') && + this.cache.feed_title_floater_feed_id != story.get('story_feed_id')) { var $story = story.story_view.$el; $header = $('.NB-feed-story-header-feed', $story); var $new_header = $header.clone(); @@ -125,9 +126,9 @@ NEWSBLUR.Views.StoryListView = Backbone.View.extend({ $feed_floater.html($new_header); this.cache.feed_title_floater_feed_id = story.get('story_feed_id'); var feed = NEWSBLUR.assets.get_feed(story.get('story_feed_id')); - $feed_floater.toggleClass('NB-inverse', feed.is_light()); + $feed_floater.toggleClass('NB-inverse', feed && feed.is_light()); $feed_floater.width($header.outerWidth()); - } else if (!story) { + } else if (!story || !story.get('story_feed_id')) { if (this.feed_title_floater) this.feed_title_floater.destroy(); this.cache.feed_title_floater_feed_id = null; } diff --git a/media/js/newsblur/welcome/welcome.js b/media/js/newsblur/welcome/welcome.js new file mode 100644 index 000000000..d8b50f902 --- /dev/null +++ b/media/js/newsblur/welcome/welcome.js @@ -0,0 +1,147 @@ +NEWSBLUR.Welcome = Backbone.View.extend({ + + el: '.NB-body-inner', + flags: {}, + rotation: 0, + + events: { + "click .NB-button-login" : "show_signin_form", + "click .NB-button-tryout" : "show_tryout", + "click .NB-welcome-header-caption" : "click_header_caption", + "mouseenter .NB-welcome-header-caption" : "enter_header_caption", + "mouseleave .NB-welcome-header-caption" : "leave_header_caption" + }, + + initialize: function() { + this.start_rotation(); + NEWSBLUR.reader.$s.$layout.hide(); + }, + + // ========== + // = Header = + // ========== + + click_header_caption: function(e) { + this.flags.on_signin = false; + this.enter_header_caption(e); + }, + + enter_header_caption: function(e) { + this.flags.on_header_caption = true; + var $caption = $(e.currentTarget); + + if (this.flags.on_signin) return; + + if ($caption.hasClass('NB-welcome-header-caption-signin')) { + this.flags.on_signin = true; + this.show_signin_form(); + } else { + var r = parseInt($caption.data('ss'), 10); + this.rotate_screenshots(r); + } + }, + + leave_header_caption: function(e) { + var $caption = $(e.currentTarget); + + if ($caption.hasClass('NB-welcome-header-caption-signin')) { + + } else { + this.flags.on_header_caption = false; + } + }, + + start_rotation: function() { + if (this.$('.NB-welcome-header-account').hasClass('NB-active')) { + this.show_signin_form(); + } + this.$('.NB-welcome-header-image img').eq(0).load(_.bind(function() { + setInterval(_.bind(this.rotate_screenshots, this), 3000); + }, this)); + }, + + rotate_screenshots: function(force, callback) { + if (this.flags.on_header_caption && _.isUndefined(force)) { + return; + } + + var NUM_CAPTIONS = 3; + var r = force ? force - 1 : (this.rotation + 1) % NUM_CAPTIONS; + if (!force) { + this.rotation += 1; + } + + var $images = $('.NB-welcome-header-image img').add('.NB-welcome-header-account'); + var $captions = $('.NB-welcome-header-caption'); + var $in_img = $images.eq(r); + var $out_img = $images.not($in_img); + var $in_caption = $captions.eq(r); + var $out_caption = $captions.not($in_caption); + + $out_img.css({zIndex: 0}).stop(true).animate({ + bottom: -300, + opacity: 0 + }, {easing: 'easeInOutQuart', queue: false, duration: force ? 650 : 1400, complete: callback}); + $in_img.css({zIndex: 1}).stop(true).animate({ + bottom: 0, + opacity: 1 + }, {easing: 'easeInOutQuart', queue: false, duration: force ? 650 : 1400}); + $out_caption.removeClass('NB-active'); + $in_caption.addClass('NB-active'); + if (r < 3) { + this.$('input').blur(); + } + }, + + show_signin_form: function() { + var open = !NEWSBLUR.reader.flags['sidebar_closed']; + this.hide_tryout(); + + this.flags.on_header_caption = true; + + _.delay(_.bind(function() { + this.rotate_screenshots(4, _.bind(function() { + this.$('input[name=login-username]').focus(); + }, this)); + }, this), open ? 560 : 0); + + }, + + show_tryout: function() { + if (!NEWSBLUR.reader) return; + + if (!this.flags.loaded) { + NEWSBLUR.reader.$s.$layout.layout().hide('west', true); + NEWSBLUR.reader.$s.$layout.show(); + this.flags.loaded = true; + } + var open = NEWSBLUR.reader.toggle_sidebar(); + + this.$('.NB-inner').animate({ + paddingLeft: open ? 240 : 0 + }, { + queue: false, + easing: 'easeInOutQuint', + duration: 560 + }); + + this.$('.NB-welcome-container')[open ? 'addClass' : 'removeClass']('NB-welcome-tryout'); + }, + + hide_tryout: function() { + if (!NEWSBLUR.reader) return; + + NEWSBLUR.reader.close_sidebar(); + + this.$('.NB-inner').animate({ + paddingLeft: 0 + }, { + queue: false, + easing: 'easeInOutQuint', + duration: 560 + }); + + this.$('.NB-welcome-container').removeClass('NB-welcome-tryout'); + } + +}); \ No newline at end of file diff --git a/media/js/vendor/jquery.effects.slideOffscreen.js b/media/js/vendor/jquery.effects.slideOffscreen.js new file mode 100644 index 000000000..22275171b --- /dev/null +++ b/media/js/vendor/jquery.effects.slideOffscreen.js @@ -0,0 +1,102 @@ +/** + * UI Layout Plugin: Slide-Offscreen Animation + * + * Prevent panes from being 'hidden' so that an iframes/objects + * does not reload/refresh when pane 'opens' again. + * This plug-in adds a new animation called "slideOffscreen". + * It is identical to the normal "slide" effect, but avoids hiding the element + * + * Requires Layout 1.3.0.RC30.1 or later for Close offscreen + * Requires Layout 1.3.0.RC30.5 or later for Hide, initClosed & initHidden offscreen + * + * Version: 1.0 - 2012-04-07 + * Author: Kevin Dalman (kevin.dalman@gmail.com) + * @preserve jquery.layout.slideOffscreen-1.0.js + */ +;(function ($) { +var _ = $.layout; + +// Add a new "slideOffscreen" effect +if ($.effects) { + + // add an option so initClosed and initHidden will work + _.defaults.panes.useOffscreenClose = false; // user must enable when needed + /* set the new animation as the default for all panes + _.defaults.panes.fxName = "slideOffscreen"; + */ + + if (_.plugins) + _.plugins.effects.slideOffscreen = true; + + // dupe 'slide' effect defaults as new effect defaults + _.effects.slideOffscreen = $.extend(true, {}, _.effects.slide); + + // add new effect to jQuery UI + $.effects.slideOffscreen = function(o) { + return this.queue(function(){ + + var fx = $.effects + , opt = o.options + , $el = $(this) + , pane = $el.data('layoutEdge') + , state = $el.data('parentLayout').state + , dist = state[pane].size + , s = this.style + , props = ['top','bottom','left','right'] + // Set options + , mode = fx.setMode($el, opt.mode || 'show') // Set Mode + , show = (mode == 'show') + , dir = opt.direction || 'left' // Default Direction + , ref = (dir == 'up' || dir == 'down') ? 'top' : 'left' + , pos = (dir == 'up' || dir == 'left') + , offscrn = _.config.offscreenCSS || {} + , keyLR = _.config.offscreenReset + , keyTB = 'offscreenResetTop' // only used internally + , animation = {} + ; + // Animation settings + animation[ref] = (show ? (pos ? '+=' : '-=') : (pos ? '-=' : '+=')) + dist; + + if (show) { // show() animation, so save top/bottom but retain left/right set when 'hidden' + $el.data(keyTB, { top: s.top, bottom: s.bottom }); + + // set the top or left offset in preparation for animation + // Note: ALL animations work by shifting the top or left edges + if (pos) { // top (north) or left (west) + $el.css(ref, isNaN(dist) ? "-" + dist : -dist); // Shift outside the left/top edge + } + else { // bottom (south) or right (east) - shift all the way across container + if (dir === 'right') + $el.css({ left: state.container.offsetWidth, right: 'auto' }); + else // dir === bottom + $el.css({ top: state.container.offsetHeight, bottom: 'auto' }); + } + // restore the left/right setting if is a top/bottom animation + if (ref === 'top') + $el.css( $el.data( keyLR ) || {} ); + } + else { // hide() animation, so save ALL CSS + $el.data(keyTB, { top: s.top, bottom: s.bottom }); + $el.data(keyLR, { left: s.left, right: s.right }); + } + + // Animate + $el.show().animate(animation, { queue: false, duration: o.duration, easing: opt.easing, complete: function(){ + // Restore top/bottom + if ($el.data( keyTB )) + $el.css($el.data( keyTB )).removeData( keyTB ); + if (show) // Restore left/right too + $el.css($el.data( keyLR ) || {}).removeData( keyLR ); + else // Move the pane off-screen (left: -99999, right: 'auto') + $el.css( offscrn ); + + if (o.callback) o.callback.apply(this, arguments); // Callback + $el.dequeue(); + }}); + + }); + }; + +} + +})( jQuery ); \ No newline at end of file diff --git a/media/js/vendor/mousetrap-1.1.3.js b/media/js/vendor/mousetrap-1.1.3.js new file mode 100644 index 000000000..04b583ea5 --- /dev/null +++ b/media/js/vendor/mousetrap-1.1.3.js @@ -0,0 +1,811 @@ +/** + * Copyright 2012 Craig Campbell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Mousetrap is a simple keyboard shortcut library for Javascript with + * no external dependencies + * + * @version 1.1.3 + * @url craig.is/killing/mice + */ +(function() { + + /** + * mapping of special keycodes to their corresponding keys + * + * everything in this dictionary cannot use keypress events + * so it has to be here to map to the correct keycodes for + * keyup/keydown events + * + * @type {Object} + */ + var _MAP = { + 8: 'backspace', + 9: 'tab', + 13: 'enter', + 16: 'shift', + 17: 'ctrl', + 18: 'alt', + 20: 'capslock', + 27: 'esc', + 32: 'space', + 33: 'pageup', + 34: 'pagedown', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down', + 45: 'ins', + 46: 'del', + 91: 'meta', + 93: 'meta', + 224: 'meta' + }, + + /** + * mapping for special characters so they can support + * + * this dictionary is only used incase you want to bind a + * keyup or keydown event to one of these keys + * + * @type {Object} + */ + _KEYCODE_MAP = { + 106: '*', + 107: '+', + 109: '-', + 110: '.', + 111 : '/', + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: '\'' + }, + + /** + * this is a mapping of keys that require shift on a US keypad + * back to the non shift equivelents + * + * this is so you can use keyup events with these keys + * + * note that this will only work reliably on US keyboards + * + * @type {Object} + */ + _SHIFT_MAP = { + '~': '`', + '!': '1', + '@': '2', + '#': '3', + '$': '4', + '%': '5', + '^': '6', + '&': '7', + '*': '8', + '(': '9', + ')': '0', + '_': '-', + '+': '=', + ':': ';', + '\"': '\'', + '<': ',', + '>': '.', + '?': '/', + '|': '\\' + }, + + /** + * this is a list of special strings you can use to map + * to modifier keys when you specify your keyboard shortcuts + * + * @type {Object} + */ + _SPECIAL_ALIASES = { + 'option': 'alt', + 'command': 'meta', + 'return': 'enter', + 'escape': 'esc' + }, + + /** + * variable to store the flipped version of _MAP from above + * needed to check if we should use keypress or not when no action + * is specified + * + * @type {Object|undefined} + */ + _REVERSE_MAP, + + /** + * a list of all the callbacks setup via Mousetrap.bind() + * + * @type {Object} + */ + _callbacks = {}, + + /** + * direct map of string combinations to callbacks used for trigger() + * + * @type {Object} + */ + _direct_map = {}, + + /** + * keeps track of what level each sequence is at since multiple + * sequences can start out with the same sequence + * + * @type {Object} + */ + _sequence_levels = {}, + + /** + * variable to store the setTimeout call + * + * @type {null|number} + */ + _reset_timer, + + /** + * temporary state where we will ignore the next keyup + * + * @type {boolean|string} + */ + _ignore_next_keyup = false, + + /** + * are we currently inside of a sequence? + * type of action ("keyup" or "keydown" or "keypress") or false + * + * @type {boolean|string} + */ + _inside_sequence = false; + + /** + * loop through the f keys, f1 to f19 and add them to the map + * programatically + */ + for (var i = 1; i < 20; ++i) { + _MAP[111 + i] = 'f' + i; + } + + /** + * loop through to map numbers on the numeric keypad + */ + for (i = 0; i <= 9; ++i) { + _MAP[i + 96] = i; + } + + /** + * cross browser add event method + * + * @param {Element|HTMLDocument} object + * @param {string} type + * @param {Function} callback + * @returns void + */ + function _addEvent(object, type, callback) { + if (object.addEventListener) { + object.addEventListener(type, callback, false); + return; + } + + object.attachEvent('on' + type, callback); + } + + /** + * takes the event and returns the key character + * + * @param {Event} e + * @return {string} + */ + function _characterFromEvent(e) { + + // for keypress events we should return the character as is + if (e.type == 'keypress') { + return String.fromCharCode(e.which); + } + + // for non keypress events the special maps are needed + if (_MAP[e.which]) { + return _MAP[e.which]; + } + + if (_KEYCODE_MAP[e.which]) { + return _KEYCODE_MAP[e.which]; + } + + // if it is not in the special map + return String.fromCharCode(e.which).toLowerCase(); + } + + /** + * checks if two arrays are equal + * + * @param {Array} modifiers1 + * @param {Array} modifiers2 + * @returns {boolean} + */ + function _modifiersMatch(modifiers1, modifiers2) { + return modifiers1.sort().join(',') === modifiers2.sort().join(','); + } + + /** + * resets all sequence counters except for the ones passed in + * + * @param {Object} do_not_reset + * @returns void + */ + function _resetSequences(do_not_reset) { + do_not_reset = do_not_reset || {}; + + var active_sequences = false, + key; + + for (key in _sequence_levels) { + if (do_not_reset[key]) { + active_sequences = true; + continue; + } + _sequence_levels[key] = 0; + } + + if (!active_sequences) { + _inside_sequence = false; + } + } + + /** + * finds all callbacks that match based on the keycode, modifiers, + * and action + * + * @param {string} character + * @param {Array} modifiers + * @param {Event|Object} e + * @param {boolean=} remove - should we remove any matches + * @param {string=} combination + * @returns {Array} + */ + function _getMatches(character, modifiers, e, remove, combination) { + var i, + callback, + matches = [], + action = e.type; + + // if there are no events related to this keycode + if (!_callbacks[character]) { + return []; + } + + // if a modifier key is coming up on its own we should allow it + if (action == 'keyup' && _isModifier(character)) { + modifiers = [character]; + } + + // loop through all callbacks for the key that was pressed + // and see if any of them match + for (i = 0; i < _callbacks[character].length; ++i) { + callback = _callbacks[character][i]; + + // if this is a sequence but it is not at the right level + // then move onto the next match + if (callback.seq && _sequence_levels[callback.seq] != callback.level) { + continue; + } + + // if the action we are looking for doesn't match the action we got + // then we should keep going + if (action != callback.action) { + continue; + } + + // if this is a keypress event and the meta key and control key + // are not pressed that means that we need to only look at the + // character, otherwise check the modifiers as well + // + // chrome will not fire a keypress if meta or control is down + // safari will fire a keypress if meta or meta+shift is down + // firefox will fire a keypress if meta or control is down + if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) { + + // remove is used so if you change your mind and call bind a + // second time with a new function the first one is overwritten + if (remove && callback.combo == combination) { + _callbacks[character].splice(i, 1); + } + + matches.push(callback); + } + } + + return matches; + } + + /** + * takes a key event and figures out what the modifiers are + * + * @param {Event} e + * @returns {Array} + */ + function _eventModifiers(e) { + var modifiers = []; + + if (e.shiftKey) { + modifiers.push('shift'); + } + + if (e.altKey) { + modifiers.push('alt'); + } + + if (e.ctrlKey) { + modifiers.push('ctrl'); + } + + if (e.metaKey) { + modifiers.push('meta'); + } + + return modifiers; + } + + /** + * actually calls the callback function + * + * if your callback function returns false this will use the jquery + * convention - prevent default and stop propogation on the event + * + * @param {Function} callback + * @param {Event} e + * @returns void + */ + function _fireCallback(callback, e) { + if (callback(e) === false) { + if (e.preventDefault) { + e.preventDefault(); + } + + if (e.stopPropagation) { + e.stopPropagation(); + } + + e.returnValue = false; + e.cancelBubble = true; + } + } + + /** + * handles a character key event + * + * @param {string} character + * @param {Event} e + * @returns void + */ + function _handleCharacter(character, e) { + + // if this event should not happen stop here + if (Mousetrap.stopCallback(e, e.target || e.srcElement)) { + return; + } + + var callbacks = _getMatches(character, _eventModifiers(e), e), + i, + do_not_reset = {}, + processed_sequence_callback = false; + + // loop through matching callbacks for this key event + for (i = 0; i < callbacks.length; ++i) { + + // fire for all sequence callbacks + // this is because if for example you have multiple sequences + // bound such as "g i" and "g t" they both need to fire the + // callback for matching g cause otherwise you can only ever + // match the first one + if (callbacks[i].seq) { + processed_sequence_callback = true; + + // keep a list of which sequences were matches for later + do_not_reset[callbacks[i].seq] = 1; + _fireCallback(callbacks[i].callback, e); + continue; + } + + // if there were no sequence matches but we are still here + // that means this is a regular match so we should fire that + if (!processed_sequence_callback && !_inside_sequence) { + _fireCallback(callbacks[i].callback, e); + } + } + + // if you are inside of a sequence and the key you are pressing + // is not a modifier key then we should reset all sequences + // that were not matched by this key event + if (e.type == _inside_sequence && !_isModifier(character)) { + _resetSequences(do_not_reset); + } + } + + /** + * handles a keydown event + * + * @param {Event} e + * @returns void + */ + function _handleKey(e) { + + // normalize e.which for key events + // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion + e.which = typeof e.which == "number" ? e.which : e.keyCode; + + var character = _characterFromEvent(e); + + // no character found then stop + if (!character) { + return; + } + + if (e.type == 'keyup' && _ignore_next_keyup == character) { + _ignore_next_keyup = false; + return; + } + + _handleCharacter(character, e); + } + + /** + * determines if the keycode specified is a modifier key or not + * + * @param {string} key + * @returns {boolean} + */ + function _isModifier(key) { + return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta'; + } + + /** + * called to set a 1 second timeout on the specified sequence + * + * this is so after each key press in the sequence you have 1 second + * to press the next key before you have to start over + * + * @returns void + */ + function _resetSequenceTimer() { + clearTimeout(_reset_timer); + _reset_timer = setTimeout(_resetSequences, 1000); + } + + /** + * reverses the map lookup so that we can look for specific keys + * to see what can and can't use keypress + * + * @return {Object} + */ + function _getReverseMap() { + if (!_REVERSE_MAP) { + _REVERSE_MAP = {}; + for (var key in _MAP) { + + // pull out the numeric keypad from here cause keypress should + // be able to detect the keys from the character + if (key > 95 && key < 112) { + continue; + } + + if (_MAP.hasOwnProperty(key)) { + _REVERSE_MAP[_MAP[key]] = key; + } + } + } + return _REVERSE_MAP; + } + + /** + * picks the best action based on the key combination + * + * @param {string} key - character for key + * @param {Array} modifiers + * @param {string=} action passed in + */ + function _pickBestAction(key, modifiers, action) { + + // if no action was picked in we should try to pick the one + // that we think would work best for this key + if (!action) { + action = _getReverseMap()[key] ? 'keydown' : 'keypress'; + } + + // modifier keys don't work as expected with keypress, + // switch to keydown + if (action == 'keypress' && modifiers.length) { + action = 'keydown'; + } + + return action; + } + + /** + * binds a key sequence to an event + * + * @param {string} combo - combo specified in bind call + * @param {Array} keys + * @param {Function} callback + * @param {string=} action + * @returns void + */ + function _bindSequence(combo, keys, callback, action) { + + // start off by adding a sequence level record for this combination + // and setting the level to 0 + _sequence_levels[combo] = 0; + + // if there is no action pick the best one for the first key + // in the sequence + if (!action) { + action = _pickBestAction(keys[0], []); + } + + /** + * callback to increase the sequence level for this sequence and reset + * all other sequences that were active + * + * @param {Event} e + * @returns void + */ + var _increaseSequence = function(e) { + _inside_sequence = action; + ++_sequence_levels[combo]; + _resetSequenceTimer(); + }, + + /** + * wraps the specified callback inside of another function in order + * to reset all sequence counters as soon as this sequence is done + * + * @param {Event} e + * @returns void + */ + _callbackAndReset = function(e) { + _fireCallback(callback, e); + + // we should ignore the next key up if the action is key down + // or keypress. this is so if you finish a sequence and + // release the key the final key will not trigger a keyup + if (action !== 'keyup') { + _ignore_next_keyup = _characterFromEvent(e); + } + + // weird race condition if a sequence ends with the key + // another sequence begins with + setTimeout(_resetSequences, 10); + }, + i; + + // loop through keys one at a time and bind the appropriate callback + // function. for any key leading up to the final one it should + // increase the sequence. after the final, it should reset all sequences + for (i = 0; i < keys.length; ++i) { + _bindSingle(keys[i], i < keys.length - 1 ? _increaseSequence : _callbackAndReset, action, combo, i); + } + } + + /** + * binds a single keyboard combination + * + * @param {string} combination + * @param {Function} callback + * @param {string=} action + * @param {string=} sequence_name - name of sequence if part of sequence + * @param {number=} level - what part of the sequence the command is + * @returns void + */ + function _bindSingle(combination, callback, action, sequence_name, level) { + + // make sure multiple spaces in a row become a single space + combination = combination.replace(/\s+/g, ' '); + + var sequence = combination.split(' '), + i, + key, + keys, + modifiers = []; + + // if this pattern is a sequence of keys then run through this method + // to reprocess each pattern one key at a time + if (sequence.length > 1) { + _bindSequence(combination, sequence, callback, action); + return; + } + + // take the keys from this pattern and figure out what the actual + // pattern is all about + keys = combination === '+' ? ['+'] : combination.split('+'); + + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + + // normalize key names + if (_SPECIAL_ALIASES[key]) { + key = _SPECIAL_ALIASES[key]; + } + + // if this is not a keypress event then we should + // be smart about using shift keys + // this will only work for US keyboards however + if (action && action != 'keypress' && _SHIFT_MAP[key]) { + key = _SHIFT_MAP[key]; + modifiers.push('shift'); + } + + // if this key is a modifier then add it to the list of modifiers + if (_isModifier(key)) { + modifiers.push(key); + } + } + + // depending on what the key combination is + // we will try to pick the best event for it + action = _pickBestAction(key, modifiers, action); + + // make sure to initialize array if this is the first time + // a callback is added for this key + if (!_callbacks[key]) { + _callbacks[key] = []; + } + + // remove an existing match if there is one + _getMatches(key, modifiers, {type: action}, !sequence_name, combination); + + // add this call back to the array + // if it is a sequence put it at the beginning + // if not put it at the end + // + // this is important because the way these are processed expects + // the sequence ones to come first + _callbacks[key][sequence_name ? 'unshift' : 'push']({ + callback: callback, + modifiers: modifiers, + action: action, + seq: sequence_name, + level: level, + combo: combination + }); + } + + /** + * binds multiple combinations to the same callback + * + * @param {Array} combinations + * @param {Function} callback + * @param {string|undefined} action + * @returns void + */ + function _bindMultiple(combinations, callback, action) { + for (var i = 0; i < combinations.length; ++i) { + _bindSingle(combinations[i], callback, action); + } + } + + // start! + _addEvent(document, 'keypress', _handleKey); + _addEvent(document, 'keydown', _handleKey); + _addEvent(document, 'keyup', _handleKey); + + var Mousetrap = { + + /** + * binds an event to mousetrap + * + * can be a single key, a combination of keys separated with +, + * an array of keys, or a sequence of keys separated by spaces + * + * be sure to list the modifier keys first to make sure that the + * correct key ends up getting bound (the last key in the pattern) + * + * @param {string|Array} keys + * @param {Function} callback + * @param {string=} action - 'keypress', 'keydown', or 'keyup' + * @returns void + */ + bind: function(keys, callback, action) { + _bindMultiple(keys instanceof Array ? keys : [keys], callback, action); + _direct_map[keys + ':' + action] = callback; + return this; + }, + + /** + * unbinds an event to mousetrap + * + * the unbinding sets the callback function of the specified key combo + * to an empty function and deletes the corresponding key in the + * _direct_map dict. + * + * the keycombo+action has to be exactly the same as + * it was defined in the bind method + * + * TODO: actually remove this from the _callbacks dictionary instead + * of binding an empty function + * + * @param {string|Array} keys + * @param {string} action + * @returns void + */ + unbind: function(keys, action) { + if (_direct_map[keys + ':' + action]) { + delete _direct_map[keys + ':' + action]; + this.bind(keys, function() {}, action); + } + return this; + }, + + /** + * triggers an event that has already been bound + * + * @param {string} keys + * @param {string=} action + * @returns void + */ + trigger: function(keys, action) { + _direct_map[keys + ':' + action](); + return this; + }, + + /** + * resets the library back to its initial state. this is useful + * if you want to clear out the current keyboard shortcuts and bind + * new ones - for example if you switch to another page + * + * @returns void + */ + reset: function() { + _callbacks = {}; + _direct_map = {}; + return this; + }, + + /** + * should we stop this event before firing off callbacks + * + * @param {Event} e + * @param {Element} element + * @return {boolean} + */ + stopCallback: function(e, element) { + + // if the element has the class "mousetrap" then no need to stop + if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { + return false; + } + + // stop for input, select, and textarea + return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true'); + } + }; + + // expose mousetrap to the global object + window.Mousetrap = Mousetrap; + + // expose mousetrap as an AMD module + if (typeof define == 'function' && define.amd) { + define('mousetrap', function() { return Mousetrap; }); + } +}) (); \ No newline at end of file diff --git a/media/maintenance.html.unused b/media/maintenance.html.unused deleted file mode 100644 index a73f00e0b..000000000 --- a/media/maintenance.html.unused +++ /dev/null @@ -1,17 +0,0 @@ - - -NewsBlur is upgrading... - - - - - - -

NewsBlur is upgrading...

- -

Moving PostgreSQL to a new server. It's Thursday night at 11:40pm. No more than 10 minutes. As little as 5 minutes.

- - - - - diff --git a/settings.py b/settings.py index 296235ac1..f3f139ce1 100644 --- a/settings.py +++ b/settings.py @@ -3,6 +3,7 @@ import logging import os import datetime from mongoengine import connect +from boto.s3.connection import S3Connection import redis from utils import jammit @@ -351,6 +352,11 @@ CELERYBEAT_SCHEDULE = { 'schedule': datetime.timedelta(hours=1), 'options': {'queue': 'beat_tasks'}, }, + 'clean-analytics': { + 'task': 'clean-analytics', + 'schedule': datetime.timedelta(hours=12), + 'options': {'queue': 'beat_tasks'}, + }, } # ========= @@ -409,6 +415,21 @@ FACEBOOK_SECRET = '99999999999999999999999999999999' TWITTER_CONSUMER_KEY = 'ooooooooooooooooooooo' TWITTER_CONSUMER_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' +# =============== +# = AWS Backing = +# =============== + +BACKED_BY_AWS = { + 'pages_on_s3': False, + 'icons_on_s3': False, + 'stories_on_dynamodb': False, +} + +PROXY_S3_PAGES = True +S3_BACKUP_BUCKET = 'newsblur_backups' +S3_PAGES_BUCKET_NAME = 'pages.newsblur.com' +S3_ICONS_BUCKET_NAME = 'icons.newsblur.com' + # ================== # = Configurations = # ================== @@ -470,3 +491,12 @@ if DEBUG: MIDDLEWARE_CLASSES += ('utils.request_introspection_middleware.DumpRequestMiddleware',) MIDDLEWARE_CLASSES += ('utils.exception_middleware.ConsoleExceptionMiddleware',) +# ======= +# = AWS = +# ======= + +S3_CONN = None +if BACKED_BY_AWS.get('pages_on_s3') or BACKED_BY_AWS.get('icons_on_s3'): + S3_CONN = S3Connection(S3_ACCESS_KEY, S3_SECRET) + S3_PAGES_BUCKET = S3_CONN.get_bucket(S3_PAGES_BUCKET_NAME) + S3_ICONS_BUCKET = S3_CONN.get_bucket(S3_ICONS_BUCKET_NAME) diff --git a/templates/base.html b/templates/base.html index 53198e769..e18e93b82 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,9 +3,11 @@ {% block title %}NewsBlur{% endblock %} - - + + + + - - {% if user.is_staff %} - - {% endif %} {% endblock %} -{% block bodyclass %}NB-body-main{% endblock %} +{% block bodyclass %}NB-welcome{% endblock %} + +{% block header %} + {% render_feeds_skeleton %} +{% endblock header %} + {% block content %} - -

NewsBlur

-

- The best stories from your friends and favorite blogs, all in one place.

- -
- -
+
-