From 1428369849cb6e6a2824fb4f3d6930d23932aad1 Mon Sep 17 00:00:00 2001 From: Steffen van Bergerem Date: Fri, 8 May 2015 23:46:21 +0200 Subject: [PATCH 1/2] Mobile: Add button to follow/unfollow tags --- app/assets/javascripts/mobile/mobile.js | 1 + .../javascripts/mobile/tag_following.js | 43 +++++++++++++++++++ app/assets/stylesheets/mobile/mobile.scss | 2 + app/views/tags/show.mobile.haml | 7 +++ config/locales/javascript/javascript.en.yml | 2 + features/mobile/tags.feature | 28 +++++++++--- 6 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/mobile/tag_following.js diff --git a/app/assets/javascripts/mobile/mobile.js b/app/assets/javascripts/mobile/mobile.js index 5cd873f13..0915480ad 100644 --- a/app/assets/javascripts/mobile/mobile.js +++ b/app/assets/javascripts/mobile/mobile.js @@ -18,6 +18,7 @@ //= require widgets/timeago //= require mobile/mobile_file_uploader //= require mobile/profile_aspects +//= require mobile/tag_following $(document).ready(function(){ diff --git a/app/assets/javascripts/mobile/tag_following.js b/app/assets/javascripts/mobile/tag_following.js new file mode 100644 index 000000000..ef46ac984 --- /dev/null +++ b/app/assets/javascripts/mobile/tag_following.js @@ -0,0 +1,43 @@ +$(document).ready(function(){ + $(".tag_following_action").bind("tap click", function(evt){ + evt.preventDefault(); + var button = $(this), + tagName = button.data("name"); + + if(button.hasClass("btn-success")){ + $.ajax({ + url: Routes.tag_followings_path(), + data: JSON.stringify({"name": tagName}), + type: "POST", + dataType: "json", + headers: { + "Accept": "application/json, text/javascript, */*; q=0.01" + }, + contentType: "application/json; charset=UTF-8" + }).done(function(data) { + gon.preloads.tagFollowings.push(data); + button.removeClass("btn-success").addClass("btn-danger"); + button.text(Diaspora.I18n.t("stream.tags.stop_following", {tag: tagName})); + }).fail(function() { + alert(Diaspora.I18n.t("stream.tags.follow_error", {name: "#" + tagName})); + }); + } + else if(button.hasClass("btn-danger")){ + var tagFollowing = _.findWhere(gon.preloads.tagFollowings,{name: tagName}); + if(!tagFollowing) { return; } + $.ajax({ + url: Routes.tag_following_path(tagFollowing.id), + dataType: "json", + type: "DELETE", + headers: { + "Accept": "application/json, text/javascript, */*; q=0.01" + } + }).done(function() { + button.removeClass("btn-danger").addClass("btn-success"); + button.text(Diaspora.I18n.t("stream.tags.follow", {tag: tagName})); + }).fail(function() { + alert(Diaspora.I18n.t("stream.tags.stop_following_error", {name: "#" + tagName})); + }); + } + }); +}); diff --git a/app/assets/stylesheets/mobile/mobile.scss b/app/assets/stylesheets/mobile/mobile.scss index 9040f6d23..94bebc6b0 100644 --- a/app/assets/stylesheets/mobile/mobile.scss +++ b/app/assets/stylesheets/mobile/mobile.scss @@ -1210,3 +1210,5 @@ select#aspect_ids_ { #app #main h1 { word-wrap: break-word; } + +.tag_following_action { margin: 5px 0 10px 0; } diff --git a/app/views/tags/show.mobile.haml b/app/views/tags/show.mobile.haml index 581af773a..4afda991b 100644 --- a/app/views/tags/show.mobile.haml +++ b/app/views/tags/show.mobile.haml @@ -4,6 +4,13 @@ %h1 = @stream.display_tag_name +- if user_signed_in? + - unless tag_followed? + .btn.btn-success.tag_following_action{data: {name: @stream.tag_name}} + = t(".follow", tag: @stream.tag_name) + - else + .btn.btn-danger.tag_following_action{data: {name: @stream.tag_name}} + = t(".stop_following", tag: @stream.tag_name) #main_stream.stream = render 'shared/stream', :posts => @stream.stream_posts diff --git a/config/locales/javascript/javascript.en.yml b/config/locales/javascript/javascript.en.yml index a970052fe..0155b062e 100644 --- a/config/locales/javascript/javascript.en.yml +++ b/config/locales/javascript/javascript.en.yml @@ -206,6 +206,8 @@ en: follow: "Follow #<%= tag %>" following: "Following #<%= tag %>" stop_following: "Stop following #<%= tag %>" + follow_error: "Couldn’t follow <%= name %> :(" + stop_following_error: "Couldn’t stop following <%= name %> :(" header: home: "Home" diff --git a/features/mobile/tags.feature b/features/mobile/tags.feature index d1804deea..63a963de5 100644 --- a/features/mobile/tags.feature +++ b/features/mobile/tags.feature @@ -2,13 +2,31 @@ Feature: Interacting with tags Background: - Given a user with username "alice" - And "alice@alice.alice" has a public post with text "Hello! i am #newhere" - When I sign in as "alice@alice.alice" + Given following users exist: + | username | + | bob | + | alice | + And "alice@alice.alice" has a public post with text "Hello! I am #newhere" + When I sign in as "bob@bob.bob" + And I visit the mobile search page + And I fill in the following: + | q | #newhere | + And I press "Search" + Then I should see "Follow #newhere" within ".tag_following_action" + + Scenario: Start and stop following a tag + When I click on selector ".tag_following_action" + Then I should see "Stop following #newhere" within ".tag_following_action" + When I am on the home page + Then I should see "Hello! I am #newhere" - Scenario: Searching for a tag When I visit the mobile search page And I fill in the following: | q | #newhere | And I press "Search" - Then I should see "#newhere" within ".ltr" + Then I should see "Stop following #newhere" within ".tag_following_action" + + When I click on selector ".tag_following_action" + Then I should see "Follow #newhere" within ".tag_following_action" + When I am on the home page + Then I should not see "Hello! I am #newhere" From 0c5443d543b53a72cee65c9c112fce0936f3682f Mon Sep 17 00:00:00 2001 From: Steffen van Bergerem Date: Sat, 9 May 2015 01:27:56 +0200 Subject: [PATCH 2/2] Make js-routes use compact and camelcase function names closes #5941 --- Changelog.md | 1 + .../app/helpers/handlebars-helpers.js | 30 ++++++------ app/assets/javascripts/app/models/person.js | 2 +- app/assets/javascripts/app/pages/contacts.js | 2 +- app/assets/javascripts/app/pages/profile.js | 4 +- app/assets/javascripts/app/views/help_view.js | 2 +- .../app/views/notification_dropdown_view.js | 2 +- .../single_post_moderation.js | 4 +- .../app/views/stream_post_views.js | 4 +- .../javascripts/mobile/profile_aspects.js | 12 ++--- .../javascripts/mobile/tag_following.js | 4 +- .../templates/profile_header_tpl.jst.hbs | 12 ++--- config/initializers/jsroutes.rb | 4 ++ spec/javascripts/app/models/person_spec.js | 48 +++++++++---------- 14 files changed, 68 insertions(+), 63 deletions(-) create mode 100644 config/initializers/jsroutes.rb diff --git a/Changelog.md b/Changelog.md index 409acf0b7..a531e0b94 100644 --- a/Changelog.md +++ b/Changelog.md @@ -34,6 +34,7 @@ * Added link to diasporafoundation.org to invitation email [#5893](https://github.com/diaspora/diaspora/pull/5893) * Gracefully handle missing `og:url`s [#5926](https://github.com/diaspora/diaspora/pull/5926) * Remove private post content from "also commented" mails [#5931](https://github.com/diaspora/diaspora/pull/5931) +* Add a button to follow/unfollow tags to the mobile interface [#5941](https://github.com/diaspora/diaspora/pull/5941) # 0.5.0.1 diff --git a/app/assets/javascripts/app/helpers/handlebars-helpers.js b/app/assets/javascripts/app/helpers/handlebars-helpers.js index c428bfdd7..1862dc11b 100644 --- a/app/assets/javascripts/app/helpers/handlebars-helpers.js +++ b/app/assets/javascripts/app/helpers/handlebars-helpers.js @@ -12,13 +12,13 @@ Handlebars.registerHelper('imageUrl', function(path){ return ImagePaths.get(path); }); -Handlebars.registerHelper('urlTo', function(path_helper, id, data){ +Handlebars.registerHelper("urlTo", function(pathHelper, id, data){ if( !data ) { // only one argument given to helper, mangle parameters data = id; - return Routes[path_helper+'_path'](data.hash); + return Routes[pathHelper](data.hash); } - return Routes[path_helper+'_path'](id, data.hash); + return Routes[pathHelper](id, data.hash); }); Handlebars.registerHelper('linkToAuthor', function(context, block) { @@ -89,32 +89,32 @@ Handlebars.registerHelper('localTime', function(timestamp) { return new Date(timestamp).toLocaleString(); }); -Handlebars.registerHelper('fmtTags', function(tags) { +Handlebars.registerHelper("fmtTags", function(tags) { var links = _.map(tags, function(tag) { - return '' + - ' #' + tag + - ''; - }).join(' '); + return "" + + " #" + tag + + ""; + }).join(" "); return new Handlebars.SafeString(links); }); -Handlebars.registerHelper('fmtText', function(text) { +Handlebars.registerHelper("fmtText", function(text) { return new Handlebars.SafeString(app.helpers.textFormatter(text)); }); -Handlebars.registerHelper('isCurrentPage', function(path_helper, id, options){ +Handlebars.registerHelper("isCurrentPage", function(pathHelper, id, options){ var currentPage = "/"+Backbone.history.fragment; - if (currentPage === Handlebars.helpers.urlTo(path_helper, id, options.data)) { + if (currentPage === Handlebars.helpers.urlTo(pathHelper, id, options.data)) { return options.fn(this); } else { return options.inverse(this); } }); -Handlebars.registerHelper('isCurrentProfilePage', function(id, diaspora_handle, options){ - var username = diaspora_handle.split("@")[0]; - return Handlebars.helpers.isCurrentPage('person', id, options) || - Handlebars.helpers.isCurrentPage('user_profile', username, options); +Handlebars.registerHelper("isCurrentProfilePage", function(id, diasporaHandle, options){ + var username = diasporaHandle.split("@")[0]; + return Handlebars.helpers.isCurrentPage("person", id, options) || + Handlebars.helpers.isCurrentPage("userProfile", username, options); }); Handlebars.registerHelper('aspectMembershipIndicator', function(contact,in_aspect) { diff --git a/app/assets/javascripts/app/models/person.js b/app/assets/javascripts/app/models/person.js index 0cfcc9f66..8c9a7241a 100644 --- a/app/assets/javascripts/app/models/person.js +++ b/app/assets/javascripts/app/models/person.js @@ -2,7 +2,7 @@ app.models.Person = Backbone.Model.extend({ url: function() { - return Routes.person_path(this.get('guid')); + return Routes.person(this.get("guid")); }, initialize: function() { diff --git a/app/assets/javascripts/app/pages/contacts.js b/app/assets/javascripts/app/pages/contacts.js index 27e7ec126..782120b22 100644 --- a/app/assets/javascripts/app/pages/contacts.js +++ b/app/assets/javascripts/app/pages/contacts.js @@ -83,7 +83,7 @@ app.pages.Contacts = Backbone.View.extend({ update: function() { $("#aspect_nav .ui-sortable").removeClass("synced"); var data = JSON.stringify({ ordered_aspect_ids: $(this).sortable("toArray", { attribute: "data-aspect-id" }) }); - $.ajax(Routes.order_aspects_path(), + $.ajax(Routes.orderAspects(), { type: "put", dataType: "text", contentType: "application/json", data: data }) .done(function() { $("#aspect_nav .ui-sortable").addClass("synced"); }); }, diff --git a/app/assets/javascripts/app/pages/profile.js b/app/assets/javascripts/app/pages/profile.js index 767c9f3a2..f9f24da73 100644 --- a/app/assets/javascripts/app/pages/profile.js +++ b/app/assets/javascripts/app/pages/profile.js @@ -76,11 +76,11 @@ app.pages.Profile = app.views.Base.extend({ } // a collection is set, this means we want to view photos - var route = this.streamCollection ? 'person_photos_path' : 'person_stream_path'; + var route = this.streamCollection ? "personPhotos" : "personStream"; var view = this.streamViewClass ? this.streamViewClass : app.views.Stream; app.stream = new app.models.Stream(null, { - basePath: Routes[route](app.page.model.get('guid')), + basePath: Routes[route](app.page.model.get("guid")), collection: this.streamCollection }); app.stream.fetch(); diff --git a/app/assets/javascripts/app/views/help_view.js b/app/assets/javascripts/app/views/help_view.js index 6cbcdf25c..f1ec4618f 100644 --- a/app/assets/javascripts/app/views/help_view.js +++ b/app/assets/javascripts/app/views/help_view.js @@ -40,7 +40,7 @@ app.views.Help = app.views.StaticContentView.extend({ this.CHAT_SUBS = { add_contact_roster_a: { toggle_privilege: this.getChatIcons(), - contacts_page: this.linkHtml(Routes.contacts_path(), Diaspora.I18n.t('chat.contacts_page')) + contacts_page: this.linkHtml(Routes.contacts(), Diaspora.I18n.t("chat.contacts_page")) } }; diff --git a/app/assets/javascripts/app/views/notification_dropdown_view.js b/app/assets/javascripts/app/views/notification_dropdown_view.js index a2c7525db..36de9c954 100644 --- a/app/assets/javascripts/app/views/notification_dropdown_view.js +++ b/app/assets/javascripts/app/views/notification_dropdown_view.js @@ -74,7 +74,7 @@ app.views.NotificationDropdown = app.views.Base.extend({ getNotifications: function(){ var self = this; - $.getJSON(Routes.notifications_path(this.getParams()), function(notifications){ + $.getJSON(Routes.notifications(this.getParams()), function(notifications){ $.each(notifications, function(){ self.notifications.push(this); }); self.hasMoreNotifs = notifications.length >= self.perPage; if(self.nextPage){ self.nextPage++; } diff --git a/app/assets/javascripts/app/views/single-post-viewer/single_post_moderation.js b/app/assets/javascripts/app/views/single-post-viewer/single_post_moderation.js index ad636addf..8604d1adc 100644 --- a/app/assets/javascripts/app/views/single-post-viewer/single_post_moderation.js +++ b/app/assets/javascripts/app/views/single-post-viewer/single_post_moderation.js @@ -49,7 +49,7 @@ app.views.SinglePostModeration = app.views.Feedback.extend({ createParticipation: function (evt) { if(evt) { evt.preventDefault(); } var self = this; - $.post(Routes.post_participation_path(this.model.get("id")), {}, function () { + $.post(Routes.postParticipation(this.model.get("id")), {}, function () { self.model.set({participation: true}); self.render(); }); @@ -58,7 +58,7 @@ app.views.SinglePostModeration = app.views.Feedback.extend({ destroyParticipation: function (evt) { if(evt) { evt.preventDefault(); } var self = this; - $.post(Routes.post_participation_path(this.model.get("id")), { _method: "delete" }, function () { + $.post(Routes.postParticipation(this.model.get("id")), { _method: "delete" }, function () { self.model.set({participation: false}); self.render(); }); diff --git a/app/assets/javascripts/app/views/stream_post_views.js b/app/assets/javascripts/app/views/stream_post_views.js index fb44250f6..60a965136 100644 --- a/app/assets/javascripts/app/views/stream_post_views.js +++ b/app/assets/javascripts/app/views/stream_post_views.js @@ -119,7 +119,7 @@ app.views.StreamPost = app.views.Post.extend({ createParticipation: function (evt) { if(evt) { evt.preventDefault(); } that = this; - $.post(Routes.post_participation_path(this.model.get("id")), {}, function () { + $.post(Routes.postParticipation(this.model.get("id")), {}, function () { that.model.set({participation: true}); that.render(); }); @@ -128,7 +128,7 @@ app.views.StreamPost = app.views.Post.extend({ destroyParticipation: function (evt) { if(evt) { evt.preventDefault(); } that = this; - $.post(Routes.post_participation_path(this.model.get("id")), { _method: "delete" }, function () { + $.post(Routes.postParticipation(this.model.get("id")), { _method: "delete" }, function () { that.model.set({participation: false}); that.render(); }); diff --git a/app/assets/javascripts/mobile/profile_aspects.js b/app/assets/javascripts/mobile/profile_aspects.js index 11769b4bd..2c664f045 100644 --- a/app/assets/javascripts/mobile/profile_aspects.js +++ b/app/assets/javascripts/mobile/profile_aspects.js @@ -27,14 +27,14 @@ $(document).ready(function(){ if(selected.hasClass('selected')) { // remove from aspect - var membership_id = selected.data('membership_id'); + var membershipId = selected.data("membership_id"); $.ajax({ - url: Routes.aspect_membership_path(membership_id), - type: 'DELETE', - dataType: 'json', + url: Routes.aspectMembership(membershipId), + type: "DELETE", + dataType: "json", headers: { - 'Accept': "application/json, text/javascript, */*; q=0.01" + "Accept": "application/json, text/javascript, */*; q=0.01" } }).done(function() { selected.text("– " + Diaspora.I18n.t('aspect_dropdown.mobile_row_unchecked', {name: selected.data('name')})); @@ -50,7 +50,7 @@ $(document).ready(function(){ var person_id = $el.data('person-id'); $.ajax({ - url: Routes.aspect_memberships_path(), + url: Routes.aspectMemberships(), data: JSON.stringify({ "person_id": person_id, "aspect_id": parseInt(selected.val(), 10) diff --git a/app/assets/javascripts/mobile/tag_following.js b/app/assets/javascripts/mobile/tag_following.js index ef46ac984..43eda0053 100644 --- a/app/assets/javascripts/mobile/tag_following.js +++ b/app/assets/javascripts/mobile/tag_following.js @@ -6,7 +6,7 @@ $(document).ready(function(){ if(button.hasClass("btn-success")){ $.ajax({ - url: Routes.tag_followings_path(), + url: Routes.tagFollowings(), data: JSON.stringify({"name": tagName}), type: "POST", dataType: "json", @@ -26,7 +26,7 @@ $(document).ready(function(){ var tagFollowing = _.findWhere(gon.preloads.tagFollowings,{name: tagName}); if(!tagFollowing) { return; } $.ajax({ - url: Routes.tag_following_path(tagFollowing.id), + url: Routes.tagFollowing(tagFollowing.id), dataType: "json", type: "DELETE", headers: { diff --git a/app/assets/templates/profile_header_tpl.jst.hbs b/app/assets/templates/profile_header_tpl.jst.hbs index 516ae8345..45c06f9eb 100644 --- a/app/assets/templates/profile_header_tpl.jst.hbs +++ b/app/assets/templates/profile_header_tpl.jst.hbs @@ -2,7 +2,7 @@
{{#if is_own_profile}} {{!-- can't block myself, so don't check it here --}} - {{t 'people.edit_my_profile'}} + {{t 'people.edit_my_profile'}} {{else}} {{#if is_blocked}} {{t 'people.stop_ignoring'}} {{else}} @@ -31,7 +31,7 @@
{{t 'profile.you_have_no_tags'}} - {{t 'profile.add_some'}} + {{t 'profile.add_some'}}
{{/if}} @@ -74,8 +74,8 @@ {{#if show_photos}} -
  • - +
  • + {{t 'profile.photos'}}
    {{photos.count}}
    @@ -83,7 +83,7 @@
  • {{/if}} {{#if show_contacts}} -
  • +
  • {{#if is_own_profile}} @@ -91,7 +91,7 @@
    {{contacts.count}}
    {{else}} - + {{t 'profile.contacts'}}
    {{contacts.count}}
    diff --git a/config/initializers/jsroutes.rb b/config/initializers/jsroutes.rb new file mode 100644 index 000000000..64ee94599 --- /dev/null +++ b/config/initializers/jsroutes.rb @@ -0,0 +1,4 @@ +JsRoutes.setup do |config| + config.camel_case = true + config.compact = true +end diff --git a/spec/javascripts/app/models/person_spec.js b/spec/javascripts/app/models/person_spec.js index b36b8beb6..21f18d658 100644 --- a/spec/javascripts/app/models/person_spec.js +++ b/spec/javascripts/app/models/person_spec.js @@ -1,69 +1,69 @@ describe("app.models.Person", function() { beforeEach(function() { - this.mutual_contact = factory.person({relationship: 'mutual'}); - this.sharing_contact = factory.person({relationship :'sharing'}); - this.receiving_contact = factory.person({relationship: 'receiving'}); - this.blocked_contact = factory.person({relationship: 'blocked', block: {id: 1}}); + this.mutualContact = factory.person({relationship: "mutual"}); + this.sharingContact = factory.person({relationship: "sharing"}); + this.receivingContact = factory.person({relationship: "receiving"}); + this.blockedContact = factory.person({relationship: "blocked", block: {id: 1}}); }); context("#isSharing", function() { it("indicates if the person is sharing", function() { - expect(this.mutual_contact.isSharing()).toBeTruthy(); - expect(this.sharing_contact.isSharing()).toBeTruthy(); + expect(this.mutualContact.isSharing()).toBeTruthy(); + expect(this.sharingContact.isSharing()).toBeTruthy(); - expect(this.receiving_contact.isSharing()).toBeFalsy(); - expect(this.blocked_contact.isSharing()).toBeFalsy(); + expect(this.receivingContact.isSharing()).toBeFalsy(); + expect(this.blockedContact.isSharing()).toBeFalsy(); }); }); context("#isReceiving", function() { it("indicates if the person is receiving", function() { - expect(this.mutual_contact.isReceiving()).toBeTruthy(); - expect(this.receiving_contact.isReceiving()).toBeTruthy(); + expect(this.mutualContact.isReceiving()).toBeTruthy(); + expect(this.receivingContact.isReceiving()).toBeTruthy(); - expect(this.sharing_contact.isReceiving()).toBeFalsy(); - expect(this.blocked_contact.isReceiving()).toBeFalsy(); + expect(this.sharingContact.isReceiving()).toBeFalsy(); + expect(this.blockedContact.isReceiving()).toBeFalsy(); }); }); context("#isMutual", function() { it("indicates if we share mutually with the person", function() { - expect(this.mutual_contact.isMutual()).toBeTruthy(); + expect(this.mutualContact.isMutual()).toBeTruthy(); - expect(this.receiving_contact.isMutual()).toBeFalsy(); - expect(this.sharing_contact.isMutual()).toBeFalsy(); - expect(this.blocked_contact.isMutual()).toBeFalsy(); + expect(this.receivingContact.isMutual()).toBeFalsy(); + expect(this.sharingContact.isMutual()).toBeFalsy(); + expect(this.blockedContact.isMutual()).toBeFalsy(); }); }); context("#isBlocked", function() { it("indicates whether we blocked the person", function() { - expect(this.blocked_contact.isBlocked()).toBeTruthy(); + expect(this.blockedContact.isBlocked()).toBeTruthy(); - expect(this.mutual_contact.isBlocked()).toBeFalsy(); - expect(this.receiving_contact.isBlocked()).toBeFalsy(); - expect(this.sharing_contact.isBlocked()).toBeFalsy(); + expect(this.mutualContact.isBlocked()).toBeFalsy(); + expect(this.receivingContact.isBlocked()).toBeFalsy(); + expect(this.sharingContact.isBlocked()).toBeFalsy(); }); }); context("#block", function() { it("POSTs a block to the server", function() { - this.sharing_contact.block(); + this.sharingContact.block(); var request = jasmine.Ajax.requests.mostRecent(); expect(request.method).toEqual("POST"); - expect($.parseJSON(request.params).block.person_id).toEqual(this.sharing_contact.id); + expect($.parseJSON(request.params).block.person_id).toEqual(this.sharingContact.id); }); }); context("#unblock", function() { it("DELETEs a block from the server", function(){ - this.blocked_contact.unblock(); + this.blockedContact.unblock(); var request = jasmine.Ajax.requests.mostRecent(); expect(request.method).toEqual("DELETE"); - expect(request.url).toEqual(Routes.block_path(this.blocked_contact.get('block').id)); + expect(request.url).toEqual(Routes.block(this.blockedContact.get("block").id)); }); }); });