diff --git a/app/assets/javascripts/app/views/conversations_view.js b/app/assets/javascripts/app/views/conversations_view.js index c5f75ce4afe17315b57a2e443a34ca69e43ebf2d..f191969877b2a4653f30a510800fce9aa2736659 100644 --- a/app/assets/javascripts/app/views/conversations_view.js +++ b/app/assets/javascripts/app/views/conversations_view.js @@ -6,18 +6,44 @@ app.views.Conversations = Backbone.View.extend({ events: { "mouseenter .stream_element.conversation" : "showParticipants", - "mouseleave .stream_element.conversation" : "hideParticipants" + "mouseleave .stream_element.conversation" : "hideParticipants", + "conversation:loaded" : "setupConversation" }, initialize: function() { - $("#people_stream.contacts .header .entypo").tooltip({ 'placement': 'bottom'}); - // TODO doesn't work anymore - if ($('#first_unread').length > 0) { - $("html").scrollTop($('#first_unread').offset().top-50); + if($('#conversation_new:visible').length > 0) { + new app.views.ConversationsForm({contacts: gon.contacts}); } + this.setupConversation(); + }, - new app.views.ConversationsForm({contacts: gon.contacts}); + setupConversation: function() { app.helpers.timeago($(this.el)); + + var conv = $('.conversation-wrapper .stream_element.selected'), + cBadge = $('#conversations_badge .badge_count'); + + if(conv.hasClass('unread') ){ + var unreadCount = parseInt(conv.find('.unread_message_count').text(), 10); + + if(cBadge.text() !== '') { + cBadge.text().replace(/\d+/, function(num){ + num = parseInt(num, 10) - unreadCount; + if(num > 0) { + cBadge.text(num); + } else { + cBadge.text(0).addClass('hidden'); + } + }); + } + conv.removeClass('unread'); + conv.find('.unread_message_count').remove(); + + var pos = $('#first_unread').offset().top - 50; + $("html").animate({scrollTop:pos}); + } else { + $("html").animate({scrollTop:0}); + } }, hideParticipants: function(e){ diff --git a/app/assets/javascripts/inbox.js b/app/assets/javascripts/inbox.js index 6ec1d03dceb40ff208b0007709fb3e56e9da9ead..e270e121bd4ee4bf2468f4e58d1bef94651d46bc 100644 --- a/app/assets/javascripts/inbox.js +++ b/app/assets/javascripts/inbox.js @@ -1,41 +1,17 @@ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later - -/* Copyright (c) 2010-2011, Diaspora Inc. This file is - * licensed under the Affero General Public License version 3 or later. See - * the COPYRIGHT file. - */ - $(document).ready(function(){ $(document).on('click', '.conversation-wrapper', function(){ var conversation_path = $(this).data('conversation-path'); - $.getScript(conversation_path, function() { Diaspora.page.directionDetector.updateBinds(); }); - history.pushState(null, "", conversation_path); - - var conv = $(this).children('.stream_element'), - cBadge = $("#conversations_badge .badge_count"); - if(conv.hasClass('unread') ){ - conv.removeClass('unread'); - } - if(cBadge.html() !== null) { - cBadge.html().replace(/\d+/, function(num){ - num = parseInt(num); - cBadge.html(parseInt(num)-1); - if(num === 1) { - cBadge.addClass("hidden"); - } - }); - } - return false; }); $(window).bind("popstate", function(){ if (location.href.match(/conversations\/\d+/) !== null) { - $.getScript(location.href, function() { + $.getScript(location.href, function() { Diaspora.page.directionDetector.updateBinds(); }); return false; diff --git a/app/controllers/conversations_controller.rb b/app/controllers/conversations_controller.rb index 59c8c74968446dbe40b190315c9a5886bebbd3ac..56b263133f4cb8c3ccd2819eb3af9252265309c4 100644 --- a/app/controllers/conversations_controller.rb +++ b/app/controllers/conversations_controller.rb @@ -21,6 +21,10 @@ class ConversationsController < ApplicationController @first_unread_message_id = @conversation.try(:first_unread_message, current_user).try(:id) + if @conversation + @conversation.set_read(current_user) + end + @authors = {} @conversations.each { |c| @authors[c.id] = c.last_author } @@ -65,21 +69,21 @@ class ConversationsController < ApplicationController end def show - if @conversation = current_user.conversations.where(id: params[:id]).first - - @first_unread_message_id = @conversation.first_unread_message(current_user).try(:id) - if @visibility = ConversationVisibility.where(:conversation_id => params[:id], :person_id => current_user.person.id).first - @visibility.unread = 0 - @visibility.save + respond_to do |format| + format.html do + redirect_to conversations_path(:conversation_id => params[:id]) + return end - respond_to do |format| - format.html { redirect_to conversations_path(:conversation_id => @conversation.id) } + if @conversation = current_user.conversations.where(id: params[:id]).first + @first_unread_message_id = @conversation.first_unread_message(current_user).try(:id) + @conversation.set_read(current_user) + format.js format.json { render :json => @conversation, :status => 200 } + else + redirect_to conversations_path end - else - redirect_to conversations_path end end diff --git a/app/helpers/conversations_helper.rb b/app/helpers/conversations_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..3c00a2b9dd81c2c377cc1c6e5857134310775fa0 --- /dev/null +++ b/app/helpers/conversations_helper.rb @@ -0,0 +1,9 @@ +module ConversationsHelper + def conversation_class(conversation, unread_count, selected_conversation_id) + conv_class = unread_count > 0 ? "unread " : "" + if selected_conversation_id && conversation.id == selected_conversation_id + conv_class << "selected" + end + conv_class + end +end diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 9d48a1edb66f85fbbd731983142d9596bcdb8d42..7ade2b88f21a78777eef9a4f434a3c78a57299d7 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -51,6 +51,13 @@ class Conversation < ActiveRecord::Base end end + def set_read(user) + if visibility = self.conversation_visibilities.where(:person_id => user.person.id).first + visibility.unread = 0 + visibility.save + end + end + def public? false end diff --git a/app/views/conversations/_conversation.haml b/app/views/conversations/_conversation.haml index bcc528ec53e917e12efc52f1130f64ba9d44d61c..0fa3d5cff4c22ed894b0b09b7a8443046dc21253 100644 --- a/app/views/conversations/_conversation.haml +++ b/app/views/conversations/_conversation.haml @@ -3,7 +3,7 @@ -# the COPYRIGHT file. .conversation-wrapper{ :"data-conversation-path" => conversation_path(conversation) } - .stream_element.conversation{:data=>{:guid=>conversation.id}, :class => ('unread' if unread_counts[conversation.id].to_i > 0)} + .stream_element.conversation{:data=>{:guid=>conversation.id}, :class => conversation_class(conversation, unread_counts[conversation.id].to_i, selected_conversation_id)} .media .img - other_participants = ordered_participants[conversation.id] - [current_user.person] diff --git a/app/views/conversations/index.haml b/app/views/conversations/index.haml index 35e63b61ee3cdcf52af37f06570f5506e95f1316..d064f042fad6d8afec51f6f17a5a457fd40ca87b 100644 --- a/app/views/conversations/index.haml +++ b/app/views/conversations/index.haml @@ -17,7 +17,7 @@ #conversation_inbox .stream.conversations - if @conversations.count > 0 - = render :partial => 'conversations/conversation', :collection => @conversations, :locals => {:authors => @authors, :ordered_participants => @ordered_participants, :unread_counts => @unread_counts} + = render :partial => 'conversations/conversation', :collection => @conversations, :locals => {:authors => @authors, :ordered_participants => @ordered_participants, :unread_counts => @unread_counts, :selected_conversation_id => @conversation.try(:id)} - else #no_conversations = t('.no_messages') diff --git a/app/views/conversations/show.js.erb b/app/views/conversations/show.js.erb index 13cba6f5e941f16bef72e17f299be16de5f9a5a1..70e2a0ed5cb5acd8d3c6a6b48f49ffd04196c5ca 100644 --- a/app/views/conversations/show.js.erb +++ b/app/views/conversations/show.js.erb @@ -8,10 +8,4 @@ $('#conversation_show').html("<%= escape_javascript(render('conversations/show', $(".stream_element", "#conversation_inbox").removeClass('selected'); $(".stream_element[data-guid='<%= @conversation.id %>']", "#conversation_inbox").addClass('selected'); -$(".stream_element[data-guid='<%= @conversation.id %>']", "#conversation_inbox").find(".unread_message_count").remove() - -app.helpers.timeago($(document)); - -if ($('#first_unread') > 0) { - $("html").scrollTop($('#first_unread').offset().top-50); -} +$('#conversation_show').trigger("conversation:loaded"); diff --git a/spec/controllers/conversations_controller_spec.rb b/spec/controllers/conversations_controller_spec.rb index ae07609fce4ed10eba2e49433025a3f67c5c88b3..fe1cb326313ab96cce1e117bbab541984d92f68c 100644 --- a/spec/controllers/conversations_controller_spec.rb +++ b/spec/controllers/conversations_controller_spec.rb @@ -86,6 +86,12 @@ describe ConversationsController, :type => :controller do get :index expect(assigns[:conversations].count).to eq(3) end + + it 'does not let you access conversations where you are not a recipient' do + sign_in :user, eve + get :index, :conversation_id => @conversations.first.id + expect(assigns[:conversation]).to be_nil + end end describe '#create' do @@ -291,14 +297,6 @@ describe ConversationsController, :type => :controller do it 'redirects to index' do get :show, :id => @conversation.id expect(response).to redirect_to(conversations_path(:conversation_id => @conversation.id)) - expect(assigns[:conversation]).to eq(@conversation) - end - - it 'does not let you access conversations where you are not a recipient' do - sign_in :user, eve - - get :show, :id => @conversation.id - expect(response.code).to redirect_to conversations_path end end end diff --git a/spec/controllers/jasmine_fixtures/conversations_spec.rb b/spec/controllers/jasmine_fixtures/conversations_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b5d86e66c9a72b53002fbcf37026553456e8b8fd --- /dev/null +++ b/spec/controllers/jasmine_fixtures/conversations_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe ConversationsController, :type => :controller do + describe '#index' do + before do + @person = alice.contacts.first.person + hash = { + :author => @person, + :participant_ids => [alice.person.id, @person.id], + :subject => 'not spam', + :messages_attributes => [ {:author => @person, :text => 'cool stuff'} ] + } + @conv1 = Conversation.create(hash) + Message.create(:author => @person, :created_at => Time.now + 100, :text => "message", :conversation_id => @conv1.id) + .increase_unread(alice) + Message.create(:author => @person, :created_at => Time.now + 200, :text => "another message", :conversation_id => @conv1.id) + .increase_unread(alice) + + @conv2 = Conversation.create(hash) + Message.create(:author => @person, :created_at => Time.now + 100, :text => "message", :conversation_id => @conv2.id) + .increase_unread(alice) + + sign_in :user, alice + end + + it "generates a jasmine fixture", :fixture => true do + get :index, :conversation_id => @conv1.id + save_fixture(html_for("body"), "conversations_unread") + + get :index, :conversation_id => @conv1.id + save_fixture(html_for("body"), "conversations_read") + end + end +end diff --git a/spec/helpers/conversations_helper_spec.rb b/spec/helpers/conversations_helper_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..fafde29bf36b52ef04365785beb1803d9c703806 --- /dev/null +++ b/spec/helpers/conversations_helper_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe ConversationsHelper, :type => :helper do + before do + @conversation = FactoryGirl.create(:conversation) + end + + describe '#conversation_class' do + it 'returns an empty string as default' do + expect(conversation_class(@conversation, 0, nil)).to eq('') + expect(conversation_class(@conversation, 0, @conversation.id+1)).to eq('') + end + + it 'includes unread for unread conversations' do + expect(conversation_class(@conversation, 1, nil)).to include('unread') + expect(conversation_class(@conversation, 42, @conversation.id+1)).to include('unread') + expect(conversation_class(@conversation, 42, @conversation.id)).to include('unread') + end + + it 'does not include unread for read conversations' do + expect(conversation_class(@conversation, 0, @conversation.id)).to_not include('unread') + end + + it 'includes selected for selected conversations' do + expect(conversation_class(@conversation, 0, @conversation.id)).to include('selected') + expect(conversation_class(@conversation, 1, @conversation.id)).to include('selected') + end + + it 'does not include selected for not selected conversations' do + expect(conversation_class(@conversation, 1, @conversation.id+1)).to_not include('selected') + expect(conversation_class(@conversation, 1, nil)).to_not include('selected') + end + end +end diff --git a/spec/javascripts/app/views/conversations_view_spec.js b/spec/javascripts/app/views/conversations_view_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..c0346cd7313e410bdffaf9bc002568c3e74f6181 --- /dev/null +++ b/spec/javascripts/app/views/conversations_view_spec.js @@ -0,0 +1,54 @@ +describe("app.views.Conversations", function(){ + describe('setupConversation', function() { + context('for unread conversations', function() { + beforeEach(function() { + spec.loadFixture('conversations_unread'); + }); + + it('removes the unread class from the conversation', function() { + expect($('.conversation-wrapper > .conversation.selected')).toHaveClass('unread'); + new app.views.Conversations(); + expect($('.conversation-wrapper > .conversation.selected')).not.toHaveClass('unread'); + }); + + it('removes the unread message counter from the conversation', function() { + expect($('.conversation-wrapper > .conversation.selected .unread_message_count').length).toEqual(1); + new app.views.Conversations(); + expect($('.conversation-wrapper > .conversation.selected .unread_message_count').length).toEqual(0); + }); + + it('decreases the unread message count in the header', function() { + var badge = '<div id="conversations_badge"><div class="badge_count">3</div></div>'; + $('header').append(badge); + expect($('#conversations_badge .badge_count').text().trim()).toEqual('3'); + expect($('.conversation-wrapper > .conversation.selected .unread_message_count').text().trim()).toEqual('2'); + new app.views.Conversations(); + expect($('#conversations_badge .badge_count').text().trim()).toEqual('1'); + }); + + it('removes the badge_count in the header if there are no unread messages left', function() { + var badge = '<div id="conversations_badge"><div class="badge_count">2</div></div>'; + $('header').append(badge); + expect($('#conversations_badge .badge_count').text().trim()).toEqual('2'); + expect($('.conversation-wrapper > .conversation.selected .unread_message_count').text().trim()).toEqual('2'); + new app.views.Conversations(); + expect($('#conversations_badge .badge_count').text().trim()).toEqual('0'); + expect($('#conversations_badge .badge_count')).toHaveClass('hidden'); + }); + }); + + context('for read conversations', function() { + beforeEach(function() { + spec.loadFixture('conversations_read'); + }); + + it('does not change the badge_count in the header', function() { + var badge = '<div id="conversations_badge"><div class="badge_count">3</div></div>'; + $('header').append(badge); + expect($('#conversations_badge .badge_count').text().trim()).toEqual('3'); + new app.views.Conversations(); + expect($('#conversations_badge .badge_count').text().trim()).toEqual('3'); + }); + }); + }); +}); diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index 09fb8ee3e60cdf47db0571b6a4b026decd33ca78..4e555bf2e03acbcbbf92d8b06d37c45c3ea3e055 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -32,16 +32,16 @@ describe Conversation, :type => :model do end end - describe '#first_unread_message' do + describe '#first_unread_message' do before do @cnv = Conversation.create(@create_hash) @message = Message.create(:author => @user2.person, :created_at => Time.now + 100, :text => "last", :conversation_id => @cnv.id) - @message.increase_unread(@user1) + @message.increase_unread(@user1) end - + it 'returns the first unread message if there are unread messages in a conversation' do @cnv.first_unread_message(@user1) == @message - end + end it 'returns nil if there are no unread messages in a conversation' do @cnv.conversation_visibilities.where(:person_id => @user1.person.id).first.tap { |cv| cv.unread = 0 }.save @@ -49,6 +49,22 @@ describe Conversation, :type => :model do end end + describe '#set_read' do + before do + @cnv = Conversation.create(@create_hash) + Message.create(:author => @user2.person, :created_at => Time.now + 100, :text => "first", :conversation_id => @cnv.id) + .increase_unread(@user1) + Message.create(:author => @user2.person, :created_at => Time.now + 200, :text => "last", :conversation_id => @cnv.id) + .increase_unread(@user1) + end + + it 'sets the unread counter to 0' do + expect(@cnv.conversation_visibilities.where(:person_id => @user1.person.id).first.unread).to eq(2) + @cnv.set_read(@user1) + expect(@cnv.conversation_visibilities.where(:person_id => @user1.person.id).first.unread).to eq(0) + end + end + context 'transport' do before do @cnv = Conversation.create(@create_hash) @@ -118,7 +134,7 @@ describe Conversation, :type => :model do :messages_attributes => [ {:author => peter.person, :text => 'hey'} ] } end - + it 'with invalid recipient' do conversation = Conversation.create(@invalid_hash) expect(conversation).to be_invalid