-
+
#{aspect.name}
-
- #{aspect_membership_button(aspect, contact, person)}
-
LISTITEM
str.html_safe
diff --git a/app/models/aspect_membership.rb b/app/models/aspect_membership.rb
index 372e744ea..64abd8f9c 100644
--- a/app/models/aspect_membership.rb
+++ b/app/models/aspect_membership.rb
@@ -16,4 +16,12 @@ class AspectMembership < ActiveRecord::Base
true
end
+ def as_json(opts={})
+ {
+ :id => self.id,
+ :person_id => self.person.id,
+ :contact_id => self.contact.id,
+ :aspect_ids => self.contact.aspects.map{|a| a.id}
+ }
+ end
end
diff --git a/app/models/person.rb b/app/models/person.rb
index 1b534eed4..c56adb374 100644
--- a/app/models/person.rb
+++ b/app/models/person.rb
@@ -220,12 +220,13 @@ class Person < ActiveRecord::Base
end
def as_json(opts={})
- {
- :id => self.guid,
+ json = {
+ :id => self.id,
:name => self.name,
:avatar => self.profile.image_url(:thumb_small),
:handle => self.diaspora_handle,
- :url => "/people/#{self.id}"
+ :url => "/people/#{self.id}",
+ :hashtags => self.profile.tags.map{|t| "##{t.name}"}
}
end
diff --git a/app/views/aspect_memberships/create.js.erb b/app/views/aspect_memberships/create.js.erb
index 595304cf6..2292d0ae6 100644
--- a/app/views/aspect_memberships/create.js.erb
+++ b/app/views/aspect_memberships/create.js.erb
@@ -19,4 +19,3 @@ if($('#aspects_list').length == 1) {
};
element.fadeTo(200,1);
-
diff --git a/app/views/comments/_comment.html.haml b/app/views/comments/_comment.html.haml
index c32283bde..0809a407b 100644
--- a/app/views/comments/_comment.html.haml
+++ b/app/views/comments/_comment.html.haml
@@ -9,7 +9,7 @@
= person_image_link(comment.author)
.content
%span.from
- = person_link(comment.author)
+ = person_link(comment.author, :class => "hovercardable")
%span{:class => direction_for(comment.text)}
= markdownify(comment.text, :youtube_maps => comment.youtube_titles)
diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml
index 55dd8ae51..f5c0d084d 100644
--- a/app/views/layouts/_header.html.haml
+++ b/app/views/layouts/_header.html.haml
@@ -52,6 +52,17 @@
.ajax_loader
= image_tag("ajax-loader.gif")
+ #hovercard_container
+ #hovercard
+ %img.avatar
+ %h4
+ %a.person
+ #hovercard_dropdown_container
+
+ .hovercard_footer
+ .footer_container
+ .hashtags
+
%ul#user_menu.dropdown
%li
.right
diff --git a/app/views/people/_index.html.haml b/app/views/people/_index.html.haml
index d7f36f7af..72df7e017 100644
--- a/app/views/people/_index.html.haml
+++ b/app/views/people/_index.html.haml
@@ -5,13 +5,9 @@
- if current_user
- contact = current_user.contacts.find_by_person_id(person.id)
- contact ||= Contact.new(:person => person)
- - unless person == current_user.person
- .right
- = render 'aspect_memberships/aspect_dropdown', :contact => contact, :person => person, :hang => 'left'
.content
%span.from
- =person_link(person)
- .info
- = person.profile.format_tags(person.profile.tag_string)
+ =person_link(person, :class => "hovercardable")
+
= will_paginate people, :params => {:controller => 'people', :action => 'tag_index'}
diff --git a/app/views/shared/_stream_element.html.haml b/app/views/shared/_stream_element.html.haml
index e65d3e039..bc075a6c9 100644
--- a/app/views/shared/_stream_element.html.haml
+++ b/app/views/shared/_stream_element.html.haml
@@ -18,7 +18,7 @@
.content
%div.post_initial_info
%span.from
- = person_link(post.author, :class => 'author')
+ = person_link(post.author, :class => 'hovercardable')
%time.time.timeago{:datetime => post.created_at, :integer => time_for_sort(post).to_i}
%span.details
\-
diff --git a/config/assets.yml b/config/assets.yml
index 28b473f00..a3a2b3037 100644
--- a/config/assets.yml
+++ b/config/assets.yml
@@ -33,9 +33,10 @@ javascripts:
- public/javascripts/widgets/infinite-scroll.js
- public/javascripts/widgets/directionDetector.js
- public/javascripts/widgets/notifications.js
- - public/javascripts/widgets/notifications-badge.js
- public/javascripts/widgets/flashes.js
- public/javascripts/widgets/post.js
+ - public/javascripts/widgets/hovercard.js
+ - public/javascripts/widgets/notifications-badge.js
- public/javascripts/view.js
- public/javascripts/stream.js
- public/javascripts/content-updater.js
@@ -45,18 +46,27 @@ javascripts:
- public/javascripts/login.js
mobile:
- public/javascripts/vendor/jquery152.min.js
- - public/javascripts/custom-mobile-scripting.js
- - public/javascripts/vendor/jquery.mobile-1.0a4.js
- - public/javascripts/jquery.infinitescroll-custom.js
+
+ - public/javascripts/vendor/underscore.js
+ - public/javascripts/vendor/backbone-min.js
+
+ - public/javascripts/vendor/Mustache.js
+
+ - public/javascripts/mobile/app.js
+ - public/javascripts/mobile/helpers.js
+ - public/javascripts/mobile/models/*
+ - public/javascripts/mobile/collections/*
+ - public/javascripts/mobile/controllers/*
+ - public/javascripts/mobile/views/*
+
- public/javascripts/diaspora.js
- public/javascripts/widgets/i18n.js
- - public/javascripts/widgets/infinite-scroll.js
- - public/javascripts/rails.js
mailchimp:
- public/javascripts/vendor/mailchimp/jquery.form.js
- public/javascripts/vendor/mailchimp/jquery.validate.js
- public/javascripts/vendor/mailchimp/jquery126.min.js
aspects:
+ - public/javascripts/aspect-edit.js
- public/javascripts/contact-list.js
finder:
- public/javascripts/friend-finder.js
@@ -86,12 +96,9 @@ stylesheets:
- public/stylesheets/vendor/fileuploader.css
- public/stylesheets/vendor/tipsy.css
- public/stylesheets/vendor/autoSuggest.css
-
- popup:
- - public/stylesheets/application.css
- - public/stylesheets/popup.css
- - public/stylesheets/ui.css
-
rtl:
- public/stylesheets/rtl.css
+
+ mobile:
+ - public/stylesheets/mobile.css
diff --git a/config/routes.rb b/config/routes.rb
index 1dd4938fa..e78d8b2ec 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -104,6 +104,8 @@ Diaspora::Application.routes.draw do
end
end
+ get "people/:id/aspect_membership_button" => "people#aspect_membership_dropdown", :as => "aspect_membership_button"
+
diff --git a/public/javascripts/contact-edit.js b/public/javascripts/contact-edit.js
index 68a235492..5c6b5036d 100644
--- a/public/javascripts/contact-edit.js
+++ b/public/javascripts/contact-edit.js
@@ -9,12 +9,9 @@ var ContactEdit = {
});
},
- updateNumber: function(personId){
- var dropdown = $(".dropdown_list[data-person_id=" + personId.toString() +"]"),
- number = dropdown.find(".selected").length,
- button = dropdown.parents(".dropdown").children('.button.toggle');
-
- var replacement;
+ updateNumber: function(dropdown, personId, number){
+ var button = dropdown.parents(".dropdown").children('.button.toggle'),
+ replacement;
if (number == 0) {
button.removeClass("in_aspects");
@@ -27,7 +24,7 @@ var ContactEdit = {
}else if (number > 3) {
replacement = Diaspora.widgets.i18n.t('aspect_dropdown.toggle.many', { count: number.toString()})
}else {
- //the above one are a totalogy, but I want to have them here once for once we figure out a neat way i18n them
+ //the above one are a tautology, but I want to have them here once for once we figure out a neat way i18n them
replacement = Diaspora.widgets.i18n.t('aspect_dropdown.toggle.other', { count: number.toString()})
}
@@ -36,7 +33,6 @@ var ContactEdit = {
toggleCheckbox:
function(check){
- check.toggleClass('hidden');
check.parent('li').toggleClass('selected');
},
@@ -44,10 +40,20 @@ var ContactEdit = {
var button = li.find('.button');
if(button.hasClass('disabled') || li.hasClass('newItem')){ return; }
- var checkbox = li.find('img.check');
- ContactEdit.toggleCheckbox(checkbox);
+ var checkbox = li.find('img.check'),
+ selected = li.hasClass("selected"),
+ routedId = selected ? "/42" : "";
- $.fn.callRemote.apply(button);
+ $.post("/aspect_memberships" + routedId + ".json", {
+ "aspect_id": li.data("aspect_id"),
+ "person_id": li.parent().data("person_id"),
+ "_method": (selected) ? "DELETE" : "POST"
+ }, function(aspectMembership) {
+ ContactEdit.toggleCheckbox(checkbox);
+ ContactEdit.updateNumber(li.closest(".dropdown_list"), li.parent().data("person_id"), aspectMembership.aspect_ids.length);
+
+ Diaspora.widgets.publish("aspectDropdown/updated", [li.parent().data("person_id"), li.parents(".dropdown").get(0).outerHTML]);
+ });
},
};
diff --git a/public/javascripts/diaspora.js b/public/javascripts/diaspora.js
index 8543f8afc..55a4bf509 100644
--- a/public/javascripts/diaspora.js
+++ b/public/javascripts/diaspora.js
@@ -39,11 +39,14 @@
};
Diaspora.WidgetCollection.prototype.subscribe = function(id, callback, context) {
- this.eventsContainer.bind(id, $.proxy(callback, context));
+ var ids = id.split(" ");
+ for(var id in ids) {
+ this.eventsContainer.bind(ids[id], $.proxy(callback, context));
+ }
};
- Diaspora.WidgetCollection.prototype.publish = function(id) {
- this.eventsContainer.trigger(id);
+ Diaspora.WidgetCollection.prototype.publish = function(id, args) {
+ this.eventsContainer.trigger(id, args);
};
Diaspora.widgets = new Diaspora.WidgetCollection();
diff --git a/public/javascripts/widgets/hovercard.js b/public/javascripts/widgets/hovercard.js
new file mode 100644
index 000000000..ab171a275
--- /dev/null
+++ b/public/javascripts/widgets/hovercard.js
@@ -0,0 +1,136 @@
+(function() {
+ var HoverCard = function() {
+ var self = this;
+
+ self.jXHRs = [];
+
+ this.start = function() {
+ self.personCache = new this.Cache();
+ self.dropdownCache = new this.Cache();
+
+ var card = $("#hovercard");
+ self.hoverCard = {
+ tip: $("#hovercard_container"),
+ dropdownContainer: $("#hovercard_dropdown_container"),
+ offset: {
+ left: -10,
+ top: 13
+ },
+ personLink: card.find("a.person"),
+ avatar: card.find(".avatar"),
+ dropdown: card.find(".dropdown_list"),
+ hashtags: card.find(".hashtags"),
+ };
+
+ $(document.body).delegate("a.hovercardable:not(.self)", "hover", self.handleHoverEvent);
+ self.hoverCard.tip.hover(self.hoverCardHover, self.clearTimeout);
+
+ Diaspora.widgets.subscribe("aspectDropdown/updated aspectDropdown/blurred", function(evt, personId, dropdownHtml) {
+ self.dropdownCache.cache["/people/" + personId + "/aspect_membership_button"] = $(dropdownHtml).removeClass("active").get(0).outerHTML;
+ });
+ };
+
+ this.handleHoverEvent = function(evt) {
+ self.target = $(evt.target);
+
+ if(evt.type === "mouseenter") {
+ self.startHover();
+ }
+ else {
+ self.clearTimeout(evt);
+ }
+ };
+
+ this.startHover = function(evt) {
+ if(!self.hoverCardTimeout) {
+ self.clearTimeout(false);
+ }
+ self.timeout = setTimeout(self.showHoverCard, 600);
+ };
+
+ this.showHoverCard = function() {
+ self.hoverCard.tip.hide();
+ self.hoverCard.tip.prependTo(self.target.parent());
+
+ self.personCache.get(self.target.attr("href") + ".json", function(person) {
+ self.populateHovercard(person);
+ });
+ };
+
+ this.populateHovercard = function(person) {
+ var position = self.target.position();
+ self.hoverCard.tip.css({
+ left: position.left + self.hoverCard.offset.left,
+ top: position.top + self.hoverCard.offset.top
+ });
+
+ self.hoverCard.avatar.attr("src", person.avatar);
+ self.hoverCard.personLink.attr("href", person.url);
+ self.hoverCard.personLink.text(person.name);
+ self.hoverCard.dropdown.attr("data-person-id", person.id);
+
+ $.each(person.hashtags, function(index, hashtag) {
+ self.hoverCard.hashtags.append(
+ $("", {
+ href: "/tags/" + hashtag.substring(1)
+ }).text(hashtag)
+ );
+ });
+
+ self.dropdownCache.get(self.target.attr("href") + "/aspect_membership_button", function(dropdown) {
+ self.hoverCard.dropdownContainer.html(dropdown);
+ self.hoverCard.tip.fadeIn(140);
+ });
+ };
+
+ this.clearTimeout = function(delayed) {
+ self.personCache.clearjXHRs();
+ self.dropdownCache.clearjXHRs();
+
+ function callback() {
+ self.timeout = clearTimeout(self.timeout);
+ self.hoverCard.tip.hide();
+ self.hoverCard.dropdownContainer.html("");
+ };
+
+ if((typeof delayed === "boolean" && delayed) || (typeof delayed === "object" && delayed.type === "mouseleave")) {
+ self.hoverCardTimeout = setTimeout(callback, 200);
+ }
+ else {
+ callback();
+ }
+ };
+
+ this.hoverCardHover = function() {
+ self.hoverCardTimeout = clearTimeout(self.hoverCardTimeout);
+ };
+
+ this.Cache = function() {
+ var self = this;
+ this.cache = {};
+ this.jXHRs = [];
+
+ this.get = function(key, callback) {
+ if(typeof self.cache[key] === "undefined") {
+ self.jXHRs.push($.get(key, function(response) {
+ self.cache[key] = response;
+ callback(response);
+ self.jXHRs.shift();
+ }));
+ }
+ else {
+ callback(self.cache[key]);
+ }
+ };
+
+ this.clearjXHRs = function() {
+ $.each(self.jXHRs, function(index, jXHR) {
+ jXHR.abort();
+ });
+ self.jXHRs = [];
+ };
+ };
+ };
+
+ Diaspora.widgets.add("hoverCard", HoverCard);
+})();
diff --git a/public/stylesheets/sass/application.sass b/public/stylesheets/sass/application.sass
index c88e9f122..db819df67 100644
--- a/public/stylesheets/sass/application.sass
+++ b/public/stylesheets/sass/application.sass
@@ -3066,6 +3066,62 @@ ul.left_nav
:margin
:top 30px
-.tags_people
- .dropdown
- :display none
+#hovercard
+ @include border-radius(2px)
+ @include box-shadow(0,0,5px,#666)
+
+ :position relative
+
+ .avatar
+ :position relative
+ :height 70px
+ :width 70px
+ :margin
+ :right 10px
+
+ :background
+ :color $background
+
+ :padding 5px
+ :bottom 55px
+
+ :border 1px solid #999
+
+ :width 220px
+
+ .hovercard_footer
+ :position absolute
+ :bottom 0
+ :left 0
+ :background
+ :color #eee
+ :width 100%
+
+ :height 20px
+ :min-height 20px
+
+ :font
+ :size smaller
+
+ :border
+ :top 1px solid #ccc
+
+ .footer_container
+ :padding 2px 5px
+
+ .hashtags
+ :overflow hidden
+ :white-space nowrap
+ :text-overflow ellipsis
+
+ a
+ :color #999
+ :margin
+ :right 4px
+
+#hovercard_container
+ :padding 10px
+ :top 5px
+ :position absolute
+ :display none
+ :z-index 10
diff --git a/public/stylesheets/sass/ui.sass b/public/stylesheets/sass/ui.sass
index f870ceeae..c811f7da6 100644
--- a/public/stylesheets/sass/ui.sass
+++ b/public/stylesheets/sass/ui.sass
@@ -150,7 +150,10 @@
&:hover
:text-decoration none
-
+
+ &:not(.selected)
+ .check
+ :display none
&.hang_right
.wrapper
@@ -170,7 +173,7 @@
@include border-radius(3px, 3px, 0, 0)
:border 1px solid #444
:bottom none
-
+
.selected
:font-weight bold
diff --git a/spec/controllers/aspect_memberships_controller_spec.rb b/spec/controllers/aspect_memberships_controller_spec.rb
index 7772de8f1..7d4cb8c5c 100644
--- a/spec/controllers/aspect_memberships_controller_spec.rb
+++ b/spec/controllers/aspect_memberships_controller_spec.rb
@@ -40,7 +40,6 @@ describe AspectMembershipsController do
}.should change{
alice.contact_for(bob.person).aspect_memberships.count
}.by(1)
-
end
it 'creates a contact' do
@@ -62,8 +61,19 @@ describe AspectMembershipsController do
:aspect_id => @aspect0.id
flash[:error].should_not be_empty
end
- end
+ context 'json' do
+ it 'returns a list of aspect ids for the person' do
+ post :create,
+ :format => 'json',
+ :person_id => @person.id,
+ :aspect_id => @aspect0.id
+
+ contact = @controller.current_user.contact_for(@person)
+ response.body.should == contact.aspect_memberships.first.to_json
+ end
+ end
+ end
describe "#destroy" do
it 'removes contacts from an aspect' do