diff --git a/Changelog.md b/Changelog.md index d33bfe15a..672a0b6a6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ * Build a color palette to uniform color usage [#4437](https://github.com/diaspora/diaspora/pull/4437) [#4469](https://github.com/diaspora/diaspora/pull/4469) [#4479](https://github.com/diaspora/diaspora/pull/4479) * Rename bitcoin_wallet_id setting to bitcoin_address [#4485](https://github.com/diaspora/diaspora/pull/4485) * Batch insert posts into stream collection for a small speedup [#4341](https://github.com/diaspora/diaspora/pull/4351) +* Ported fileuploader to Backbone and refactored publisher views [#4480](https://github.com/diaspora/diaspora/pull/4480) ## Bug fixes * Highlight down arrow at the user menu on hover [#4441](https://github.com/diaspora/diaspora/pull/4441) diff --git a/app/assets/javascripts/app/router.js b/app/assets/javascripts/app/router.js index b4b74eb7f..169c1e32c 100644 --- a/app/assets/javascripts/app/router.js +++ b/app/assets/javascripts/app/router.js @@ -99,7 +99,7 @@ app.Router = Backbone.Router.extend({ app.page = new app.views.Stream({model : app.stream}); app.publisher = app.publisher || new app.views.Publisher({collection : app.stream.items}); - app.publisher.updateAspectsSelector(ids); + app.publisher.setSelectedAspects(ids); var streamFacesView = new app.views.StreamFaces({collection : app.stream.items}); diff --git a/app/assets/javascripts/app/views/publisher/aspect_selector_view.js b/app/assets/javascripts/app/views/publisher/aspect_selector_view.js new file mode 100644 index 000000000..49c0f6567 --- /dev/null +++ b/app/assets/javascripts/app/views/publisher/aspect_selector_view.js @@ -0,0 +1,87 @@ +/* Copyright (c) 2010-2012, Diaspora Inc. This file is + * licensed under the Affero General Public License version 3 or later. See + * the COPYRIGHT file. + */ + +// Aspects view for the publisher. +// Provides the ability to specify the visibility of posted content as public +// or limited to selected aspects +app.views.PublisherAspectSelector = Backbone.View.extend({ + + events: { + "click .dropdown_list > li": "toggleAspect" + }, + + // event handler for aspect selection + toggleAspect: function(evt) { + var el = $(evt.target); + var btn = el.parent('.dropdown').find('.button'); + + // visually toggle the aspect selection + if( el.is('.radio') ) { + AspectsDropdown.toggleRadio(el); + } else { + AspectsDropdown.toggleCheckbox(el); + } + + // update the selection summary + this._updateAspectsNumber(el); + + this._updateSelectedAspectIds(); + }, + + // select a (list of) aspects in the dropdown selector by the given list of ids + updateAspectsSelector: function(ids){ + var el = this.$("ul.dropdown_list"); + this.$('.dropdown_list > li').each(function(){ + var el = $(this); + var aspectId = el.data('aspect_id'); + if (_.contains(ids, aspectId)) { + el.addClass('selected'); + } + else { + el.removeClass('selected'); + } + }); + + this._updateAspectsNumber(el); + this._updateSelectedAspectIds(); + }, + + // take care of the form fields that will indicate the selected aspects + _updateSelectedAspectIds: function() { + var self = this; + + // remove previous selection + this.options.form.find('input[name="aspect_ids[]"]').remove(); + + // create fields for current selection + this.$('.dropdown_list li.selected').each(function() { + var el = $(this); + var aspectId = el.data('aspect_id'); + + self._addHiddenAspectInput(aspectId); + + // close the dropdown when a radio item was selected + if( el.is('.radio') ) { + el.closest('.dropdown').removeClass('active'); + } + }); + }, + + _updateAspectsNumber: function(el){ + AspectsDropdown.updateNumber( + el.closest(".dropdown_list"), + null, + el.parent().find('li.selected').length, + '' + ); + }, + + _addHiddenAspectInput: function(id) { + var uid = _.uniqueId('aspect_ids_'); + this.options.form.append( + '' + ); + } +}); diff --git a/app/assets/javascripts/app/views/publisher/aspects_selector.js b/app/assets/javascripts/app/views/publisher/aspects_selector.js deleted file mode 100644 index d3b364265..000000000 --- a/app/assets/javascripts/app/views/publisher/aspects_selector.js +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright (c) 2010-2012, Diaspora Inc. This file is - * licensed under the Affero General Public License version 3 or later. See - * the COPYRIGHT file. - */ - -(function(){ - // mixin-object, used in conjunction with the publisher to provide the - // functionality for selecting aspects - app.views.PublisherAspectsSelector = { - - // event handler for aspect selection - toggleAspect: function(evt) { - var el = $(evt.target); - var btn = el.parent('.dropdown').find('.button'); - - // visually toggle the aspect selection - if( el.is('.radio') ) { - AspectsDropdown.toggleRadio(el); - } else { - AspectsDropdown.toggleCheckbox(el); - } - - // update the selection summary - this._updateAspectsNumber(el); - - this._updateSelectedAspectIds(); - }, - - updateAspectsSelector: function(ids){ - var el = this.$("ul.dropdown_list"); - this.$('.dropdown_list > li').each(function(){ - var el = $(this); - var aspectId = el.data('aspect_id'); - if (_.contains(ids, aspectId)) { - el.addClass('selected'); - } - else { - el.removeClass('selected'); - } - }); - - this._updateAspectsNumber(el); - this._updateSelectedAspectIds(); - }, - - // take care of the form fields that will indicate the selected aspects - _updateSelectedAspectIds: function() { - var self = this; - - // remove previous selection - this.$('input[name="aspect_ids[]"]').remove(); - - // create fields for current selection - this.$('.dropdown .dropdown_list li.selected').each(function() { - var el = $(this); - var aspectId = el.data('aspect_id'); - - self._addHiddenAspectInput(aspectId); - - // close the dropdown when a radio item was selected - if( el.is('.radio') ) { - el.closest('.dropdown').removeClass('active'); - } - }); - }, - - _updateAspectsNumber: function(el){ - AspectsDropdown.updateNumber( - el.closest(".dropdown_list"), - null, - el.parent().find('li.selected').length, - '' - ); - }, - - _addHiddenAspectInput: function(id) { - var uid = _.uniqueId('aspect_ids_'); - this.$('.content_creation form').append( - '' - ); - } - }; -})(); diff --git a/app/assets/javascripts/app/views/publisher/getting_started.js b/app/assets/javascripts/app/views/publisher/getting_started.js deleted file mode 100644 index 815f05585..000000000 --- a/app/assets/javascripts/app/views/publisher/getting_started.js +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright (c) 2010-2012, Diaspora Inc. This file is - * licensed under the Affero General Public License version 3 or later. See - * the COPYRIGHT file. - */ - -(function(){ - // mixin-object, used in conjunction with the publisher to provide the - // functionality for displaying 'getting-started' information - app.views.PublisherGettingStarted = { - - // initiate all the popover message boxes - triggerGettingStarted: function() { - this._addPopover(this.el_input, { - trigger: 'manual', - offset: 30, - id: 'first_message_explain', - placement: 'right', - html: true - }, 600); - this._addPopover(this.$('.dropdown'), { - trigger: 'manual', - offset: 10, - id: 'message_visibility_explain', - placement: 'bottom', - html: true - }, 1000); - this._addPopover($('#gs-shim'), { - trigger: 'manual', - offset: -5, - id: 'stream_explain', - placement: 'left', - html: true - }, 1400); - - // hide some popovers when a post is created - this.$('.button.creation').click(function() { - this.$('.dropdown').popover('hide'); - this.el_input.popover('hide'); - }); - }, - - _addPopover: function(el, opts, timeout) { - el.popover(opts); - el.click(function() { - el.popover('hide'); - }); - - // show the popover after the given timeout - setTimeout(function() { - el.popover('show'); - - // disable 'getting started' when the last popover is closed - var popup = el.data('popover').$tip[0]; - var close = $(popup).find('.close'); - - close.click(function() { - if( $('.popover').length==1 ) { - $.get('/getting_started_completed'); - } - el.popover('hide'); - }); - }, timeout); - } - }; -})(); \ No newline at end of file diff --git a/app/assets/javascripts/app/views/publisher/getting_started_view.js b/app/assets/javascripts/app/views/publisher/getting_started_view.js new file mode 100644 index 000000000..cee509884 --- /dev/null +++ b/app/assets/javascripts/app/views/publisher/getting_started_view.js @@ -0,0 +1,65 @@ +/* Copyright (c) 2010-2012, Diaspora Inc. This file is + * licensed under the Affero General Public License version 3 or later. See + * the COPYRIGHT file. + */ + +// Getting started view for the publisher. +// Provides "getting started" popups around the elements of the publisher +// for describing their use to new users. +app.views.PublisherGettingStarted = Backbone.View.extend({ + + // initiate all the popover message boxes + show: function() { + this._addPopover(this.options.el_first_msg, { + trigger: 'manual', + offset: 30, + id: 'first_message_explain', + placement: 'right', + html: true + }, 600); + this._addPopover(this.options.el_visibility, { + trigger: 'manual', + offset: 10, + id: 'message_visibility_explain', + placement: 'bottom', + html: true + }, 1000); + this._addPopover(this.options.el_stream, { + trigger: 'manual', + offset: -5, + id: 'stream_explain', + placement: 'left', + html: true + }, 1400); + + // hide some popovers when a post is created + this.$('.button.creation').click(function() { + this.options.el_visibility.popover('hide'); + this.options.el_first_msg.popover('hide'); + }); + }, + + _addPopover: function(el, opts, timeout) { + el.popover(opts); + el.click(function() { + el.popover('hide'); + }); + + // show the popover after the given timeout + setTimeout(function() { + el.popover('show'); + + // disable 'getting started' when the last popover is closed + var popup = el.data('popover').$tip[0]; + var close = $(popup).find('.close'); + + close.click(function() { + if( $('.popover').length==1 ) { + $.get('/getting_started_completed'); + } + el.popover('hide'); + return false; + }); + }, timeout); + } +}); diff --git a/app/assets/javascripts/app/views/publisher/services.js b/app/assets/javascripts/app/views/publisher/services.js deleted file mode 100644 index f8f250093..000000000 --- a/app/assets/javascripts/app/views/publisher/services.js +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright (c) 2010-2012, Diaspora Inc. This file is - * licensed under the Affero General Public License version 3 or later. See - * the COPYRIGHT file. - */ - -(function(){ - // mixin-object, used in conjunction with the publisher to provide the - // functionality for selecting services for cross-posting - app.views.PublisherServices = { - - // visually toggle the icon and kick-off all other actions for cross-posting - toggleService: function(evt) { - var el = $(evt.target); - var provider = el.attr('id'); - - el.toggleClass("dim"); - - this._createCounter(); - this._toggleServiceField(provider); - }, - - // keep track of character count - _createCounter: function() { - // remove obsolete counter - this.$('.counter').remove(); - - // create new counter - var min = 40000; - var a = this.$('.service_icon:not(.dim)'); - if(a.length > 0){ - $.each(a, function(index, value){ - var num = parseInt($(value).attr('maxchar')); - if (min > num) { min = num; } - }); - this.el_input.charCount({allowed: min, warning: min/10 }); - } - }, - - // add or remove the input containing the selected service - _toggleServiceField: function(provider) { - var hidden_field = this.$('input[name="services[]"][value="'+provider+'"]'); - if(hidden_field.length > 0){ - hidden_field.remove(); - } else { - var uid = _.uniqueId('services_'); - this.$(".content_creation form").append( - ''); - } - } - }; -})(); \ No newline at end of file diff --git a/app/assets/javascripts/app/views/publisher/services_view.js b/app/assets/javascripts/app/views/publisher/services_view.js new file mode 100644 index 000000000..fd2a8c534 --- /dev/null +++ b/app/assets/javascripts/app/views/publisher/services_view.js @@ -0,0 +1,60 @@ +/* Copyright (c) 2010-2012, Diaspora Inc. This file is + * licensed under the Affero General Public License version 3 or later. See + * the COPYRIGHT file. + */ + +// Services view for the publisher. +// Provides the ability for selecting services for cross-posting +app.views.PublisherServices = Backbone.View.extend({ + + events: { + 'click .service_icon': 'toggleService' + }, + + tooltipSelector: '.service_icon', + + initialize: function() { + // init tooltip plugin + this.$(this.tooltipSelector).tooltip(); + }, + + // visually toggle the icon and handle all other actions for cross-posting + toggleService: function(evt) { + var el = $(evt.target); + var provider = el.attr('id'); + + el.toggleClass("dim"); + + this._createCounter(); + this._toggleServiceField(provider); + }, + + // keep track of character count + _createCounter: function() { + // remove any obsolete counters + this.options.input.siblings('.counter').remove(); + + // create new counter + var min = 40000; + var a = this.$('.service_icon:not(.dim)'); + if(a.length > 0){ + $.each(a, function(index, value){ + var num = parseInt($(value).attr('maxchar')); + if (min > num) { min = num; } + }); + this.options.input.charCount({allowed: min, warning: min/10 }); + } + }, + + // add or remove the input containing the selected service + _toggleServiceField: function(provider) { + var hidden_field = this.options.form.find('input[name="services[]"][value="'+provider+'"]'); + if(hidden_field.length > 0){ + hidden_field.remove(); + } else { + var uid = _.uniqueId('services_'); + this.options.form.append( + ''); + } + } +}); diff --git a/app/assets/javascripts/app/views/publisher/uploader_view.js b/app/assets/javascripts/app/views/publisher/uploader_view.js new file mode 100644 index 000000000..b41475de6 --- /dev/null +++ b/app/assets/javascripts/app/views/publisher/uploader_view.js @@ -0,0 +1,124 @@ + +// Uploader view for the publisher. +// Initializes the file uploader plugin and handles callbacks for the upload +// progress. Attaches previews of finished uploads to the publisher. + +app.views.PublisherUploader = Backbone.View.extend({ + + allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'tif', 'tiff'], + sizeLimit: 4194304, // bytes + + initialize: function() { + this.uploader = new qq.FileUploaderBasic({ + element: this.el, + button: this.el, + + //debug: true, + + action: '/photos', + params: { photo: { pending: true }}, + allowedExtensions: this.allowedExtensions, + sizeLimit: this.sizeLimit, + messages: { + typeError: Diaspora.I18n.t('photo_uploader.invalid_ext'), + sizeError: Diaspora.I18n.t('photo_uploader.size_error'), + emptyError: Diaspora.I18n.t('photo_uploader.empty') + }, + onProgress: _.bind(this.progressHandler, this), + onSubmit: _.bind(this.submitHandler, this), + onComplete: _.bind(this.uploadCompleteHandler, this) + + }); + + this.el_info = $('
'); + this.options.publisher.el_wrapper.before(this.el_info); + + this.options.publisher.el_photozone.on('click', '.x', _.bind(this._removePhoto, this)); + }, + + progressHandler: function(id, fileName, loaded, total) { + var progress = Math.round(loaded / total * 100); + this.el_info.text(fileName + ' ' + progress + '%').fadeTo(200, 1); + }, + + submitHandler: function(id, fileName) { + this.$el.addClass('loading'); + this._addPhotoPlaceholder(); + }, + + // add photo placeholders to the publisher to indicate an upload in progress + _addPhotoPlaceholder: function() { + var publisher = this.options.publisher; + publisher.setButtonsEnabled(false); + + publisher.el_wrapper.addClass('with_attachments'); + publisher.el_photozone.append( + '
  • ' + + ' '+ + '
  • ' + ); + }, + + uploadCompleteHandler: function(id, fileName, response) { + this.el_info.text(Diaspora.I18n.t('photo_uploader.completed', {file: fileName})).fadeTo(2000, 0); + + var id = response.data.photo.id, + url = response.data.photo.unprocessed_image.url; + + this._addFinishedPhoto(id, url); + }, + + // replace the first photo placeholder with the finished uploaded image and + // add the id to the publishers form + _addFinishedPhoto: function(id, url) { + var publisher = this.options.publisher; + + // add form input element + publisher.$('.content_creation form').append( + '' + ); + + // replace placeholder + var placeholder = publisher.el_photozone.find('li.loading').first(); + placeholder + .removeClass('loading') + .append( + '
    X
    '+ + '
    ' + ) + .find('img').attr({'src': url, 'data-id': id}); + + // no more placeholders? enable buttons + if( publisher.el_photozone.find('li.loading').length == 0 ) { + this.$el.removeClass('loading'); + publisher.setButtonsEnabled(true); + } + }, + + // remove an already uploaded photo + _removePhoto: function(evt) { + var self = this; + var photo = $(evt.target).parents('.publisher_photo') + var img = photo.find('img'); + + photo.addClass('dim'); + $.ajax({ + url: '/photos/'+img.attr('data-id'), + dataType: 'json', + type: 'DELETE', + success: function() { + $.when(photo.fadeOut(400)).then(function(){ + photo.remove(); + + if( self.options.publisher.$('.publisher_photo').length == 0 ) { + // no more photos left... + self.options.publisher.el_wrapper.removeClass('with_attachments'); + } + }); + } + }); + + return false; + } + +}); diff --git a/app/assets/javascripts/app/views/publisher_view.js b/app/assets/javascripts/app/views/publisher_view.js index 85a3af762..e0f24dbca 100644 --- a/app/assets/javascripts/app/views/publisher_view.js +++ b/app/assets/javascripts/app/views/publisher_view.js @@ -3,15 +3,13 @@ * the COPYRIGHT file. */ -//= require ./publisher/services -//= require ./publisher/aspects_selector -//= require ./publisher/getting_started +//= require ./publisher/services_view +//= require ./publisher/aspect_selector_view +//= require ./publisher/getting_started_view +//= require ./publisher/uploader_view //= require jquery.textchange -app.views.Publisher = Backbone.View.extend(_.extend( - app.views.PublisherServices, - app.views.PublisherAspectsSelector, - app.views.PublisherGettingStarted, { +app.views.Publisher = Backbone.View.extend({ el : "#publisher", @@ -21,16 +19,12 @@ app.views.Publisher = Backbone.View.extend(_.extend( "click #hide_publisher" : "clear", "submit form" : "createStatusMessage", "click .post_preview_button" : "createPostPreview", - "click .service_icon": "toggleService", "textchange #status_message_fake_text": "handleTextchange", - "click .dropdown .dropdown_list li": "toggleAspect", "click #locator" : "showLocation", "click #hide_location" : "destroyLocation", "keypress #location_address" : "avoidEnter" }, - tooltipSelector: ".service_icon", - initialize : function(){ // init shortcut references to the various elements this.el_input = this.$('#status_message_fake_text'); @@ -46,9 +40,6 @@ app.views.Publisher = Backbone.View.extend(_.extend( // init autoresize plugin this.el_input.autoResize({ 'extraSpace' : 10, 'maxHeight' : Infinity }); - // init tooltip plugin - this.$(this.tooltipSelector).tooltip(); - // sync textarea content if( this.el_hiddenInput.val() == "" ) { this.el_hiddenInput.val( this.el_input.val() ); @@ -73,10 +64,48 @@ app.views.Publisher = Backbone.View.extend(_.extend( } }); + this.initSubviews(); return this; }, + initSubviews: function() { + var form = this.$('.content_creation form'); + + this.view_services = new app.views.PublisherServices({ + el: this.$('#publisher_service_icons'), + input: this.el_input, + form: form + }); + + this.view_aspect_selector = new app.views.PublisherAspectSelector({ + el: this.$('.public_toggle > .dropdown'), + form: form + }); + + this.view_getting_started = new app.views.PublisherGettingStarted({ + el_first_msg: this.el_input, + el_visibility: this.$('.public_toggle > .dropdown'), + el_stream: $('#gs-shim') + }); + + this.view_uploader = new app.views.PublisherUploader({ + el: this.$('#file-upload'), + publisher: this + }); + + }, + + // set the selected aspects in the dropdown by their ids + setSelectedAspects: function(ids) { + this.view_aspect_selector.updateAspectsSelector(ids); + }, + + // show the "getting started" popups around the publisher + triggerGettingStarted: function() { + this.view_getting_started.show(); + }, + createStatusMessage : function(evt) { if(evt){ evt.preventDefault(); } @@ -120,14 +149,15 @@ app.views.Publisher = Backbone.View.extend(_.extend( showLocation: function(){ if($('#location').length == 0){ $('#publisher_textarea_wrapper').after('
    '); - app.views.location = new app.views.Location(); + this.view_locator = new app.views.Location(); } }, // destroys the location destroyLocation: function(){ - if(app.views.location){ - app.views.location.remove(); + if(this.view_locator){ + this.view_locator.remove(); + delete this.view_locator; } }, @@ -260,11 +290,11 @@ app.views.Publisher = Backbone.View.extend(_.extend( }, tryClose : function(){ - // if it is not submittable, close it. + // if it is not submittable, close it. if( !this._submittable() ){ this.close() } - }, + }, open : function() { // visually 'open' the publisher @@ -286,14 +316,18 @@ app.views.Publisher = Backbone.View.extend(_.extend( checkSubmitAvailability: function() { if( this._submittable() ) { - this.el_submit.removeAttr('disabled'); - this.el_preview.removeAttr('disabled'); + this.setButtonsEnabled(true); } else { - this.el_submit.attr('disabled','disabled'); - this.el_preview.attr('disabled','disabled'); + this.setButtonsEnabled(false); } }, + setButtonsEnabled: function(bool) { + bool = !bool; + this.el_submit.prop({disabled: bool}); + this.el_preview.prop({disabled: bool}); + }, + // determine submit availability _submittable: function() { var onlyWhitespaces = ($.trim(this.el_input.val()) === ''), @@ -311,7 +345,7 @@ app.views.Publisher = Backbone.View.extend(_.extend( }); } -})); +}); // jQuery helper for serializing a
    into JSON $.fn.serializeObject = function() diff --git a/app/views/photos/_new_photo.haml b/app/views/photos/_new_photo.haml deleted file mode 100644 index 8aa2996ed..000000000 --- a/app/views/photos/_new_photo.haml +++ /dev/null @@ -1,91 +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. - -:javascript - function createUploader(){ - - var aspectIds = "#{aspect_ids}".split(','); - - var uploader = new qq.FileUploaderBasic({ - element: document.getElementById('file-upload'), - params: {'photo' : {'pending' : 'true', 'aspect_ids' : aspectIds}, 'set_profile_image' : "#{set_profile_image if defined?(set_profile_image)}"}, - allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'tiff'], - action: "#{photos_path}", - debug: true, - button: document.getElementById('file-upload'), - sizeLimit: 4194304, - - onProgress: function(id, fileName, loaded, total){ - var progress = Math.round(loaded / total * 100 ); - $('#fileInfo').text(fileName + ' ' + progress + '%').fadeTo(200, 1); - }, - - messages: { - typeError: "#{t('.invalid_ext')}", - sizeError: "#{t('.size_error')}", - emptyError: "#{t('.empty')}" - }, - - onSubmit: function(id, fileName){ - $('#file-upload').addClass("loading"); - $('#publisher').find("input[type='submit']").attr('disabled','disabled'); - $('#publisher').find("button.post_preview_button").attr('disabled','disabled'); - - app.publisher.el_wrapper.addClass("with_attachments"); - $('#photodropzone').append( - "
  • " + - "#{escape_javascript(image_tag('ajax-loader2.gif'))}" + - "
  • " - ); - }, - - onComplete: function(id, fileName, responseJSON) { - $('#fileInfo').text(Diaspora.I18n.t("photo_uploader.completed", file=fileName)).fadeTo(2000, 0); - var id = responseJSON.data.photo.id, - url = responseJSON.data.photo.unprocessed_image.url, - currentPlaceholder = $('li.loading').first(); - - app.publisher.el_wrapper.addClass("with_attachments"); - $('#new_status_message').append(""); - - // replace image placeholders - var img = currentPlaceholder.find('img'); - img.attr('src', url); - img.attr('data-id', id); - currentPlaceholder.removeClass('loading'); - currentPlaceholder.append("
    X
    " + - "
    "); - //// - - var publisher = $('#publisher'), - textarea = publisher.find('textarea'); - - publisher.find("input[type='submit']").removeAttr('disabled'); - publisher.find("button.post_preview_button").removeAttr('disabled'); - - $('.x').bind('click', function(){ - var photo = $(this).closest('.publisher_photo'); - photo.addClass("dim"); - $.ajax({url: "/photos/" + photo.children('img').attr('data-id'), - dataType: 'json', - type: 'DELETE', - success: function() { - photo.fadeOut(400, function(){ - photo.remove(); - if ( $('.publisher_photo').length == 0){ - app.publisher.el_wrapper.removeClass("with_attachments"); - } - }); - } - }); - }); - }, - - onAllComplete: function(completed_files){ - } - - }); - } - - createUploader(); diff --git a/app/views/shared/_publisher.html.haml b/app/views/shared/_publisher.html.haml index f892b4e44..f0a38fee6 100644 --- a/app/views/shared/_publisher.html.haml +++ b/app/views/shared/_publisher.html.haml @@ -14,7 +14,6 @@ = status.error_messages %div %params - #fileInfo #publisher_textarea_wrapper = link_to(content_tag(:div, nil, :class => 'icons-deletelabel'), "#", :id => "hide_publisher", :title => t('.discard_post')) %ul#photodropzone @@ -90,5 +89,3 @@ #publisher_photo_upload - = render 'photos/new_photo', :aspect_ids => aspect_ids.join(',') - diff --git a/spec/javascripts/app/views/publisher_view_spec.js b/spec/javascripts/app/views/publisher_view_spec.js index ed46a8a87..a6a7135b3 100644 --- a/spec/javascripts/app/views/publisher_view_spec.js +++ b/spec/javascripts/app/views/publisher_view_spec.js @@ -18,7 +18,7 @@ describe("app.views.Publisher", function() { it("hides the close button in standalone mode", function() { expect(this.view.$('#hide_publisher').is(':visible')).toBeFalsy(); }); - + it("hides the post preview button in standalone mode", function() { expect(this.view.$('.post_preview_button').is(':visible')).toBeFalsy(); }); @@ -65,7 +65,7 @@ describe("app.views.Publisher", function() { this.view.clear($.Event()); expect(this.view.close).toHaveBeenCalled(); }) - + it("calls removePostPreview", function(){ spyOn(this.view, "removePostPreview"); @@ -121,18 +121,18 @@ describe("app.views.Publisher", function() { var form = this.view.$("form") var submitCallback = jasmine.createSpy().andReturn(false); form.submit(submitCallback); - + var e = $.Event("keydown", { keyCode: 13 }); e.ctrlKey = true; this.view.keyDown(e); - + expect(submitCallback).toHaveBeenCalled(); expect($(this.view.el)).not.toHaveClass("closed"); }) }) }); - context("#toggleService", function(){ + context("services", function(){ beforeEach( function(){ spec.loadFixture('aspects_index_services'); this.view = new app.views.Publisher(); @@ -156,52 +156,42 @@ describe("app.views.Publisher", function() { expect(second.hasClass('dim')).toBeTruthy(); }); - describe("#_createCounter", function() { - it("gets called in when you toggle service icons", function(){ - spyOn(this.view, '_createCounter'); - $(".service_icon").first().trigger('click'); - expect(this.view._createCounter).toHaveBeenCalled(); - }); - - it("removes the 'old' .counter span", function(){ - spyOn($.fn, "remove"); - $(".service_icon").first().trigger('click'); - expect($.fn.remove).toHaveBeenCalled(); - }); + it("creates a counter element", function(){ + expect(this.view.$('.counter').length).toBe(0); + $(".service_icon").first().trigger('click'); + expect(this.view.$('.counter').length).toBe(1); }); - describe("#_toggleServiceField", function() { - it("gets called when you toggle service icons", function(){ - spyOn(this.view, '_toggleServiceField'); - $(".service_icon").first().trigger('click'); - expect(this.view._toggleServiceField).toHaveBeenCalled(); - }); + it("removes any old counters", function(){ + spyOn($.fn, "remove"); + $(".service_icon").first().trigger('click'); + expect($.fn.remove).toHaveBeenCalled(); + }); - it("toggles the hidden input field", function(){ - expect($('input[name="services[]"]').length).toBe(0); - $(".service_icon").first().trigger('click'); - expect($('input[name="services[]"]').length).toBe(1); - $(".service_icon").first().trigger('click'); - expect($('input[name="services[]"]').length).toBe(0); - }); + it("toggles the hidden input field", function(){ + expect(this.view.$('input[name="services[]"]').length).toBe(0); + $(".service_icon").first().trigger('click'); + expect(this.view.$('input[name="services[]"]').length).toBe(1); + $(".service_icon").first().trigger('click'); + expect(this.view.$('input[name="services[]"]').length).toBe(0); + }); - it("toggles the correct input", function() { - var first = $(".service_icon").eq(0); - var second = $(".service_icon").eq(1); + it("toggles the correct input", function() { + var first = $(".service_icon").eq(0); + var second = $(".service_icon").eq(1); - first.trigger('click'); - second.trigger('click'); + first.trigger('click'); + second.trigger('click'); - expect($('input[name="services[]"]').length).toBe(2); + expect(this.view.$('input[name="services[]"]').length).toBe(2); - first.trigger('click'); + first.trigger('click'); - var prov1 = first.attr('id'); - var prov2 = second.attr('id'); + var prov1 = first.attr('id'); + var prov2 = second.attr('id'); - expect($('input[name="services[]"][value="'+prov1+'"]').length).toBe(0); - expect($('input[name="services[]"][value="'+prov2+'"]').length).toBe(1); - }); + expect(this.view.$('input[name="services[]"][value="'+prov1+'"]').length).toBe(0); + expect(this.view.$('input[name="services[]"][value="'+prov2+'"]').length).toBe(1); }); }); @@ -236,18 +226,12 @@ describe("app.views.Publisher", function() { expect(this.check_els.last().hasClass('selected')).toBeTruthy(); }); - describe("#_updateSelectedAspectIds", function(){ + describe("hidden form elements", function(){ beforeEach(function(){ this.li = $('
  • '); this.view.$('.dropdown_list').append(this.li); }); - it("gets called when aspects are selected", function(){ - spyOn(this.view, "_updateSelectedAspectIds"); - this.check_els.last().trigger('click'); - expect(this.view._updateSelectedAspectIds).toHaveBeenCalled(); - }); - it("removes a previous selection and inserts the current one", function() { var selected = this.view.$('input[name="aspect_ids[]"]'); expect(selected.length).toBe(1); @@ -261,13 +245,13 @@ describe("app.views.Publisher", function() { }); it("toggles the same item", function() { - expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(1); + expect(this.view.$('input[name="aspect_ids[]"][value="42"]').length).toBe(0); this.li.trigger('click'); - expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(1); + expect(this.view.$('input[name="aspect_ids[]"][value="42"]').length).toBe(1); this.li.trigger('click'); - expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(0); + expect(this.view.$('input[name="aspect_ids[]"][value="42"]').length).toBe(0); }); it("keeps other fields with different values", function() { @@ -275,30 +259,13 @@ describe("app.views.Publisher", function() { this.view.$('.dropdown_list').append(li2); this.li.trigger('click'); - expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(1); - li2.trigger('click'); - expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(2); + + expect(this.view.$('input[name="aspect_ids[]"][value="42"]').length).toBe(1); + expect(this.view.$('input[name="aspect_ids[]"][value="99"]').length).toBe(1); }); }); - describe("#_addHiddenAspectInput", function(){ - it("gets called when aspects are selected", function(){ - spyOn(this.view, "_addHiddenAspectInput"); - this.check_els.last().trigger('click'); - expect(this.view._addHiddenAspectInput).toHaveBeenCalled(); - }); - - it("adds a hidden input to the form", function(){ - var id = 42; - - this.view._addHiddenAspectInput(id); - var input = this.view.$('input[name="aspect_ids[]"][value="'+id+'"]'); - - expect(input.length).toBe(1); - expect(input.val()).toBe('42'); - }); - }); }); context("locator", function() { @@ -314,9 +281,9 @@ describe("app.views.Publisher", function() { it("Show location", function(){ // inserts location to the DOM; it is the location's view element - setFixtures('
    '); + setFixtures('
    '); - // creates a fake Locator + // creates a fake Locator OSM = {}; OSM.Locator = function(){return { getAddress:function(){}}}; @@ -333,26 +300,11 @@ describe("app.views.Publisher", function() { describe('#destroyLocation', function(){ it("Destroy location if exists", function(){ - - // inserts location to the DOM; it is the location's view element - setFixtures('
    '); - - //Backup original view - var original_location = app.views.Location; - - // creates a new Location view with the #location element - app.views.Location = new Backbone.View({el:"#location"}); - - // creates the mock - app.views.location = sinon.mock(app.views.Location).object; - - // calls the destroy function and test the expected result + setFixtures('
    '); + this.view.view_locator = new app.views.Location({el: "#location"}); this.view.destroyLocation(); expect($("#location").length).toBe(0); - - //Restore view - app.views.Location = original_location; }) }); @@ -368,5 +320,135 @@ describe("app.views.Publisher", function() { }); }); + context('uploader', function() { + beforeEach(function() { + jQuery.fx.off = true; + setFixtures( + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ' '+ + '
    '+ + '
    ' + ); + }); + + it('initializes the file uploader plugin', function() { + spyOn(qq, 'FileUploaderBasic'); + var publisher = new app.views.Publisher(); + + expect(qq.FileUploaderBasic).toHaveBeenCalled(); + }); + + context('event handlers', function() { + beforeEach(function() { + this.view = new app.views.Publisher(); + + // replace the uploader plugin with a dummy object + var upload_view = this.view.view_uploader; + this.uploader = { + onProgress: _.bind(upload_view.progressHandler, upload_view), + onSubmit: _.bind(upload_view.submitHandler, upload_view), + onComplete: _.bind(upload_view.uploadCompleteHandler, upload_view) + }; + upload_view.uploader = this.uploader; + }); + + context('progress', function() { + it('shows progress in percent', function() { + this.uploader.onProgress(null, 'test.jpg', 20, 100); + + var info = this.view.view_uploader.el_info; + expect(info.text()).toContain('test.jpg'); + expect(info.text()).toContain('20%'); + }); + }); + + context('submitting', function() { + beforeEach(function() { + this.uploader.onSubmit(null, 'test.jpg'); + }); + + it('adds a placeholder', function() { + expect(this.view.el_wrapper.attr('class')).toContain('with_attachments'); + expect(this.view.el_photozone.find('li').length).toBe(1); + }); + + it('disables the publisher buttons', function() { + expect(this.view.el_submit.prop('disabled')).toBeTruthy(); + expect(this.view.el_preview.prop('disabled')).toBeTruthy(); + }); + }); + + context('completion', function() { + beforeEach(function() { + Diaspora.I18n.loadLocale({ photo_uploader: { completed: '<%= file %> completed' }}); + $('#photodropzone').html('
  • '); + + this.uploader.onComplete(null, 'test.jpg', { data: { photo: { + id: '987', + unprocessed_image: { url: 'test.jpg' } + }}}); + }); + + it('shows it in text form', function() { + var info = this.view.view_uploader.el_info; + expect(info.text()).toBe(Diaspora.I18n.t('photo_uploader.completed', {file: 'test.jpg'})) + }); + + it('adds a hidden input to the publisher', function() { + var input = this.view.$('input[type="hidden"][value="987"][name="photos[]"]'); + expect(input.length).toBe(1); + }); + + it('replaces the placeholder', function() { + var li = this.view.el_photozone.find('li'); + var img = li.find('img'); + + expect(li.attr('class')).not.toContain('loading'); + expect(img.attr('src')).toBe('test.jpg'); + expect(img.attr('data-id')).toBe('987'); + }); + + it('re-enables the buttons', function() { + expect(this.view.el_submit.prop('disabled')).toBeFalsy(); + expect(this.view.el_preview.prop('disabled')).toBeFalsy(); + }); + }); + }); + + context('photo removal', function() { + beforeEach(function() { + this.view = new app.views.Publisher(); + this.view.el_wrapper.addClass('with_attachments'); + this.view.el_photozone.html( + '
  • .'+ + ' '+ + '
    X
    '+ + '
    '+ + '
  • ' + ); + + spyOn(jQuery, 'ajax').andCallFake(function(opts) { opts.success(); }); + this.view.el_photozone.find('.x').click(); + }); + + it('removes the element', function() { + var photo = this.view.el_photozone.find('li.publisher_photo'); + expect(photo.length).toBe(0); + }); + + it('sends an ajax request', function() { + expect($.ajax).toHaveBeenCalled(); + }); + + it('removes class on wrapper element', function() { + expect(this.view.el_wrapper.attr('class')).not.toContain('with_attachments'); + }); + }); + }); + });