Merge pull request #5153 from svbergerem/redesign-contacts-page

Redesign contacts page
This commit is contained in:
Jonne Haß 2014-08-27 10:01:41 +02:00
commit f1ab434b18
48 changed files with 650 additions and 1000 deletions

View file

@ -16,6 +16,7 @@ Diaspora::Application.config.secret_key_base = '*************...'
The default for including jQuery from a CDN has changed. If you want to continue to include it from a CDN, please explicitly set the `jquery_cdn` setting to `true` in diaspora.yml.
## Refactor
* Redesign contacts page [#5153](https://github.com/diaspora/diaspora/pull/5153)
## Bug fixes
* orca cannot see 'Add Contact' button [#5158](https://github.com/diaspora/diaspora/pull/5158)
@ -107,7 +108,7 @@ Read more in [#4249](https://github.com/diaspora/diaspora/pull/4249) and [#4883]
* Reorder and reword items on user settings page [#4912](https://github.com/diaspora/diaspora/pull/4912)
* SPV: Improve padding and interaction counts [#4426](https://github.com/diaspora/diaspora/pull/4426)
* Remove auto 'mark as read' for notifications [#4810](https://github.com/diaspora/diaspora/pull/4810)
* Improve set read/unread in notifications dropdown [#4869](https://github.com/diaspora/diaspora/pull/4869)
* Improve set read/unread in notifications dropdown [#4869](https://github.com/diaspora/diaspora/pull/4869)
* Refactor publisher: trigger events for certain actions, introduce 'disabled' state [#4932](https://github.com/diaspora/diaspora/pull/4932)
## Bug fixes

View file

@ -1,6 +1,7 @@
app.Router = Backbone.Router.extend({
routes: {
"help": "help",
"contacts": "contacts",
//new hotness
"posts/:id": "singlePost",
@ -23,7 +24,7 @@ app.Router = Backbone.Router.extend({
"people/:id": "stream",
"u/:name": "stream"
},
initialize: function() {
// To support encoded linefeeds (%0A) we need to specify
// our own internal router.route call with the correct regexp.
@ -37,6 +38,10 @@ app.Router = Backbone.Router.extend({
app.help.render();
},
contacts: function() {
app.contacts = new app.views.Contacts();
},
singlePost : function(id) {
this.renderPage(function(){ return new app.pages.SinglePostViewer({ id: id })});
},

View file

@ -0,0 +1,112 @@
app.views.Contacts = Backbone.View.extend({
el: "#contacts_container",
events: {
"click #contacts_visibility_toggle" : "toggleContactVisibility",
"click #change_aspect_name" : "showAspectNameForm",
"click .contact_remove-from-aspect" : "removeContactFromAspect",
"click .contact_add-to-aspect" : "addContactToAspect",
"keyup #contact_list_search" : "searchContactList"
},
initialize: function() {
this.visibility_toggle = $("#contacts_visibility_toggle .entypo");
$("#people_stream.contacts .header .entypo").tooltip({ 'placement': 'bottom'});
$(".contact_remove-from-aspect").tooltip();
$(".contact_add-to-aspect").tooltip();
$(document).on('ajax:success', 'form.edit_aspect', this.updateAspectName);
},
toggleContactVisibility: function() {
if (this.visibility_toggle.hasClass("lock-open")) {
this.visibility_toggle.removeClass("lock-open")
.addClass("lock")
.tooltip("destroy")
.removeAttr("data-original-title")
.attr("title", Diaspora.I18n.t("contacts.aspect_list_is_not_visible"))
.tooltip({'placement': 'bottom'});
}
else {
this.visibility_toggle.removeClass("lock")
.addClass("lock-open")
.tooltip("destroy")
.removeAttr("data-original-title")
.attr("title", Diaspora.I18n.t("contacts.aspect_list_is_visible"))
.tooltip({'placement': 'bottom'});
}
},
showAspectNameForm: function() {
$(".header > h3").hide();
$(".header > #aspect_name_form").show();
},
updateAspectName: function(evt,data,status,xhr){
$(".header #aspect_name").text(data['name']);
$("#aspect_nav [data-aspect-id='"+data['id']+"'] .name").text(data['name']);
$(".header > #aspect_name_form").hide();
$(".header > h3").show();
},
addContactToAspect: function(e){
var contact = $(e.currentTarget);
var aspect_membership = new app.models.AspectMembership({
'person_id': contact.attr('data-person_id'),
'aspect_id': contact.attr('data-aspect_id')
});
aspect_membership.save({},{
success: function(model,response){
contact.attr('data-membership_id',model.id)
.tooltip('destroy')
.removeAttr('data-original-title')
.removeClass("contact_add-to-aspect").removeClass("circled-plus")
.addClass("contact_remove-from-aspect").addClass("circled-cross")
.attr('title', Diaspora.I18n.t('contacts.remove_contact'))
.tooltip()
.closest('.stream_element').addClass('in_aspect');
},
error: function(model,response){
var msg = Diaspora.I18n.t('contacts.error_add', { 'name':contact.closest('.stream_element').find('.name').text() });
Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg });
}
});
},
removeContactFromAspect: function(e){
var contact = $(e.currentTarget);
var aspect_membership = new app.models.AspectMembership({
'id': contact.attr('data-membership_id')
});
aspect_membership.destroy({
success: function(model,response){
contact.removeAttr('data-membership_id')
.tooltip('destroy')
.removeAttr('data-original-title')
.removeClass("contact_remove-from-aspect").removeClass("circled-cross")
.addClass("contact_add-to-aspect").addClass("circled-plus")
.attr('title', Diaspora.I18n.t('contacts.add_contact'))
.tooltip()
.closest('.stream_element').removeClass('in_aspect');
},
error: function(model,response){
var msg = Diaspora.I18n.t('contacts.error_remove', { 'name':contact.closest('.stream_element').find('.name').text() });
Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg });
}
});
},
searchContactList: function(e) {
var query = new RegExp($(e.target).val(),'i');
$("#people_stream.stream.contacts .stream_element").each(function(){
if($(this).find(".name").text().match(query)){
$(this).show();
} else {
$(this).hide();
}
});
}
});

View file

@ -1,120 +0,0 @@
/* Copyright (c) 2010-2011, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
function toggleAspectTitle(){
$("#aspect_name_title").toggleClass('hidden');
$("#aspect_name_edit").toggleClass('hidden');
}
function updateAspectName(new_name) {
$('#aspect_name_title .name').text(new_name);
$('input#aspect_name').val(new_name);
}
function updatePageAspectName( an_id, new_name) {
$('ul#aspect_nav [data-aspect-id="'+an_id+'"] .selectable').text(new_name);
}
$(document).ready(function() {
$(document).on('click', '#rename_aspect_link', function(){
toggleAspectTitle();
});
$(document).on('ajax:success', 'form.edit_aspect', function(evt, data, status, xhr) {
updateAspectName(data['name']);
updatePageAspectName( data['id'], data['name'] );
toggleAspectTitle();
});
});
/**
* TEMPORARY SOLUTION
* TODO remove me, when the contacts section is done with Backbone.js ...
* (this is about as much covered by tests as the old code ... not at all)
*
* see also 'contact-edit.js'
*/
app.tmp || (app.tmp = {});
// on the contacts page, viewing the facebox for single aspect
app.tmp.ContactAspectsBox = function() {
$('body').on('click', '#aspect_edit_pane a.add.btn', _.bind(this.addToAspect, this));
$('body').on('click', '#aspect_edit_pane a.added.btn', _.bind(this.removeFromAspect, this));
};
_.extend(app.tmp.ContactAspectsBox.prototype, {
addToAspect: function(evt) {
var el = $(evt.currentTarget);
var aspect_membership = new app.models.AspectMembership({
'person_id': el.data('person_id'),
'aspect_id': el.data('aspect_id')
});
aspect_membership.on('sync', this._successSaveCb, this);
aspect_membership.on('error', function() {
this._displayError('aspect_dropdown.error', el);
}, this);
aspect_membership.save();
return false;
},
_successSaveCb: function(aspect_membership) {
var membership_id = aspect_membership.get('id');
var person_id = aspect_membership.get('person_id');
var el = $('li.contact').find('a.add[data-person_id="'+person_id+'"]');
el.removeClass('add')
.addClass('added')
.attr('data-membership_id', membership_id) // just to be sure...
.data('membership_id', membership_id);
el.find('div').removeClass('icons-monotone_plus_add_round')
.addClass('icons-monotone_check_yes');
},
removeFromAspect: function(evt) {
var el = $(evt.currentTarget);
var aspect_membership = new app.models.AspectMembership({
'id': el.data('membership_id')
});
aspect_membership.on('sync', this._successDestroyCb, this);
aspect_membership.on('error', function(aspect_membership) {
this._displayError('aspect_dropdown.error_remove', el);
}, this);
aspect_membership.destroy();
return false;
},
_successDestroyCb: function(aspect_membership) {
var membership_id = aspect_membership.get('id');
var el = $('li.contact').find('a.added[data-membership_id="'+membership_id+'"]');
el.removeClass('added')
.addClass('add')
.removeAttr('data-membership_id')
.removeData('membership_id');
el.find('div').removeClass('icons-monotone_check_yes')
.addClass('icons-monotone_plus_add_round');
},
_displayError: function(msg_id, contact_el) {
var name = $('li.contact')
.has(contact_el)
.find('h4.name')
.text();
var msg = Diaspora.I18n.t(msg_id, { 'name': name });
Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg });
}
});
$(function() {
var contact_aspects_box = new app.tmp.ContactAspectsBox();
});

View file

@ -1,58 +0,0 @@
// Copyright (c) 2010-2011, Diaspora Inc. This file is
// licensed under the Affero General Public License version 3 or later. See
// the COPYRIGHT file.
/**
* TEMPORARY SOLUTION
* TODO remove me, when the contacts section is done with Backbone.js ...
* (this is about as much covered by tests as the old code ... not at all)
*
* see also 'aspect-edit-pane.js'
*/
app.tmp || (app.tmp = {});
// on the contacts page, viewing the list of people in a single aspect
app.tmp.ContactAspects = function() {
$('#people_stream').on('click', '.contact_remove-from-aspect', _.bind(this.removeFromAspect, this));
};
_.extend(app.tmp.ContactAspects.prototype, {
removeFromAspect: function(evt) {
evt.stopImmediatePropagation();
evt.preventDefault();
var el = $(evt.currentTarget);
var id = el.data('membership_id');
var aspect_membership = new app.models.AspectMembership({'id':id});
aspect_membership.on('sync', this._successDestroyCb, this);
aspect_membership.on('error', function(aspect_membership) {
this._displayError('aspect_dropdown.error_remove', aspect_membership.get('id'));
}, this);
aspect_membership.destroy();
return false;
},
_successDestroyCb: function(aspect_membership) {
var membership_id = aspect_membership.get('id');
$('.stream_element').has('[data-membership_id="'+membership_id+'"]')
.fadeOut(300, function() { $(this).remove() });
},
_displayError: function(msg_id, membership_id) {
var name = $('.stream_element')
.has('[data-membership_id="'+membership_id+'"]')
.find('div.bd > a')
.text();
var msg = Diaspora.I18n.t(msg_id, { 'name': name });
Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg });
}
});
$(function() {
var contact_aspects = new app.tmp.ContactAspects();
});

View file

@ -1,40 +1,4 @@
/* Copyright (c) 2010-2011, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
var List = {
initialize: function() {
$(document).on("keyup", ".contact_list_search", function(e) {
var search = $(this);
var list = $(".contacts", ".searchable");
var query = new RegExp(search.val(),'i');
$("> .contact", list).each( function(idx, element) {
element = $(element);
if( !element.find(".name").text().match(query) ) {
element.addClass('hidden');
} else {
element.removeClass('hidden');
}
});
});
},
disconnectUser: function(contact_id){
$.ajax({
url: "/contacts/" + contact_id,
type: "DELETE",
success: function(){
if( $('.searchable').length == 1){
$('.searchable .contact[data-contact_id='+contact_id+']').fadeOut(200);
} else if($('#aspects_list').length == 1) {
$.facebox.close();
};
}
});
},
runDelayedSearch: function( searchTerm ) {
$.getJSON('/people/refresh_search',
{ q: searchTerm },
@ -54,29 +18,4 @@ var List = {
startSearchDelay: function (theSearch) {
setTimeout( "List.runDelayedSearch('" + theSearch + "')", 10000);
}
};
$(document).ready(function() {
$('.added').bind('ajax:loading', function() {
var $this = $(this);
$this.addClass('disabled');
$this.fadeTo(200,0.4);
});
$('.added').bind('hover',
function() {
var $this = $(this)
$this.addClass("remove");
$this.children("img").attr("src","/images/icons/monotone_close_exit_delete.png");
},
function() {
var $this = $(this)
$this.removeClass("remove");
$this.children("img").attr("src","/images/icons/monotone_check_yes.png");
});
List.initialize();
});

View file

@ -3,5 +3,4 @@
* the COPYRIGHT file.
*/
//= require aspect-edit-pane
//= require fileuploader-custom

View file

@ -7,4 +7,5 @@
//= require mobile
//= require profile
//= require people
//= require contact-list
//= require sinon

View file

@ -32,8 +32,6 @@
//= require_tree ./widgets
//= require view
//= require aspects-dropdown
//= require contact-edit
//= require contact-list
//= require mentions
//= require bootstrap-tooltip
//= require bootstrap-popover

View file

@ -2,7 +2,6 @@
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
//= require aspect-edit-pane
//= require fileuploader-custom
//= require jquery.autoSuggest.custom

View file

@ -1,5 +1,5 @@
.aspect_dropdown {
li {
.status_indicator {
width: 19px;
@ -38,224 +38,6 @@
display: inline-block;
}
/* -- Used in contacts/index.html.haml -- */
#aspect_controls {
@include border-radius(2px);
background-color: #fafafa;
border: 1px solid #ccc;
padding: 10px;
min-height: 23px;
.button, .button_to {
margin-right: 5px;
}
.button_to { display: inline-block; }
.contact_visibility_padlock,
.modify_aspect,
.icons-mail,
.icons-monotone_close_exit_delete {
margin-left: 4px;
margin-bottom: -2px;
display: inline-block;
}
.icons-mail {
width: 16px;
height: 13px;
margin-bottom: -1px;
}
}
#aspect_nav {
list-style-type: none;
.icons-check_yes_ok {
height:18px;
width:18px;
background: url('icons/check_yes_ok.png') no-repeat;
float: left;
visibility: hidden;
&.selected {
visibility: visible;
}
&.selected + a {
color: #333333;
}
}
}
.contact_visibility_padlock {
height: 16px;
width: 16px;
/* -- To remove when the facebox will be deleted -- */
display: inline-block;
margin-left: 4px;
margin-bottom: -2px;
}
/* -- Used in aspects/edit.haml and in contacts/sharing.haml -- */
#aspect_edit_controls { margin-top: 8px; }
#aspect_edit_pane {
width: 772px;
.contact_list_controls {
height: 30px;
margin-bottom: 5px;
}
#aspect_name_title {
margin: 0px 0px 5px 0px;
}
#contact_list_search {
width: 200px;
margin-bottom: 2px;
margin-top: 0px;
float: right;
}
.contact_list,
.aspect_list {
@include border-radius(3px);
height: 300px;
max-height: 300px;
overflow-y: auto;
overflow-x: hidden;
border: 1px solid #bbb;
background-color: rgb(252,252,252);
.avatar {
float: left;
height: 50px;
width: 50px;
margin-right: 10px !important;
}
.button.share {
padding-right: 20px;
.right {
right: 8px;
top: 3px;
}
}
.icons-monotone_plus_add_round,
.icons-monotone_check_yes {
height: 20px;
width: 20px;
}
& > .contacts {
margin: 0px;
padding: 5px;
padding-right: 0px;
& > .contact {
@include border-radius(3px);
@include box-shadow(0, 1px, 5px, #ccc);
position: relative;
display: inline-block;
float: left;
height: 50px;
width: 170px;
overflow-x: hidden;
overflow-y: hidden;
margin-bottom: 5px;
margin-right: 5px;
padding: 5px;
background-color: #fff;
border: 1px solid $border-dark-grey;
&.hidden { display: none; }
&.remote_friend { width: 285px; }
& > .name {
font-size: 16px;
margin: 0px 0px 5px 0px;
white-space: nowrap;
}
a.btn {
@include box-shadow(0,0,0);
border-bottom: none;
min-width: 90px;
float: right;
padding: 2px 0px;
margin-top: 5px;
margin-right: -5px;
&.added {
@include linear-gradient(rgb(158,255,153), rgb(92,199,86));
background-color: rgb(92,199,86);
}
&.added.remove {
@include linear-gradient(rgb(255,153,153), rgb(199,86,86));
&:active { @include linear-gradient(rgb(199,119,119), rgb(130,55,55)); }
}
}
}
}
}
.aspect_list {
height: 300px;
max-height: 300px;
.name { left: 1em; }
ul > li {
padding: 1em;
height: 1em;
.right {
top: 2px;
right: 1em;
}
}
}
.person_tiles .tile {
@include border-radius(2px);
padding: 6px;
padding-left: 65px;
margin: 3px;
margin-left: 0px;
display: inline-block;
width: 126px;
height: 50px;
border: 1px solid #eee;
position: relative;
img {
position: absolute;
left: 6px;
}
}
&.larger { width: 650px; }
.bottom_submit_section {
margin-top: 12px;
form {
display: inline-block;
margin: 0px;
}
.creation {
float: right;
}
}
}
#new_aspect {
#aspect_contacts_visible.checkbox {
margin: 0px;

View file

@ -1,121 +1,64 @@
#section_header {
h3 {
border-bottom: 1px solid $border-grey;
}
}
#people_stream {
.bd {
font-size: 13px;
line-height: 19.5px;
}
.btn-group {
margin-top: 5px;
.caret {
margin-top: 8px;
}
.text {
text-shadow: none;
}
}
.info {
font-size: 11px;
line-height: 16.5px;
}
.stream_element {
border-bottom: 1px solid $border-grey;
.icons-monotone_close_exit_delete {
margin-top: 8px;
}
.media {
margin: 10px;
}
}
}
#people_stream.contacts {
.stream_element {
padding: 10px;
min-height: 30px;
.media { overflow: visible; }
.float-right {
top: 16px;
a {
@include opacity(1);
&:hover {
@include opacity(0.6);
#contacts_container {
#people_stream.contacts {
.header {
border-bottom: 1px solid $border-grey;
margin-top: 10px;
min-height: 53px;
#change_aspect_name { cursor: pointer; }
#aspect_name_form {
display: none;
form { margin: 0px; }
input {
margin-bottom: 0px;
margin-top: 10px;
}
.btn { margin-top: 10px; }
}
#contact_list_search {
margin: 6px 30px 0 0;
width: 150px;
&:focus { width: 250px; }
}
& > a, #aspect_controls > a {
text-decoration: none;
margin-right: 25px;
}
.entypo.contacts-header-icon {
font-size: 24.5px;
line-height: 40px;
color: lighten($black,75%);
&:hover { color: $black; }
}
#suggest_member.btn { margin-top: 8px; }
}
.info { margin-top: -2px; }
}
.stream_element {
.contact_remove-from-aspect, .contact_add-to-aspect {
text-decoration: none;
cursor: pointer;
font-size: 20px;
line-height: 50px;
margin: 10px;
color: lighten($black,75%);
&:hover { color: $black; }
}
&.in_aspect {
border-left: 3px solid $green;
background-color: lighten($green,35%);
}
&:not(.in_aspect) { border-left: 3px solid $white; }
}
.avatar {
height: 30px;
width: 30px;
}
.icons-monotone_close_exit_delete {
height: 14px;
width: 14px;
}
.no_contacts {
text-align: center;
margin-top: 50px;
.no_contacts {
text-align: center;
margin-top: 50px;
}
}
}
#community_spotlight {
.avatar {
width: 140px;
height: 140px;
}
.user_card {
@include border-radius(3px);
@include box-shadow(0,1px,5px,#ccc);
border: 1px solid #ccc;
display: inline-block;
margin-bottom: 15px;
margin-right: 10px;
min-height: 220px;
padding: 10px 10px 30px 10px;
position: relative;
vertical-align: top;
width: 140px;
h4 {
margin-bottom: 0px;
padding-bottom: 2px;
}
.add_user_to_aspect {
bottom: 10px;
right: 10px;
position: absolute;
}
.dropdown {
width: 100%;
}
.tags {
color: $text-grey;
}
}
#aspect_nav {
li.aspect > a { padding-left: 30px; }
li.new_aspect > a { padding-left: 30px; }
}
#no_contacts {

View file

@ -9,8 +9,7 @@
@import 'sprites';
@import 'new_styles/base';
@import 'new_styles/viewer_nav';
@import 'buttons';
@import 'new_styles/buttons';
/* font overrides */
@import 'new_styles/typography';
@ -22,6 +21,9 @@
@import 'new_styles/forms';
/* navs */
@import 'new_styles/navs';
/* profile and settings pages */
@import 'new_styles/settings';

View file

@ -86,7 +86,7 @@ form.block-form {
}
}
textarea, input[type=text], input[type=password] {
textarea, input[type=text], input[type=password], input[type=search] {
&:focus, &:invalid:focus {
border: 1px solid $border-dark-grey;
outline: none;

View file

@ -0,0 +1,14 @@
.nav.nav-tabs{
li > a {
color: $text-dark-grey;
.entypo {
color: $text-dark-grey;
margin-right: 5px;
}
}
li.active > a {
background-color: $background-grey;
color: $black;
.entypo { color: $black; }
}
}

View file

@ -1,18 +0,0 @@
@import '../mixins';
body.idle {
#post-nav {
@include opacity(0);
.nav-arrow{
&.right {
margin-right: -40px;
}
&.left {
margin-left: -40px;
}
}
}
}

View file

@ -1,18 +1,8 @@
#notifications_container {
.nav.nav-tabs{
li > a {
color: $text-dark-grey;
.entypo {
color: $text-dark-grey;
margin-right: 5px;
}
.badge.badge-default { display: none; }
}
li.active > a {
background-color: $background-grey;
color: $black;
.entypo { color: $black; }
}
}
.stream {

View file

@ -1,17 +1,29 @@
#people_search {
#people_stream {
.stream_element {
border-bottom: 1px solid $border-grey;
padding-right: 10px;
.avatar {
width: 50px;
height: 50px;
}
.info { font-size: 11px; }
}
}
#search_title {
.term { font-weight: 700; }
small { margin-left: 15px; }
}
}
#people_stream {
.media, .media-body {
overflow: visible;
}
.stream_element.media {
border-bottom: 1px solid $border-grey;
padding: 10px;
margin: 0px;
font-size: 13px;
line-height: 16px;
min-height: 50px;
.avatar {
width: 50px;
height: 50px;
}
.btn-group.aspect_membership_dropdown { margin: 12px 0; }
.thats_you {
line-height: 50px;
margin-right: 10px;
}
.info { font-size: 11px; }
}
}

