diff --git a/apps/api/views.py b/apps/api/views.py index 6678ddcc4..339317f3a 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -8,7 +8,8 @@ from django.contrib.auth import login as login_user from django.contrib.auth import logout as logout_user from apps.reader.forms import SignupForm, LoginForm from apps.profile.models import Profile -from apps.social.models import MSocialProfile +from apps.social.models import MSocialProfile, MSharedStory +from apps.rss_feeds.models import Feed from apps.reader.models import UserSubscription, UserSubscriptionFolders from utils import json_functions as json from utils import log as logging @@ -129,7 +130,8 @@ def add_site(request, token): if code > 0: message = 'OK' - logging.user(profile.user, "~FRAdding URL from site: ~SB%s (in %s)" % (url, folder)) + logging.user(profile.user, "~FRAdding URL from site: ~SB%s (in %s)" % (url, folder), + request=request) return HttpResponse(callback + '(' + json.encode({ 'code': code, @@ -140,9 +142,13 @@ def add_site(request, token): def check_share_on_site(request, token): code = 0 story_url = request.GET['story_url'] - rss_url = request.GET['rss_url'] + rss_url = request.GET.get('rss_url') callback = request.GET['callback'] + other_stories = None + same_stories = None + usersub = None message = None + user = None if not story_url: @@ -150,22 +156,38 @@ def check_share_on_site(request, token): else: try: profile = Profile.objects.get(secret_token=token) + user = profile.user except Profile.DoesNotExist: code = -1 - - logging.user(profile.user, "~BM~FCChecking share from site: ~SB%s" % (story_url)) - return HttpResponse(callback + '(' + json.encode({ - 'code': code, - 'message': message, - 'feed': None, + feed = Feed.get_feed_from_url(rss_url, create=False, fetch=False) + if not feed: + feed = Feed.get_feed_from_url(story_url, create=False, fetch=True) + + if feed and user: + usersub = UserSubscription.objects.filter(user=user, feed=feed) + same_stories, other_stories = MSharedStory.get_shared_stories(feed.pk, story_url) + + logging.user(profile.user, "~BM~FCChecking share from site: ~SB%s" % (story_url), + request=request) + + response = HttpResponse(callback + '(' + json.encode({ + 'code' : code, + 'message' : message, + 'feed' : feed, + 'subscribed' : usersub and usersub.count() > 0, + 'same_stories' : same_stories, + 'other_stories' : other_stories, }) + ')', mimetype='text/plain') + response['Access-Control-Allow-Origin'] = '*' + response['Access-Control-Allow-Methods'] = 'GET' + + return response def share_story(request, token): code = 0 - story_url = request.GET['story_url'] - comments = request.GET['comments'] - callback = request.GET['callback'] + story_url = request.POST['story_url'] + comments = request.POST['comments'] message = None if not story_url: @@ -178,8 +200,12 @@ def share_story(request, token): logging.user(profile.user, "~BM~FYSharing story from site: ~SB%s: %s" % (story_url, comments)) - return HttpResponse(callback + '(' + json.encode({ + response = HttpResponse(json.encode({ 'code': code, 'message': message, 'story': None, - }) + ')', mimetype='text/plain') \ No newline at end of file + }), mimetype='text/plain') + response['Access-Control-Allow-Origin'] = '*' + response['Access-Control-Allow-Methods'] = 'POST' + + return response \ No newline at end of file diff --git a/apps/social/models.py b/apps/social/models.py index a604d0cbd..d84f9bf7d 100644 --- a/apps/social/models.py +++ b/apps/social/models.py @@ -1099,6 +1099,16 @@ class MSharedStory(mongo.Document): super(MSharedStory, self).delete(*args, **kwargs) + @classmethod + def get_shared_stories(cls, feed_id, story_url=None, limit=3): + same_stories = None + if story_url: + same_stories = cls.objects.filter(story_feed_id=feed_id, story_permalink=story_url).order_by('-shared_date') + + other_stories = cls.objects.filter(story_feed_id=feed_id, story_permalink__ne=story_url).order_by('-shared_date') + + return same_stories, other_stories + def ensure_story_db_id(self, save=True): if not self.story_db_id: story, _ = MStory.find_story(self.story_feed_id, self.story_guid) diff --git a/assets.yml b/assets.yml index d5c65185d..9080f65e6 100644 --- a/assets.yml +++ b/assets.yml @@ -91,6 +91,8 @@ javascripts: - media/js/vendor/jquery.simplemodal-1.3.js - media/js/vendor/jquery.corners.js - media/js/vendor/jquery.hotkeys.js + - media/js/vendor/jquery.easing.js + - media/js/vendor/underscore-*.js - media/js/vendor/readability-*.js blurblog: - media/js/vendor/jquery-*.js diff --git a/media/css/bookmarklet.css b/media/css/bookmarklet.css index 9adb5b2c7..3882331ef 100644 --- a/media/css/bookmarklet.css +++ b/media/css/bookmarklet.css @@ -90,7 +90,7 @@ overflow: hidden; } .NB-bookmarklet .NB-bookmarklet-main { - width: 70%; + width: 530px; overflow: hidden; float: right; border-left: 6px solid #74B3B7; @@ -107,6 +107,21 @@ /* = Bookmarklet Add Site = */ /* ======================== */ +.NB-bookmarklet .NB-subscribe-feed { + font-size: 12px; + font-weight: bold; + overflow: hidden; +} +.NB-bookmarklet .NB-subscribe-feed img { + width: 16px; + height: 16px; + float: left; + vertical-align: bottom; + margin: 11px 6px 0 0; +} +.NB-bookmarklet .NB-subscribe-feed-title { + margin: 12px 0 0; +} .NB-bookmarklet .NB-modal-information { float: right; @@ -116,8 +131,7 @@ font-size: 10px; } .NB-bookmarklet .NB-bookmarklet-folder-container { - margin: 28px 0; - float: right; + margin: 12px 0 0; } .NB-bookmarklet .NB-bookmarklet-folder-container .NB-bookmarklet-folder-label { float: left; @@ -133,8 +147,9 @@ } .NB-bookmarklet .NB-bookmarklet-new-folder-container { display: none; - float: right; clear: both; + overflow: hidden; + padding-left: 24px; } .NB-bookmarklet .NB-bookmarklet-folder-new { float: left; @@ -143,17 +158,17 @@ font-size: 11px; line-height: 13px; margin: 2px 0 0; - width: 170px; + width: 156px; } .NB-bookmarklet .NB-bookmarklet-folder-new-label { margin: 4px 4px 0 0; float: left; } .NB-bookmarklet .NB-folders { - width: 150px; + width: 186px; } .NB-bookmarklet .NB-modal-submit { - margin: 24px 0 8px; + margin: 12px 0 8px; padding: 0 0 6px; clear: both; overflow: hidden; @@ -162,16 +177,21 @@ clear: both; padding: 0 16px; line-height: 24px; - width: 160px; + width: 176px; text-align: center; text-shadow: none; + font-weight: normal; +} +.NB-bookmarklet .NB-modal-submit-button.NB-disabled:hover, +.NB-bookmarklet .NB-modal-submit-button.NB-disabled:active { + cursor: default; } .NB-bookmarklet .NB-modal-submit-button:hover, .NB-bookmarklet .NB-modal-submit-button:active { font-weight: normal; } .NB-bookmarklet .NB-modal-submit-button img { - margin: 5px 6px 0 0; + margin: 4px 6px 0 0; width: 16px; height: 16px; float: left; @@ -189,12 +209,6 @@ vertical-align: top; margin: -2px 6px 0 0; } -.NB-bookmarklet .NB-bookmarklet-folder-label { - float: right; -} -.NB-bookmarklet .NB-folders { - float: right; -} .NB-bookmarklet .NB-error-invalid { padding: 12px 12px; font-size: 13px; @@ -215,16 +229,23 @@ .NB-bookmarklet .NB-bookmarklet-page-title { font-size: 20px; font-weight: bold; - margin: 0 0 0 24px; + margin: 0 18px 0 14px; + padding: 12px 12px 12px 10px; + border-radius: 4px; + background-color: #F6F6F6; line-height: 24px; } -.NB-bookmarklet .NB-bookmarklet-page-content { +.NB-bookmarklet .NB-bookmarklet-page-content-wrapper { max-height: 340px; overflow-y: scroll; padding: 0 24px; margin: 12px 0 0; background-color: white; } +.NB-bookmarklet .NB-bookmarklet-page-content { + border-top: 1px solid #F0F0F0; + border-bottom: 1px solid #F0F0F0; +} .NB-bookmarklet .NB-bookmarklet-page-comment { margin-top: 12px; /* border-top: 1px solid #F0F0F0;*/ @@ -261,9 +282,71 @@ .NB-bookmarklet .NB-bookmarklet-comment-submit { float: left; clear: none; - width: 100px; + width: 98px; + height: 32px; margin: 0 0 4px 12px; padding: 8px 0; line-height: 16px; +} +.NB-bookmarklet .NB-bookmarklet-comment-submit.NB-disabled { + padding: 14px 0 0; +} +.NB-bookmarklet .NB-bookmarklet-comment-submit .NB-bookmarklet-accept { + padding: 0 12px; +} +.NB-bookmarklet .NB-bookmarklet-comment-submit .NB-bookmarklet-accept img { + vertical-align: top; + margin: 0; +} +/* =============== */ +/* = Side Layout = */ +/* =============== */ + +.NB-bookmarklet .NB-bookmarklet-side { + position: relative; + width: 200px; + height: 400px; + margin: 24px 0 0; + overflow: hidden; +} +.NB-bookmarklet .NB-bookmarklet-side-half { + position: absolute; + top: 0; + left: 0; + width: 100%; +} +.NB-bookmarklet .NB-bookmarklet-side-subscribe { + left: 100%; +} +.NB-bookmarklet .NB-bookmarklet-side-loading { + text-align: center; + font-size: 15px; + color: #C0C0C0; + text-transform: uppercase; font-weight: bold; -} \ No newline at end of file + line-height: 18px; + padding: 48px 0 0; +} +.NB-bookmarklet .NB-subscribe-loader { + margin: 0 auto; + width: 16px; + height: 16px; + -webkit-animation: -webkit-slow-spin 4s infinite linear; + -moz-animation-duration: 4s; + -moz-animation-name: -moz-slow-spin; + -moz-animation-iteration-count: infinite; + -moz-animation-timing-function: linear; +} +@-webkit-keyframes -webkit-slow-spin { + from {-webkit-transform: rotate(0deg)} + to {-webkit-transform: rotate(360deg)} +} +@-moz-keyframes -moz-slow-spin { + from {-moz-transform: rotate(0deg)} + to {-moz-transform: rotate(360deg)} +} + +.NB-bookmarklet .NB-subscribe-load-text { + padding: 24px 24px; +} + diff --git a/templates/api/share_bookmarklet.js b/templates/api/share_bookmarklet.js index c7c2c30e5..4ccef4329 100644 --- a/templates/api/share_bookmarklet.js +++ b/templates/api/share_bookmarklet.js @@ -17,6 +17,9 @@ this.flags = { 'new_folder': false }; + this.images = { + 'accept_image': "{{ accept_image }}" + }; this.options = $.extend({}, defaults, options); this.runner(); @@ -57,7 +60,9 @@ this.make_modal(); this.open_modal(); this.get_page_content(); - this.pre_share_check_story(); + _.delay(_.bind(function() { + this.pre_share_check_story(); + }, this), 0); this.$modal.bind('click', $.rescope(this.handle_clicks, this)); @@ -80,8 +85,10 @@ $.make('div', { className: 'NB-bookmarklet-main'}, [ $.make('div', { className: 'NB-bookmarklet-page' }, [ $.make('div', { className: 'NB-bookmarklet-page-title' }), - $.make('div', { className: 'NB-bookmarklet-page-content' }), - $.make('div', { className: 'NB-bookmarklet-page-comment' }, [ + $.make('div', { className: 'NB-bookmarklet-page-content-wrapper' }, [ + $.make('div', { className: 'NB-bookmarklet-page-content' }) + ]), + $.make('div', { className: 'NB-bookmarklet-page-comment NB-modal-submit' }, [ $.make('div', { className: 'NB-bookmarklet-comment-photo' }, [ $.make('img', { src: this.profile.photo_url }) ]), @@ -93,16 +100,23 @@ ]) ]), $.make('div', { className: 'NB-bookmarklet-side' }, [ - $.make('div', { className: 'NB-modal-submit' }, [ - $.make('div', { className: 'NB-modal-submit-button NB-modal-submit-green' }, 'Subscribe to this site') - ]), - $.make('div', { className: 'NB-bookmarklet-folder-container' }, [ - $.make('img', { className: 'NB-bookmarklet-folder-add-button', src: 'data:image/png;charset=utf-8;base64,{{ add_image }}', title: 'Add New Folder' }), - this.make_folders(), - $.make('div', { className: 'NB-bookmarklet-new-folder-container' }, [ - $.make('img', { className: 'NB-bookmarklet-folder-new-label', src: 'data:image/png;charset=utf-8;base64,{{ new_folder_image }}' }), - $.make('input', { type: 'text', name: 'new_folder_name', className: 'NB-bookmarklet-folder-new' }) + $.make('div', { className: 'NB-bookmarklet-side-half NB-bookmarklet-side-subscribe' }, [ + $.make('div', { className: 'NB-subscribe-feed' }), + $.make('div', { className: 'NB-bookmarklet-folder-container' }, [ + $.make('img', { className: 'NB-bookmarklet-folder-add-button', src: 'data:image/png;charset=utf-8;base64,{{ add_image }}', title: 'Add New Folder' }), + this.make_folders(), + $.make('div', { className: 'NB-bookmarklet-new-folder-container' }, [ + $.make('img', { className: 'NB-bookmarklet-folder-new-label', src: 'data:image/png;charset=utf-8;base64,{{ new_folder_image }}' }), + $.make('input', { type: 'text', name: 'new_folder_name', className: 'NB-bookmarklet-folder-new' }) + ]) + ]), + $.make('div', { className: 'NB-modal-submit' }, [ + $.make('div', { className: 'NB-bookmarklet-button-subscribe NB-modal-submit-button NB-modal-submit-green' }, 'Subscribe to this site') ]) + ]), + $.make('div', { className: 'NB-bookmarklet-side-half NB-bookmarklet-side-loading' }, [ + $.make('img', { className: 'NB-subscribe-loader', src: 'data:image/png;charset=utf-8;base64,{{ add_image }}', title: 'Loading...' }), + $.make('div', { className: 'NB-subscribe-load-text' }, 'Shared stories are on their way...') ]) ]) ]); @@ -191,7 +205,7 @@ save: function() { var self = this; - var $submit = $('.NB-modal-submit-button'); + var $submit = $('.NB-bookmarklet-button-subscribe', this.$modal); var folder = $('.NB-folders').val(); var add_site_url = "http://"+this.domain+"{% url api-add-site token %}?callback=?"; @@ -210,23 +224,23 @@ } $.getJSON(add_site_url, data, function(resp) { - self.post_save(resp); + self.confirm_subscription(resp.code > 0, resp.message); }); }, - post_save: function(resp) { - var $submit = $('.NB-modal-submit-button'); + confirm_subscription: function(subscribed, message) { + var $submit = $('.NB-bookmarklet-button-subscribe', this.$modal); - $submit.addClass('NB-close'); + $submit.addClass('NB-disabled'); - if (resp.code == 1) { + if (subscribed) { $submit.html($.make('div', { className: 'NB-bookmarklet-accept' }, [ - $.make('img', { src: 'data:image/png;charset=utf-8;base64,{{ accept_image }}' }), - 'Added!' + $.make('img', { src: 'data:image/png;charset=utf-8;base64,' + this.images['accept_image'] }), + 'Subscribed' ])); - setTimeout(function() { - $.modal.close(); - }, 2000); + // setTimeout(function() { + // $.modal.close(); + // }, 2000); } else { var $error = $.make('div', { className: 'NB-bookmarklet-error' }, [ $.make('img', { className: 'NB-bookmarklet-folder-label', src: 'data:image/png;charset=utf-8;base64,{{ error_image }}' }), @@ -242,15 +256,43 @@ // ============= pre_share_check_story: function() { + var $side = $('.NB-bookmarklet-side', this.$modal); + var $side_loading = $('.NB-bookmarklet-side-loading', this.$modal); + var $side_subscribe = $('.NB-bookmarklet-side-subscribe', this.$modal); var check_story_url = "http://"+this.domain+"{% url api-check-share-on-site token %}?callback=?"; var data = { story_url: window.location.href, - rss_url: $('link[type="application/rss+xml"]').attr('href') + rss_url: this.get_page_rss_url() }; - $.getJSON(check_story_url, data, function(resp) { - - }); + $.getJSON(check_story_url, data, _.bind(function(data) { + $side_loading.animate({'left': '-100%'}, { + 'easing': 'easeInOutQuint', + 'duration': 1650, + 'queue': false + }); + $side_subscribe.css('left', $side.outerWidth(true)+4).animate({'left': 0}, { + 'easing': 'easeInOutQuint', + 'duration': 1650, + 'queue': false + }); + this.feed = data.feed; + this.make_feed_subscribe(data.feed); + if (data.subscribed) { + $('.NB-bookmarklet-folder-container', this.$modal).hide(); + this.confirm_subscription(data.subscribed); + } + }, this)); + }, + + make_feed_subscribe: function(feed) { + if (feed) { + var $feed = $.make('div', { className: 'NB-subscribe-feed' }, [ + $.make('img', { src: 'data:image/png;charset=utf-8;base64,' + feed.favicon }), + $.make('div', { className: 'NB-subscribe-feed-title' }, feed.feed_title) + ]); + $('.NB-subscribe-feed', this.$modal).replaceWith($feed); + } }, // =============== @@ -258,14 +300,47 @@ // =============== share_story: function() { + var $share = $(".NB-bookmarklet-comment-submit", this.$modal); + + $share.addClass('NB-disabled').text('Sharing...'); + $.ajax({ - url: 'http://'+this.domain+"{% url api-share-story token %}?callback=?", - - }) + url: '//'+this.domain+"{% url api-share-story token %}", + type: 'POST', + data: { + title: this.story_title, + content: this.story_content, + comments: $('textarea[name=newsblur_comment]', this.$modal).val(), + feed_id: this.feed && this.feed.id, + story_url: window.location.href, + rss_url: this.get_page_rss_url() + }, + success: _.bind(this.post_share_story, this), + error: _.bind(this.error_share_story, this) + }); }, post_share_story: function(data) { + var $share = $(".NB-bookmarklet-comment-submit", this.$modal); + if (data.code < 0) { + return this.error_share_story(data); + } + + $share.html($.make('div', { className: 'NB-bookmarklet-accept' }, [ + $.make('img', { src: 'data:image/png;charset=utf-8;base64,' + this.images['accept_image'] }), + 'Shared' + ])); + // setTimeout(function() { + // $.modal.close(); + // }, 2000); + + }, + + error_share_story: function(data) { + $share.removeClass('NB-disabled'); + console.log(["error sharing", data]); + this.update_share_button_title(); }, open_add_folder: function() { @@ -296,12 +371,12 @@ } else { var $readability = $(window.readability.init()); - var title = $readability.children("h1").text(); - var content = $("#readability-content", $readability).html(); + this.story_title = $readability.children("h1").text(); + this.story_content = $("#readability-content", $readability).html(); } - $title.html(title); - $content.html(content); + $title.html(this.story_title); + $content.html(this.story_content); }, get_selected_html: function() { @@ -355,6 +430,10 @@ return title; }, + get_page_rss_url: function() { + return $('link[type="application/rss+xml"]').attr('href'); + }, + // =========== // = Actions = // =========== @@ -362,7 +441,7 @@ handle_clicks: function(elem, e) { var self = this; - $.targetIs(e, { tagSelector: '.NB-modal-submit-button' }, function($t, $p) { + $.targetIs(e, { tagSelector: '.NB-bookmarklet-button-subscribe' }, function($t, $p) { e.preventDefault(); if (!$t.hasClass('NB-disabled')) { @@ -381,6 +460,14 @@ $t.toggleClass('NB-active'); }); + $.targetIs(e, { tagSelector: '.NB-bookmarklet-comment-submit' }, function($t, $p) { + e.preventDefault(); + + if (!$t.hasClass('NB-disabled')) { + self.share_story(); + } + }); + $.targetIs(e, { tagSelector: '.NB-close' }, function($t, $p) { e.preventDefault(); diff --git a/utils/log.py b/utils/log.py index 1aa33caad..f45b4821f 100644 --- a/utils/log.py +++ b/utils/log.py @@ -13,12 +13,13 @@ def getlogger(): logger = logging.getLogger('newsblur') return logger -def user(u, msg): +def user(u, msg, request=None): platform = '------' time_elapsed = "" - if isinstance(u, WSGIRequest): - request = u - u = request.user + if isinstance(u, WSGIRequest) or request: + if not request: + request = u + u = request.user user_agent = request.environ.get('HTTP_USER_AGENT', '') if 'iPad App' in user_agent: platform = 'iPad'