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( + '
'+
+ '