View file

@ -67,28 +67,6 @@ class AspectsController < ApplicationController
end
end
def edit
@aspect = current_user.aspects.where(:id => params[:id]).includes(:contacts => {:person => :profile}).first
@contacts_in_aspect = @aspect.contacts.includes(:aspect_memberships, :person => :profile).to_a.sort_by { |c| c.person.name }
c = Contact.arel_table
if @contacts_in_aspect.empty?
@contacts_not_in_aspect = current_user.contacts.includes(:aspect_memberships, :person => :profile).to_a.sort_by { |c| c.person.name }
else
@contacts_not_in_aspect = current_user.contacts.where(c[:id].not_in(@contacts_in_aspect.map(&:id))).includes(:aspect_memberships, :person => :profile).to_a.sort_by { |c| c.person.name }
end
@contacts = @contacts_in_aspect + @contacts_not_in_aspect
unless @aspect
render :file => Rails.root.join('public', '404.html').to_s, :layout => false, :status => 404
else
@aspect_ids = [@aspect.id]
@aspect_contacts_count = @aspect.contacts.size
render :layout => false
end
end
def update
@aspect = current_user.aspects.where(:id => params[:id]).first
@ -109,6 +87,7 @@ class AspectsController < ApplicationController
@aspect.contacts_visible = true
end
@aspect.save
render :nothing => true
end
private

View file

@ -5,16 +5,17 @@
class ContactsController < ApplicationController
before_action :authenticate_user!
layout ->(c) { request.format == :mobile ? "application" : "with_header_with_footer" }
use_bootstrap_for :index, :spotlight
def index
respond_to do |format|
# Used for normal requests to contacts#index and subsequent infinite scroll calls
# Used for normal requests to contacts#index
format.html { set_up_contacts }
# Used by the mobile site
format.mobile { set_up_contacts }
format.mobile { set_up_contacts_mobile }
# Used to populate mentions in the publisher
format.json {
@ -25,11 +26,6 @@ class ContactsController < ApplicationController
end
end
def sharing
@contacts = current_user.contacts.sharing.includes(:aspect_memberships)
render :layout => false
end
def spotlight
@spotlight = true
@people = Person.community_spotlight
@ -38,6 +34,41 @@ class ContactsController < ApplicationController
private
def set_up_contacts
type = params[:set].presence
type ||= "by_aspect" if params[:a_id].present?
type ||= "receiving"
@contacts = contacts_by_type(type)
@contacts_size = @contacts.length
end
def contacts_by_type(type)
contacts = case type
when "all"
[current_user.contacts]
when "only_sharing"
[current_user.contacts.only_sharing]
when "receiving"
[current_user.contacts.receiving]
when "by_aspect"
@aspect = current_user.aspects.find(params[:a_id])
@contacts_in_aspect = @aspect.contacts
@contacts_not_in_aspect = current_user.contacts.where.not(contacts: {id: @contacts_in_aspect.pluck(:id) })
[@contacts_in_aspect, @contacts_not_in_aspect].map {|relation|
relation.includes(:aspect_memberships)
}
else
raise ArgumentError, "unknown type #{type}"
end
contacts.map {|relation|
relation.includes(:person => :profile).to_a.tap {|contacts|
contacts.sort_by! {|contact| contact.person.name }
}
}.inject(:+)
end
def set_up_contacts_mobile
@contacts = case params[:set]
when "only_sharing"
current_user.contacts.only_sharing

View file

@ -3,33 +3,32 @@ module ContactsHelper
membership = contact.aspect_memberships.where(:aspect_id => @aspect.id).first unless @aspect.nil?
if membership
link_to(content_tag(:div, nil, :class => 'icons-monotone_close_exit_delete'),
{ :controller => "aspect_memberships",
:action => 'destroy',
:id => membership.id
},
:title => t('contacts.index.remove_person_from_aspect', :person_name => contact.person_first_name, :aspect_name => @aspect.name),
:class => 'contact_remove-from-aspect',
:method => 'delete',
'data-membership_id' => membership.id
)
content_tag(:i, nil, :class => 'entypo circled-cross contact_remove-from-aspect',
:title => t('contacts.index.remove_contact'),
'data-aspect_id' => @aspect.id,
'data-person_id' => contact.person_id,
'data-membership_id' => membership.id )
else
elsif @aspect.nil?
render :partial => 'people/relationship_action',
:locals => { :person => contact.person,
:contact => contact,
:current_user => current_user }
else
content_tag(:i, nil, :class => 'entypo circled-plus contact_add-to-aspect',
:title => t('contacts.index.add_contact'),
'data-aspect_id' => @aspect.id,
'data-person_id' => contact.person_id )
end
end
def start_a_conversation_link(aspect, contacts_size)
suggested_limit = 16
conv_opts = { class: "btn conversation_button", rel: "facebox"}
conv_opts = { class: "conversation_button", rel: "facebox"}
conv_opts[:title] = t('.many_people_are_you_sure', suggested_limit: suggested_limit) if contacts_size > suggested_limit
link_to new_conversation_path(aspect_id: aspect.id, name: aspect.name), conv_opts do
concat t('.start_a_conversation')
concat content_tag(:span, nil, class: "icons-mail")
content_tag(:i, nil, :class => 'entypo mail contacts-header-icon', :title => t('contacts.index.start_a_conversation'))
end
end
end

View file

@ -1,24 +0,0 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
#aspect_edit_pane
#facebox_header{:data=>{:guid=>@aspect.id}}
%h3#aspect_name_title
%span.name= @aspect
%span.tiny_text
= link_to t('.rename'), '#', :id => 'rename_aspect_link'
#aspect_name_edit.hidden
= form_for @aspect, :remote => true do |aspect|
= aspect.text_field :name, :maxlength => 20
= aspect.submit t('.update'), 'data-disable-with' => t('.updating'), :class => 'btn'
- if @contacts.count > 0
= render 'shared/contact_list', :aspect => @aspect, :contacts => @contacts
.bottom_submit_section
= button_to t('delete'), @aspect, :method => "delete", :data => { :confirm => t('.confirm_remove_aspect') }, :class => 'btn delete'
= aspect_visibility_link(@aspect)
= submit_tag t('.done'), :class => 'btn creation', :rel => 'close'

View file

@ -1,15 +0,0 @@
$(document).ready(function() {
var padlockImg = $(".contact_visibility_padlock");
var linkText = $(".contact_visibility_link");
if (padlockImg.hasClass('icons-padlock-open')) {
padlockImg.removeClass('icons-padlock-open');
padlockImg.addClass('icons-padlock-closed');
linkText.attr('title', "<%= t('aspects.edit.aspect_list_is_not_visible') %>");
} else {
padlockImg.removeClass('icons-padlock-closed');
padlockImg.addClass('icons-padlock-open');
linkText.attr('title', "<%= t('aspects.edit.aspect_list_is_visible') %>");
}
});

View file

@ -1,13 +0,0 @@
.user_card
= person_image_link(person, :size => :thumb_large)
%h4
= person.name
.tags
- person.tags.each do |tag|
= link_to "##{tag}", tag_path(:name => tag.name)
.add_user_to_aspect
= render :partial => 'people/relationship_action',
:locals => { :person => person, :contact => current_user.contact_for(person),
:current_user => current_user }

View file

@ -1,36 +1,31 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
#aspect_nav
%ul.nav.nav-tabs.nav-stacked
%li.all_contacts{:class => ("active" if params["set"] == "all")}
%a{:href => contacts_path(:set => "all")}
= t('contacts.index.all_contacts')
.badge.badge-default.pull-right
= all_contacts_count
%ul#aspect_nav
%li.all_aspects{:class => ("active" if params["set"] != "all" && params["set"] != "only_sharing" && !@spotlight)}
%a.home_selector.hoverable{:href => contacts_path, :class => ("sub_selected" if params["a_id"])}
= t('contacts.index.my_contacts')
.label.pull-right
= my_contacts_count
%li.all_aspects{:class => ("active" if !params["set"] && !params["a_id"] && !@spotlight)}
%a{:href => contacts_path}
= t('contacts.index.my_contacts')
.badge.badge-default.pull-right
= my_contacts_count
%ul.sub_nav#aspects_list
- all_aspects.each do |aspect|
%li.hoverable{:data => {:aspect_id => aspect.id}, :class => ("active" if params["a_id"].to_i == aspect.id)}
.label.pull-right
- all_aspects.each do |aspect|
%li.aspect{:data => {:aspect_id => aspect.id}, :class => ("active" if params["a_id"].to_i == aspect.id)}
%a{:href => contacts_path(:a_id => aspect.id)}
.badge.badge-default.pull-right
= aspect.contacts.size
.icons-check_yes_ok{:class => ("selected" if params["a_id"].to_i == aspect.id) }
%a.selectable{:href => contacts_path(:a_id => aspect.id)}
.name
= aspect
%li.hoverable
= link_to t('aspects.aspect_listings.add_an_aspect'), new_aspect_path, :class => "selectable new_aspect", :rel => "facebox"
%li.new_aspect
%a{:href => new_aspect_path, :rel => "facebox"}
= t('aspects.aspect_listings.add_an_aspect')
%li.all_contacts{:class => ("active" if params["set"] == "all" || params["set"] == "only_sharing")}
%a.home_selector.hoverable{:href => contacts_path(:set => "all"), :class => ("sub_selected" if params["set"] == "only_sharing")}
= t('contacts.index.all_contacts')
.label.pull-right
= all_contacts_count
%ul.sub_nav
%li.hoverable{:class => ("active" if params["set"] == "only_sharing")}
.icons-check_yes_ok{:class => ("invisible" if params["set"] == "only_sharing")}
%a.selectable{:href => contacts_path(:set => "only_sharing")}
= t('contacts.index.only_sharing_with_me')
.label.pull-right
= only_sharing_count
%li.only_sharing{:class => ("active" if params["set"] == "only_sharing")}
%a{:href => contacts_path(:set => "only_sharing")}
= t('contacts.index.only_sharing_with_me')
.badge.badge-default.pull-right
= only_sharing_count

View file

@ -1,10 +1,12 @@
.stream_element{:id => contact.person_id}
.media
.pull-right
= contact_aspect_dropdown(contact)
.img
= person_image_link(contact.person, :size => :thumb_small)
.bd
= person_link(contact.person)
.info
= contact.person_diaspora_handle
- membership = contact.aspect_memberships.where(:aspect_id => @aspect.id).first unless @aspect.nil?
.media.stream_element{:id => contact.person_id, :class => ("in_aspect" if membership)}
.pull-right
= contact_aspect_dropdown(contact)
.media-object.pull-left
= person_image_link(contact.person, :size => :thumb_small)
.media-body
= person_link(contact.person, :class => 'name')
.info.diaspora_handle
= contact.person_diaspora_handle
.info.tags
= Diaspora::Taggable.format_tags(contact.person.profile.tag_string)

View file

@ -0,0 +1,36 @@
.header
- if @aspect
#aspect_controls.pull-right
- if @contacts_size > 0 && @contacts_size < 20
= start_a_conversation_link(@aspect, @contacts_size)
= link_to aspect_toggle_contact_visibility_path(@aspect), id: "contacts_visibility_toggle", method: :put, remote: true do
-if @aspect.contacts_visible?
%i.entypo.lock-open.contacts-header-icon{:title => t('aspects.edit.aspect_list_is_visible')}
-else
%i.entypo.lock.contacts-header-icon{:title => t('aspects.edit.aspect_list_is_not_visible')}
= link_to @aspect, method: "delete", data: { confirm: t('aspects.edit.confirm_remove_aspect') }, class: 'delete', id: 'delete_aspect' do
%i.entypo.trash.contacts-header-icon{:title => t('delete')}
.pull-right
= search_field_tag :contact_search, "", id: "contact_list_search", placeholder: t('contacts.index.user_search')
%h3
%span#aspect_name
= @aspect.name
%span#change_aspect_name
%i.entypo.pencil.contacts-header-icon{:title => t('aspects.edit.rename')}
#aspect_name_form
= form_for @aspect, :remote => true do |aspect|
= aspect.text_field :name, :maxlength => 20
= aspect.submit t('aspects.edit.update'), 'data-disable-with' => t('aspects.edit.updating'), :class => "btn"
- else
%h3
- case params["set"]
- when "only_sharing"
= t('contacts.index.only_sharing_with_me')
- when "all"
= t('contacts.index.all_contacts')
- else
= t('contacts.index.my_contacts')

View file

@ -0,0 +1,7 @@
%h3
= t('contacts.index.title')
= render 'contacts/aspect_listings'
- if AppConfig.settings.community_spotlight.enable?
%hr
.text-center.spotlight
= link_to t('contacts.spotlight.community_spotlight'), community_spotlight_path, :class => "element_selector"

View file

@ -1,49 +1,25 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
- content_for :page_title do
= t('.title')
- content_for :head do
= javascript_include_tag :people
.row
.span12
#section_header
%h3
= t('.title')
.container-fluid#contacts_container
.row-fluid
.span3
= render 'contacts/sidebar'
.row
= render 'shared/contact_sidebar'
.span9
#people_stream.stream.contacts
= render 'contacts/header'
.span9
#people_stream.stream.contacts
- if @aspect
#aspect_controls
- if @contacts_size > 0 && @contacts_size < 20
= start_a_conversation_link(@aspect, @contacts_size)
= link_to edit_aspect_path(@aspect), rel: "facebox", class: "btn" do
= t('aspects.edit.manage')
%span.modify_aspect
= aspect_visibility_link(@aspect)
= link_to @aspect, method: "delete", data: { confirm: t('aspects.edit.confirm_remove_aspect') }, class: 'btn delete' do
= t('delete')
%span.icons-monotone_close_exit_delete
- if @contacts_size > 0
= render @contacts
= will_paginate @contacts
- else
.no_contacts
%h3
= t('.no_contacts')
%p
- if @aspect
!= t('.no_contacts_message_with_aspect',
:community_spotlight => link_to(t('.community_spotlight'), community_spotlight_path),
:add_to_aspect_link => link_to(t('.add_to_aspect_link', :name => @aspect.name), edit_aspect_path(@aspect), :rel => "facebox"))
- else
- if @contacts_size > 0
- @contacts.each do |contact|
= render 'contacts/contact', :contact => contact
- else
.no_contacts
%h3
= t('.no_contacts')
%p
!= t('.no_contacts_message',
:community_spotlight => link_to(t('.community_spotlight'), community_spotlight_path))

View file

@ -1,30 +0,0 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
#aspect_edit_pane
#facebox_header
.right
= t('contacts', :count =>@contacts.count)
%h3#aspect_name_title
%span.name= t('.people_sharing')
- if @contacts.count > 0
.contact_list
= search_field_tag :contact_search, "", :id => 'contact_list_search', :class => 'contact_list_search', :results => 5, :placeholder => "People sharing with you"
%ul
- for contact in @contacts
%li{:data=>{:contact_id=>contact.id}}
= person_image_tag contact.person
%h4.name
= link_to contact.person_name, contact.person
.description
= contact.person_diaspora_handle
.right
= aspect_membership_dropdown(contact, contact.person, 'right')
%br
%div{:style => "text-align:right;"}
= link_to t('aspects.edit.done'), '#', :class => 'btn', :rel => 'close'

View file

@ -1,31 +1,28 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
- content_for :page_title do
= t('contacts.spotlight.community_spotlight')
- content_for :head do
= javascript_include_tag :people
.row
.span12
#section_header
%h3
= t('contacts.index.title')
.row
= render 'shared/contact_sidebar'
.container-fluid#contacts_container
.row-fluid
.span3
= render 'contacts/sidebar'
.span9
- if AppConfig.settings.community_spotlight.suggest_email.present?
.right
= link_to t('contacts.spotlight.suggest_member'), "mailto:#{AppConfig.settings.community_spotlight.suggest_email}", :class => "button"
.span9
#people_stream.stream.contacts
.header
- if AppConfig.settings.community_spotlight.suggest_email.present?
.pull-right
= link_to t('contacts.spotlight.suggest_member'), "mailto:#{AppConfig.settings.community_spotlight.suggest_email}", :class => "btn btn-default", :id => "suggest_member"
%h3
= t('contacts.spotlight.community_spotlight')
%h3
= t('contacts.spotlight.community_spotlight')
#community_spotlight
- unless @people.blank?
- @people.each do |person|
= render 'people/person', :person => person, :contact => current_user.contact_for(person)
#community_spotlight
- unless @people.blank?
- @people.each do |person|
= render 'community_spotlight/user', :person => person
-# if @contacts_size > 0
= render @contacts

View file

@ -1,20 +1,13 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
.stream_element{:id => person.id}
%div{:style => "float:right;margin-top:2px;"}
.media.stream_element{:id => person.id}
.pull-right
= render :partial => 'people/relationship_action',
:locals => { :person => person, :contact => contact,
:current_user => current_user }
.media
.img
= person_image_link(person)
.bd
=person_link(person)
.info
= Diaspora::Taggable.format_tags(person.profile.tag_string)
.media-object.pull-left
= person_image_link(person)
.media-body
= person_link(person)
.info.diaspora_handle
= person.diaspora_handle
.info.tags
= Diaspora::Taggable.format_tags(person.profile.tag_string)

View file

@ -3,4 +3,5 @@
- contact ||= Contact.new(:person => person)
= aspect_membership_dropdown(contact, person, 'right')
-else
= t('people.person.thats_you')
%span.thats_you
= t('people.person.thats_you')

View file

@ -1,13 +0,0 @@
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
#leftNavBar.span3
= render 'contacts/aspect_listings'
- if AppConfig.settings.community_spotlight.enable?
%hr
%ul.left_nav
%li{:class => ("active" if @spotlight)}
= link_to t('contacts.spotlight.community_spotlight'), community_spotlight_path, :class => "element_selector"

View file

@ -343,8 +343,10 @@ en:
my_contacts: "My Contacts"
all_contacts: "All Contacts"
only_sharing_with_me: "Only sharing with me"
remove_person_from_aspect: "Remove %{person_name} from \"%{aspect_name}\""
add_contact: "Add contact"
remove_contact: "Remove contact"
many_people_are_you_sure: "Are you sure you want to start a private conversation with more than %{suggested_limit} contacts? Posting to this aspect may be a better way to contact them."
user_search: "User Search"
spotlight:
community_spotlight: "Community Spotlight"
suggest_member: "Suggest a member"

View file

@ -38,6 +38,14 @@ en:
years: "%d years"
wordSeparator: " "
contacts:
add_contact: "Add contact"
aspect_list_is_visible: "Contacts in this aspect are able to see each other."
aspect_list_is_not_visible: "Contacts in this aspect are not able to see each other."
remove_contact: "Remove contact"
error_add: "Couldn't add <%= name %> to the aspect :("
error_remove: "Couldn't remove <%= name %> from the aspect :("
my_activity: "My Activity"
my_stream: "Stream"
my_aspects: "My Aspects"

View file

@ -84,7 +84,7 @@ Diaspora::Application.routes.draw do
get :read_all
end
end
resources :tags, :only => [:index]
@ -146,7 +146,6 @@ Diaspora::Application.routes.draw do
resources :contacts, :except => [:update, :create] do
get :sharing, :on => :collection
end
resources :aspect_memberships, :only => [:destroy, :create]
resources :share_visibilities, :only => [:update]
@ -222,7 +221,7 @@ Diaspora::Application.routes.draw do
#Statistics
get :statistics, controller: :statistics
# Terms
if AppConfig.settings.terms.enable?
get 'terms' => 'terms#index'

View file

@ -16,7 +16,7 @@ Feature: show contacts
And I press the first "a" within ".section.contact_pictures"
Then I should see "Alice Smith"
Scenario: see contacts of a visible aspect list
Scenario: see contacts of a visible aspect list
When I am on "bob@bob.bob"'s page
And I add the person to my "Unicorns" aspect
And I sign out
@ -25,14 +25,12 @@ Feature: show contacts
And I press the first "a" within ".section.contact_pictures"
Then I should see "Bob Jones"
Scenario: don't see contacts of an invisible aspect list
Scenario: don't see contacts of an invisible aspect list
When I am on "bob@bob.bob"'s page
And I add the person to my "Unicorns" aspect
And I am on the contacts page
And I follow "Unicorns"
And I follow "Manage"
And I press the first "a.contact_visibility_link" in the modal window
And I press "Done" in the modal window
And I press the first "a#contacts_visibility_toggle"
And I sign out
And I sign in as "alice@alice.alice"

View file

@ -25,55 +25,42 @@ Feature: User manages contacts
And I have an aspect called "People"
When I am on the contacts page
And I follow "People"
And I follow "add contacts to People"
And I press "Delete" in the modal window
And I click on selector "#delete_aspect"
And I confirm the alert
Then I should be on the contacts page
And I should not see "People" within "#aspects_list"
And I should not see "People" within "#aspect_nav"
Scenario: deleting an aspect from homepage
Given I am signed in
And I have an aspect called "People"
When I am on the aspects page
And I click on "People" aspect edit icon
And I follow "Delete" within "#aspect_controls"
And I click on selector "#delete_aspect"
And I confirm the alert
Then I should be on the contacts page
And I should not see "People" within "#aspects_list"
And I should not see "People" within "#aspect_nav"
Scenario: Editing the aspect memberships of a contact from the aspect edit facebox
Scenario: Editing the aspect memberships of a contact from the contacts page
Given I am signed in
And I have 2 contacts
And I have an aspect called "Cat People"
When I am on the contacts page
And I follow "Cat People"
And I follow "add contacts to Cat People"
And I check the first contact list button
And I add the first person to the aspect
Then I should have 1 contact in "Cat People"
When I uncheck the first contact list button
When I remove the first person from the aspect
Then I should have 0 contacts in "Cat People"
Scenario: Renaming an aspect
Given I am signed in
And I have an aspect called "Cat People"
When I am on the contacts page
And I follow "Cat People"
And I follow "Manage" within "#aspect_controls"
And I follow "rename"
And I click on selector "#change_aspect_name"
And I fill in "aspect_name" with "Unicorn People"
And I press "update"
Then I should see "Unicorn People" within "#aspect_name_title"
Scenario: infinite scroll on contacts index
Given I am signed in
And I resize my window to 800x600
And I have 30 contacts
And I am on the contacts page
Then I should see 25 contacts
When I scroll down
Then I should see 30 contacts
Then I should see "Unicorn People" within "#aspect_name"
Scenario: clicking on the contacts link in the header with zero contacts directs a user to the featured users page
Given I am signed in

View file

@ -27,14 +27,13 @@ Feature: Unfollowing
Then I should not see "is sharing with you."
Scenario: stop following someone while on the aspect edit page
Scenario: stop following someone while on the contacts page
When I go to the home page
And I go to the contacts page
And I follow "Besties"
And I follow "Manage"
And I press the first ".added" within "#facebox .contact_list ul > li:first-child"
And I remove the first person from the aspect
When I follow "My Contacts"
Then I should have 0 contacts in "Besties"

View file

@ -41,17 +41,17 @@ When /^I select "([^"]*)" aspect as well$/ do |aspect_name|
step %Q(I should see "#{aspect_name}" aspect selected)
end
When /^I check the first contact list button$/ do
find(".contact_list .btn", match: :first).tap do |button|
When /^I add the first person to the aspect$/ do
find(".contact_add-to-aspect", match: :first).tap do |button|
button.click
button.parent.should have_css ".added"
button.parent.should have_css ".contact_remove-from-aspect"
end
end
When /^I uncheck the first contact list button$/ do
find(".contact_list .btn", match: :first).tap do |button|
When /^I remove the first person from the aspect$/ do
find(".contact_remove-from-aspect", match: :first).tap do |button|
button.click
button.parent.should have_css ".add"
button.parent.should have_css ".contact_add-to-aspect"
sleep 1 # The expectation above should wait for the request to finsh, but that doesn't work for some reason
end
end

View file

@ -112,57 +112,12 @@ describe AspectsController do
end
end
describe '#edit' do
before do
eve.profile.first_name = eve.profile.last_name = nil
eve.profile.save
eve.save
@zed = FactoryGirl.create(:user_with_aspect, :username => "zed")
@zed.profile.first_name = "zed"
@zed.profile.save
@zed.save
@katz = FactoryGirl.create(:user_with_aspect, :username => "katz")
@katz.profile.first_name = "katz"
@katz.profile.save
@katz.save
connect_users(alice, @alices_aspect_2, eve, eve.aspects.first)
connect_users(alice, @alices_aspect_2, @zed, @zed.aspects.first)
connect_users(alice, @alices_aspect_1, @katz, @katz.aspects.first)
end
it 'renders' do
get :edit, :id => @alices_aspect_1.id
response.should be_success
end
it 'assigns the contacts in alphabetical order with people in aspects first' do
get :edit, :id => @alices_aspect_2.id
assigns[:contacts].map(&:id).should == [alice.contact_for(eve.person), alice.contact_for(@zed.person), alice.contact_for(bob.person), alice.contact_for(@katz.person)].map(&:id)
end
it 'assigns all the contacts if noone is there' do
alices_aspect_3 = alice.aspects.create(:name => "aspect 3")
get :edit, :id => alices_aspect_3.id
assigns[:contacts].map(&:id).should == [alice.contact_for(bob.person), alice.contact_for(eve.person), alice.contact_for(@katz.person), alice.contact_for(@zed.person)].map(&:id)
end
it 'eager loads the aspect memberships for all the contacts' do
get :edit, :id => @alices_aspect_2.id
assigns[:contacts].each do |c|
c.aspect_memberships.loaded?.should be_true
end
end
end
describe "#toggle_contact_visibility" do
it 'sets contacts visible' do
@alices_aspect_1.contacts_visible = false
@alices_aspect_1.save
xhr :get, :toggle_contact_visibility, :format => 'js', :aspect_id => @alices_aspect_1.id
xhr :get, :toggle_contact_visibility, :aspect_id => @alices_aspect_1.id
@alices_aspect_1.reload.contacts_visible.should be_true
end
@ -170,7 +125,7 @@ describe AspectsController do
@alices_aspect_1.contacts_visible = true
@alices_aspect_1.save
xhr :get, :toggle_contact_visibility, :format => 'js', :aspect_id => @alices_aspect_1.id
xhr :get, :toggle_contact_visibility, :aspect_id => @alices_aspect_1.id
@alices_aspect_1.reload.contacts_visible.should be_false
end
end

View file

@ -10,23 +10,6 @@ describe ContactsController do
@controller.stub(:current_user).and_return(bob)
end
describe '#sharing' do
it "succeeds" do
get :sharing
response.should be_success
end
it 'eager loads the aspects' do
get :sharing
assigns[:contacts].first.aspect_memberships.loaded?.should be_true
end
it "assigns only the people sharing with you with 'share_with' flag" do
get :sharing, :id => 'share_with'
assigns[:contacts].to_set.should == bob.contacts.sharing.to_set
end
end
describe '#index' do
context 'format mobile' do
it "succeeds" do
@ -51,7 +34,7 @@ describe ContactsController do
contact = bob.contacts.first
contact.update_attributes(:sharing => false)
get :index, :set => "mine"
get :index
contacts = assigns(:contacts)
contacts.to_set.should == bob.contacts.receiving.to_set
end

View file

@ -7,11 +7,13 @@ require 'spec_helper'
describe ContactsController do
describe '#index' do
before do
@aspect = bob.aspects.create(:name => "another aspect")
bob.share_with alice.person, @aspect
sign_in :user, bob
end
it "generates a jasmine fixture", :fixture => true do
get :index
get :index, :a_id => @aspect.id
save_fixture(html_for("body"), "aspects_manage")
end
end

View file

@ -0,0 +1,215 @@
describe("app.views.Contacts", function(){
beforeEach(function() {
spec.loadFixture("aspects_manage");
this.view = new app.views.Contacts();
Diaspora.I18n.load({
contacts: {
add_contact: "Add contact",
aspect_list_is_visible: "Contacts in this aspect are able to see each other.",
aspect_list_is_not_visible: "Contacts in this aspect are not able to see each other.",
remove_contact: "Remove contact",
error_add: "Couldn't add <%= name %> to the aspect :(",
error_remove: "Couldn't remove <%= name %> from the aspect :("
}
});
});
context('toggle contacts visibility', function() {
beforeEach(function() {
this.visibility_toggle = $("#contacts_visibility_toggle");
this.lock_icon = $("#contacts_visibility_toggle .entypo");
});
it('updates the title for the tooltip', function() {
expect(this.lock_icon.attr('data-original-title')).toBe(
Diaspora.I18n.t("contacts.aspect_list_is_visible")
);
this.visibility_toggle.trigger('click');
expect(this.lock_icon.attr('data-original-title')).toBe(
Diaspora.I18n.t("contacts.aspect_list_is_not_visible")
);
});
it('toggles the lock icon', function() {
expect(this.lock_icon.hasClass('lock-open')).toBeTruethy;
expect(this.lock_icon.hasClass('lock')).toBeFalsy;
this.visibility_toggle.trigger('click');
expect(this.lock_icon.hasClass('lock')).toBeTruethy;
expect(this.lock_icon.hasClass('lock-open')).toBeFalsy;
});
});
context('show aspect name form', function() {
beforeEach(function() {
this.button = $('#change_aspect_name');
});
it('shows the form', function() {
expect($('#aspect_name_form').css('display')).toBe('none');
this.button.trigger('click');
expect($('#aspect_name_form').css('display')).not.toBe('none');
});
it('hides the aspect name', function() {
expect($('.header > h3').css('display')).not.toBe('none');
this.button.trigger('click');
expect($('.header > h3').css('display')).toBe('none');
});
});
context('add contact to aspect', function() {
beforeEach(function() {
this.contact = $('#people_stream .stream_element').last();
this.button = this.contact.find('.contact_add-to-aspect');
this.person_id = this.button.attr('data-person_id');
this.aspect_id = this.button.attr('data-aspect_id');
});
it('sends a correct ajax request', function() {
jasmine.Ajax.install();
$('.contact_add-to-aspect',this.contact).trigger('click');
var obj = $.parseJSON(jasmine.Ajax.requests.mostRecent().params);
expect(obj.person_id).toBe(this.person_id);
expect(obj.aspect_id).toBe(this.aspect_id);
});
it('adds a membership id to the contact', function() {
jasmine.Ajax.install();
$('.contact_add-to-aspect',this.contact).trigger('click');
jasmine.Ajax.requests.mostRecent().response({
status: 200, // success
responseText: '{ "id": 42 }'
});
expect(this.button.attr('data-membership_id')).toBe('42');
});
it('displays a flash message on errors', function(){
jasmine.Ajax.install();
$('.contact_add-to-aspect',this.contact).trigger('click');
jasmine.Ajax.requests.mostRecent().response({
status: 400, // fail
});
expect($('[id^="flash"]')).toBeErrorFlashMessage(
Diaspora.I18n.t(
'contacts.error_add',
{name: this.contact.find('.name').text()}
)
);
});
it('changes the appearance of the contact', function() {
expect(this.button.hasClass('contact_add-to-aspect')).toBeTruethy;
expect(this.button.hasClass('circled-cross')).toBeTruethy;
expect(this.contact.hasClass('in_aspect')).toBeTruethy;
expect(this.button.hasClass('contact_remove-from-aspect')).toBeFalsy;
expect(this.button.hasClass('circled-plus')).toBeFalsy;
expect(this.button.attr('data-original-title')).toBe(
Diaspora.I18n.t('contacts.add_contact')
);
jasmine.Ajax.install();
$('.contact_add-to-aspect',this.contact).trigger('click');
jasmine.Ajax.requests.mostRecent().response({
status: 200, // success
responseText: '{ "id": 42 }'
});
expect(this.button.hasClass('contact_add-to-aspect')).toBeFalsy;
expect(this.button.hasClass('circled-cross')).toBeFalsy;
expect(this.contact.hasClass('in_aspect')).toBeFalsy;
expect(this.button.hasClass('contact_remove-from-aspect')).toBeTruethy;
expect(this.button.hasClass('circled-plus')).toBeTruethy;
expect(this.button.attr('data-original-title')).toBe(
Diaspora.I18n.t('contacts.remove_contact')
);
});
});
context('remove contact from aspect', function() {
beforeEach(function() {
this.contact = $('#people_stream .stream_element').first();
this.button = this.contact.find('.contact_remove-from-aspect');
this.person_id = this.button.attr('data-person_id');
this.aspect_id = this.button.attr('data-aspect_id');
this.membership_id = this.button.attr('data-membership_id');
});
it('sends a correct ajax request', function() {
jasmine.Ajax.install();
$('.contact_remove-from-aspect',this.contact).trigger('click');
expect(jasmine.Ajax.requests.mostRecent().url).toBe(
"/aspect_memberships/"+this.membership_id
);
});
it('removes the membership id from the contact', function() {
jasmine.Ajax.install();
$('.contact_remove-from-aspect',this.contact).trigger('click');
jasmine.Ajax.requests.mostRecent().response({
status: 200, // success
responseText: '{}'
});
expect(this.button.attr('data-membership_id')).toBe(undefined);
});
it('displays a flash message on errors', function(){
jasmine.Ajax.install();
$('.contact_remove-from-aspect',this.contact).trigger('click');
jasmine.Ajax.requests.mostRecent().response({
status: 400, // fail
});
expect($('[id^="flash"]')).toBeErrorFlashMessage(
Diaspora.I18n.t(
'contacts.error_remove',
{name: this.contact.find('.name').text()}
)
);
});
it('changes the appearance of the contact', function() {
expect(this.button.hasClass('contact_add-to-aspect')).toBeFalsy;
expect(this.button.hasClass('circled-cross')).toBeFalsy;
expect(this.contact.hasClass('in_aspect')).toBeFalsy;
expect(this.button.hasClass('contact_remove-from-aspect')).toBeTruethy;
expect(this.button.hasClass('circled-plus')).toBeTruethy;
expect(this.button.attr('data-original-title')).toBe(
Diaspora.I18n.t('contacts.remove_contact')
);
jasmine.Ajax.install();
$('.contact_remove-from-aspect',this.contact).trigger('click');
jasmine.Ajax.requests.mostRecent().response({
status: 200, // success
responseText: '{}'
});
expect(this.button.hasClass('contact_add-to-aspect')).toBeTruethy;
expect(this.button.hasClass('circled-cross')).toBeTruethy;
expect(this.contact.hasClass('in_aspect')).toBeTruethy;
expect(this.button.hasClass('contact_remove-from-aspect')).toBeFalsy;
expect(this.button.hasClass('circled-plus')).toBeFalsy;
expect(this.button.attr('data-original-title')).toBe(
Diaspora.I18n.t('contacts.add_contact')
);
});
});
context('search contact list', function() {
beforeEach(function() {
this.searchinput = $('#contact_list_search');
this.username = $('.stream_element .name').first().text();
});
it('filters the contact list by name', function() {
expect($('.stream_element').length).toBeGreaterThan(1);
this.searchinput.val(this.username);
this.searchinput.trigger('keyup');
expect($('.stream_element:visible').length).toBe(1);
expect($('.stream_element:visible .name').first().text()).toBe(this.username);
});
});
});

View file

@ -1,19 +0,0 @@
/* Copyright (c) 2010-2011, Diaspora Inc. This file is
* licensed under the Affero General Public License version 3 or later. See
* the COPYRIGHT file.
*/
describe("Contact List", function() {
describe("disconnectUser", function() {
it("does an ajax call to person delete with the passed in id", function(){
var id = '3';
spyOn($,'ajax');
List.disconnectUser(id);
expect($.ajax).toHaveBeenCalled();
var option_hash = $.ajax.calls.mostRecent().args[0];
expect(option_hash.url).toEqual("/contacts/" + id);
expect(option_hash.type).toEqual("DELETE");
expect(option_hash.success).toBeDefined();
});
});
});

View file

@ -7,7 +7,6 @@ describe("List", function() {
describe("runDelayedSearch", function() {
beforeEach( function(){
spec.loadFixture('empty_people_search');
List.initialize();
});
it('inserts contact html', function(){