From 7cc4b46d7ec8b89af7d47e27e4e954b3d9c4556b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bolvin?= <frederic@bol.vin>
Date: Sat, 10 Sep 2016 17:06:33 +0200
Subject: [PATCH] Replaced fileuploader-custom with FineUploader

closes #7083
---
 Changelog.md                                  |    1 +
 Gemfile                                       |    1 +
 Gemfile.lock                                  |    4 +-
 .../app/views/publisher/uploader_view.js      |   45 +-
 .../helpers/profile_photo_uploader.js         |   94 +-
 app/assets/javascripts/jasmine-load-all.js    |    2 +-
 app/assets/javascripts/main.js                |    2 +-
 app/assets/javascripts/mobile/mobile.js       |    2 +-
 .../mobile/mobile_file_uploader.js            |  129 +-
 features/desktop/edits_profile.feature        |    6 +-
 features/mobile/edits_profile.feature         |    4 +-
 features/mobile/getting_started.feature       |    4 +-
 features/mobile/multiphoto.feature            |    6 +-
 features/mobile/posts_from_main_page.feature  |   10 +-
 features/support/publishing_cuke_helpers.rb   |    4 +-
 lib/assets/javascripts/fileuploader-custom.js | 1267 -----------------
 .../app/views/publisher_view_spec.js          |    6 +-
 17 files changed, 188 insertions(+), 1399 deletions(-)
 delete mode 100644 lib/assets/javascripts/fileuploader-custom.js

diff --git a/Changelog.md b/Changelog.md
index b025b9a14d..c4232da020 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,6 +2,7 @@
 
 ## Refactor
 * Increase the spacing above and below post contents [#7267](https://github.com/diaspora/diaspora/pull/7267)
+* Replace fileuploader-custom with FineUploader [#7083](https://github.com/diaspora/diaspora/pull/7083)
 
 ## Bug fixes
 * Fix background color of year on notifications page with dark theme [#7263](https://github.com/diaspora/diaspora/pull/7263)
diff --git a/Gemfile b/Gemfile
index 9e4a60e827..0c25a8dc92 100644
--- a/Gemfile
+++ b/Gemfile
@@ -105,6 +105,7 @@ source "https://rails-assets.org" do
   gem "rails-assets-highlightjs",                         "9.7.0"
   gem "rails-assets-bootstrap-markdown",                  "2.10.0"
   gem "rails-assets-corejs-typeahead",                    "1.0.1"
+  gem "rails-assets-fineuploader-dist",                   "5.11.0"
 
   # jQuery plugins
 
diff --git a/Gemfile.lock b/Gemfile.lock
index 70ff9e4fee..63b87bd2c7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -657,6 +657,7 @@ GEM
       rails-assets-jquery.ui (~> 1.11.4)
     rails-assets-emojione (2.0.1)
     rails-assets-favico.js (0.3.10)
+    rails-assets-fineuploader-dist (5.11.0)
     rails-assets-highlightjs (9.7.0)
     rails-assets-jasmine (2.4.1)
     rails-assets-jasmine-ajax (3.2.0)
@@ -1001,6 +1002,7 @@ DEPENDENCIES
   rails-assets-bootstrap-markdown (= 2.10.0)!
   rails-assets-corejs-typeahead (= 1.0.1)!
   rails-assets-diaspora_jsxc (= 0.1.5.develop.7)!
+  rails-assets-fineuploader-dist (= 5.11.0)!
   rails-assets-highlightjs (= 9.7.0)!
   rails-assets-jasmine-ajax (= 3.2.0)!
   rails-assets-jquery (= 2.2.4)!
@@ -1052,4 +1054,4 @@ DEPENDENCIES
   will_paginate (= 3.1.5)
 
 BUNDLED WITH
-   1.13.6
+   1.13.7
diff --git a/app/assets/javascripts/app/views/publisher/uploader_view.js b/app/assets/javascripts/app/views/publisher/uploader_view.js
index 5f1fedc0cc..775cf69bba 100644
--- a/app/assets/javascripts/app/views/publisher/uploader_view.js
+++ b/app/assets/javascripts/app/views/publisher/uploader_view.js
@@ -5,32 +5,43 @@
 // 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(opts) {
     this.publisher = opts.publisher;
-
-    this.uploader = new qq.FileUploaderBasic({
+    this.uploader = new qq.FineUploaderBasic({
       element: this.el,
-      button:  this.el,
-
-      //debug: true,
-
-      action: "/photos",
-      params: { photo: { pending: true }},
-      allowedExtensions: this.allowedExtensions,
-      sizeLimit: this.sizeLimit,
+      button: this.el,
+
+      request: {
+        endpoint: Routes.photos(),
+        params: {
+          /* eslint-disable camelcase */
+          authenticity_token: $("meta[name='csrf-token']").attr("content"),
+          /* eslint-enable camelcase */
+          photo: {
+            pending: true
+          }
+        }
+      },
+      validation: {
+        allowedExtensions: this.allowedExtensions,
+        sizeLimit: this.sizeLimit
+      },
       messages: {
-        typeError:  Diaspora.I18n.t("photo_uploader.invalid_ext"),
-        sizeError:  Diaspora.I18n.t("photo_uploader.size_error"),
+        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)
-
+      callbacks: {
+        onProgress: _.bind(this.progressHandler, this),
+        onSubmit: _.bind(this.submitHandler, this),
+        onComplete: _.bind(this.uploadCompleteHandler, this),
+        onError: function(id, name, errorReason) {
+          if (app.flashMessages) { app.flashMessages.error(errorReason); }
+        }
+      }
     });
 
     this.info = $("<div id=\"fileInfo\" />");
diff --git a/app/assets/javascripts/helpers/profile_photo_uploader.js b/app/assets/javascripts/helpers/profile_photo_uploader.js
index c50bc50219..1b50b5bcf3 100644
--- a/app/assets/javascripts/helpers/profile_photo_uploader.js
+++ b/app/assets/javascripts/helpers/profile_photo_uploader.js
@@ -8,18 +8,22 @@ Diaspora.ProfilePhotoUploader.prototype = {
   constructor: Diaspora.ProfilePhotoUploader,
 
   initialize: function() {
-    new qq.FileUploaderBasic({
+    new qq.FineUploaderBasic({
       element: document.getElementById("file-upload"),
-      params: {"photo": {"pending": true, "aspect_ids": "all", "set_profile_photo": true}},
-      allowedExtensions: ["jpg", "jpeg", "png"],
-      action: "/photos",
-      button: document.getElementById("file-upload"),
-      sizeLimit: 4194304,
-
-      onProgress: function(id, fileName, loaded, total) {
-        var progress = Math.round(loaded / total * 100);
-        $("#fileInfo").text(fileName + " " + progress + "%");
+      validation: {
+        allowedExtensions: ["jpg", "jpeg", "png"],
+        sizeLimit: 4194304
       },
+      request: {
+        endpoint: Routes.photos(),
+        params: {
+          /* eslint-disable camelcase */
+          authenticity_token: $("meta[name='csrf-token']").attr("content"),
+          /* eslint-enable camelcase */
+          photo: {"pending": true, "aspect_ids": "all", "set_profile_photo": true}
+        }
+      },
+      button: document.getElementById("file-upload"),
 
       messages: {
         typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
@@ -27,33 +31,53 @@ Diaspora.ProfilePhotoUploader.prototype = {
         emptyError: Diaspora.I18n.t("photo_uploader.empty")
       },
 
-      onSubmit: function() {
-        $("#file-upload").addClass("loading");
-        $("#profile_photo_upload").find(".avatar").addClass("loading");
-        $("#file-upload-spinner").removeClass("hidden");
-        $("#fileInfo").show();
-      },
+      callbacks: {
+        onProgress: function(id, fileName, loaded, total) {
+          var progress = Math.round(loaded / total * 100);
+          $("#fileInfo").text(fileName + " " + progress + "%");
+        },
+        onSubmit: function() {
+          $("#file-upload").addClass("loading");
+          $("#profile_photo_upload").find(".avatar").addClass("loading");
+          $("#file-upload-spinner").removeClass("hidden");
+          $("#fileInfo").show();
+        },
+        onComplete: function(id, fileName, responseJSON) {
+          $("#file-upload-spinner").addClass("hidden");
+          $("#fileInfo").text(Diaspora.I18n.t("photo_uploader.completed", {"file": fileName}));
+          $("#file-upload").removeClass("loading");
 
-      onComplete: function(_id, fileName, responseJSON) {
-        $("#file-upload-spinner").addClass("hidden");
-        $("#fileInfo").text(Diaspora.I18n.t("photo_uploader.completed", {"file": fileName}));
-        $("#file-upload").removeClass("loading");
-
-        /* flash message prompt */
-        var message = Diaspora.I18n.t("photo_uploader.looking_good");
-        if (app.flashMessages) { app.flashMessages.success(message); }
-
-        var id = responseJSON.data.photo.id;
-        var url = responseJSON.data.photo.unprocessed_image.url;
-        var oldPhoto = $("#photo_id");
-        if (oldPhoto.length === 0) {
-          $("#update_profile_form").prepend("<input type='hidden' value='" + id + "' id='photo_id' name='photo_id'/>");
-        } else {
-          oldPhoto.val(id);
-        }
+          if (responseJSON.data !== undefined) {
+            /* flash message prompt */
+            var message = Diaspora.I18n.t("photo_uploader.looking_good");
+            if (app.flashMessages) {
+              app.flashMessages.success(message);
+            } else {
+              alert(message);
+            }
 
-        $("#profile_photo_upload").find(".avatar").removeClass("loading");
-        $("#profile_photo_upload").find(".avatar").attr("src", url);
+            var photoId = responseJSON.data.photo.id;
+            var url = responseJSON.data.photo.unprocessed_image.url;
+            var oldPhoto = $("#photo_id");
+            if (oldPhoto.length === 0) {
+              $("#update_profile_form")
+                .prepend("<input type='hidden' value='" + photoId + "' id='photo_id' name='photo_id'/>");
+            } else {
+              oldPhoto.val(photoId);
+            }
+
+            $("#profile_photo_upload").find(".avatar").attr("src", url);
+            $(".avatar[data-person_id=" + gon.user.id + "]").attr("src", url);
+          }
+          $("#profile_photo_upload").find(".avatar").removeClass("loading");
+        },
+        onError: function(id, name, errorReason) {
+          if (app.flashMessages) {
+            app.flashMessages.error(errorReason);
+          } else {
+            alert(errorReason);
+          }
+        }
       }
     });
   }
diff --git a/app/assets/javascripts/jasmine-load-all.js b/app/assets/javascripts/jasmine-load-all.js
index c84835fe3f..756f84ce7d 100644
--- a/app/assets/javascripts/jasmine-load-all.js
+++ b/app/assets/javascripts/jasmine-load-all.js
@@ -2,7 +2,7 @@
 //= require handlebars.runtime
 //= require templates
 //= require main
-//= require fileuploader-custom
+//= require fineuploader-dist/dist/fine-uploader.core
 //= require mobile/mobile
 //= require jquery.autoSuggest.custom
 //= require contact-list
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 36541f536d..96acc6b469 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -18,7 +18,7 @@
 //= require jquery-ui/sortable
 //= require keycodes
 //= require jquery.autoSuggest.custom
-//= require fileuploader-custom
+//= require fineuploader-dist/dist/fine-uploader.core
 //= require handlebars.runtime
 //= require posix-bracket-expressions
 //= require markdown-it
diff --git a/app/assets/javascripts/mobile/mobile.js b/app/assets/javascripts/mobile/mobile.js
index 5ae8717e37..70f55e694f 100644
--- a/app/assets/javascripts/mobile/mobile.js
+++ b/app/assets/javascripts/mobile/mobile.js
@@ -10,7 +10,7 @@
 //= require autosize
 //= require keycodes
 //= require jquery.autoSuggest.custom
-//= require fileuploader-custom
+//= require fineuploader-dist/dist/fine-uploader.core
 //= require rails-timeago
 //= require underscore
 //= require bootstrap
diff --git a/app/assets/javascripts/mobile/mobile_file_uploader.js b/app/assets/javascripts/mobile/mobile_file_uploader.js
index 14f966eb2f..3b5f996a79 100644
--- a/app/assets/javascripts/mobile/mobile_file_uploader.js
+++ b/app/assets/javascripts/mobile/mobile_file_uploader.js
@@ -4,78 +4,93 @@
 function createUploader(){
    var aspectIds = gon.preloads.aspect_ids;
 
-   new qq.FileUploaderBasic({
-       element: document.getElementById('file-upload-publisher'),
-       params: {'photo' : {'pending' : 'true', 'aspect_ids' : aspectIds},},
-       allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'tiff'],
-       action: "/photos",
-       debug: false,
-       button: document.getElementById('file-upload-publisher'),
-       sizeLimit: 4194304,
+  new qq.FineUploaderBasic({
+    element: document.getElementById("file-upload-publisher"),
+    request: {
+      endpoint: Routes.photos(),
+      params: {
+        /* eslint-disable camelcase */
+        authenticity_token: $("meta[name='csrf-token']").attr("content"),
+        photo: {
+          aspect_ids: aspectIds,
+          /* eslint-enable camelcase */
+          pending: true
+        }
+      }
+    },
+    validation: {
+      allowedExtensions: ["jpg", "jpeg", "png", "gif", "tif", "tiff"],
+      sizeLimit: 4194304
+    },
+    button: document.getElementById("file-upload-publisher"),
 
-       onProgress: function(id, fileName, loaded, total){
-        var progress = Math.round(loaded / total * 100 );
-         $('#fileInfo-publisher').text(fileName + ' ' + progress + '%');
-       },
-
-       messages: {
-          typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
-          sizeError: Diaspora.I18n.t("photo_uploader.size_error"),
-          emptyError: Diaspora.I18n.t("photo_uploader.empty")
-       },
-
-       onSubmit: function(){
-        $('#file-upload-publisher').addClass("loading");
-        $('#publisher_textarea_wrapper').addClass("with_attachments");
-        $('#photodropzone').append(
+    callbacks: {
+      onProgress: function(id, fileName, loaded, total) {
+        var progress = Math.round(loaded / total * 100);
+        $("#fileInfo-publisher").text(fileName + " " + progress + "%");
+      },
+      onSubmit: function() {
+        $("#file-upload-publisher").addClass("loading");
+        $("#publisher_textarea_wrapper").addClass("with_attachments");
+        $("#photodropzone").append(
           "<li class='publisher_photo loading' style='position:relative;'>" +
-            "<img alt='Ajax-loader2' src='"+ImagePaths.get('ajax-loader2.gif')+"' />" +
+          "<img alt='Ajax-loader2' src='" + ImagePaths.get("ajax-loader2.gif") + "' />" +
           "</li>"
-          );
-       },
+        );
+      },
+      onComplete: function(_id, fileName, responseJSON) {
+        if (responseJSON.data === undefined) {
+          return;
+        }
 
-       onComplete: function(_id, fileName, responseJSON) {
-        $('#fileInfo-publisher').text(Diaspora.I18n.t("photo_uploader.completed", {'file': fileName}));
+        $("#fileInfo-publisher").text(Diaspora.I18n.t("photo_uploader.completed", {"file": fileName}));
         var id = responseJSON.data.photo.id,
             url = responseJSON.data.photo.unprocessed_image.url,
-            currentPlaceholder = $('li.loading').first();
+            currentPlaceholder = $("li.loading").first();
 
-        $('#publisher_textarea_wrapper').addClass("with_attachments");
-        $('#new_status_message').append("<input type='hidden' value='" + id + "' name='photos[]' />");
+        $("#publisher_textarea_wrapper").addClass("with_attachments");
+        $("#new_status_message").append("<input type='hidden' value='" + id + "' name='photos[]' />");
 
         // replace image placeholders
-        var img = currentPlaceholder.find('img');
-        img.attr('src', url);
-        img.attr('data-id', id);
-        currentPlaceholder.removeClass('loading');
+        var img = currentPlaceholder.find("img");
+        img.attr("src", url);
+        img.attr("data-id", id);
+        currentPlaceholder.removeClass("loading");
         currentPlaceholder.append("<div class='x'>X</div>" +
-            "<div class='circle'></div>");
-        ////
+          "<div class='circle'></div>");
 
-        var publisher = $('#publisher');
+        var publisher = $("#publisher");
 
-        publisher.find("input[type='submit']").removeAttr('disabled');
+        publisher.find("input[type='submit']").removeAttr("disabled");
 
-        $('.x').bind('click', function(){
-          var photo = $(this).closest('.publisher_photo');
+        $(".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){
-                                $('#publisher_textarea_wrapper').removeClass("with_attachments");
-                              }
-                            });
-                          }
-                  });
+          $.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) {
+                  $("#publisher_textarea_wrapper").removeClass("with_attachments");
+                }
+              });
+            }
+          });
         });
-       },
-
-       onAllComplete: function(){}
-   });
+      },
+      onError: function(id, name, errorReason) {
+        alert(errorReason);
+      }
+    },
+    messages: {
+      typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
+      sizeError: Diaspora.I18n.t("photo_uploader.size_error"),
+      emptyError: Diaspora.I18n.t("photo_uploader.empty")
+    }
+  });
 }
 window.addEventListener("load", function() {
   createUploader();
diff --git a/features/desktop/edits_profile.feature b/features/desktop/edits_profile.feature
index 9d07b58063..8eeb5bb9d0 100644
--- a/features/desktop/edits_profile.feature
+++ b/features/desktop/edits_profile.feature
@@ -42,8 +42,10 @@ Feature: editing your profile
     And I should see "#starwars" within "ul#as-selections-tags"
     And the "#profile_public_details" bootstrap-switch should be on
 
-    When I confirm the alert after I attach the file "spec/fixtures/bad_urls.txt" to "file" within "#file-upload"
-    And I attach the file "spec/fixtures/button.png" to hidden "file" within "#file-upload"
+    When I attach the file "spec/fixtures/bad_urls.txt" to "qqfile" within "#file-upload"
+    Then I should see a flash message indicating failure
+
+    When I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload"
     Then I should see "button.png completed"
     And I should see a "img" within "#profile_photo_upload"
 
diff --git a/features/mobile/edits_profile.feature b/features/mobile/edits_profile.feature
index 77a41ab7e3..402545bf2b 100644
--- a/features/mobile/edits_profile.feature
+++ b/features/mobile/edits_profile.feature
@@ -39,8 +39,8 @@ Feature: editing the profile in the mobile view
     Then I should see "#kamino" within "ul#as-selections-tags"
     And I should see "#starwars" within "ul#as-selections-tags"
 
-    When I confirm the alert after I attach the file "spec/fixtures/bad_urls.txt" to "file" within "#file-upload"
-    And I attach the file "spec/fixtures/button.png" to hidden "file" within "#file-upload"
+    When I confirm the alert after I attach the file "spec/fixtures/bad_urls.txt" to "qqfile" within "#file-upload"
+    And I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload"
     Then I should see "button.png completed"
     And I should see a "img" within "#profile_photo_upload"
 
diff --git a/features/mobile/getting_started.feature b/features/mobile/getting_started.feature
index 7714f8807a..1a8b240ecf 100644
--- a/features/mobile/getting_started.feature
+++ b/features/mobile/getting_started.feature
@@ -17,8 +17,8 @@ Feature: editing the getting started in the mobile view
     And I should not see "awesome_button"
 
   Scenario: new user adds a profile photo and tags
-    When I confirm the alert after I attach the file "spec/fixtures/bad_urls.txt" to "file" within "#file-upload"
-    And I attach the file "spec/fixtures/button.png" to hidden "file" within "#file-upload"
+    When I confirm the alert after I attach the file "spec/fixtures/bad_urls.txt" to "qqfile" within "#file-upload"
+    And I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload"
     Then I should see a "img" within "#profile_photo_upload"
 
     When I fill in "follow_tags" with "#men"
diff --git a/features/mobile/multiphoto.feature b/features/mobile/multiphoto.feature
index ddc79a2c16..c56b6989e3 100644
--- a/features/mobile/multiphoto.feature
+++ b/features/mobile/multiphoto.feature
@@ -10,7 +10,7 @@ Feature: viewing photos on the mobile main page
 
   Scenario: view full size image
     Given I visit the mobile publisher page
-    When I attach the file "spec/fixtures/button.png" to hidden "file" within "#file-upload-publisher"
+    When I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload-publisher"
     Then I should see "button.png completed"
     And I should see an uploaded image within the photo drop zone
 
@@ -23,9 +23,9 @@ Feature: viewing photos on the mobile main page
 
   Scenario: view multiphoto post
     Given I visit the mobile publisher page
-    When I attach the file "spec/fixtures/button.png" to hidden "file" within "#file-upload-publisher"
+    When I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload-publisher"
     Then I should see "button.png completed"
-    When I attach the file "spec/fixtures/button.gif" to hidden "file" within "#file-upload-publisher"
+    When I attach the file "spec/fixtures/button.gif" to hidden "qqfile" within "#file-upload-publisher"
     Then I should see "button.gif completed"
 
     When I press "Share"
diff --git a/features/mobile/posts_from_main_page.feature b/features/mobile/posts_from_main_page.feature
index d4c688d56b..22fb6195ad 100644
--- a/features/mobile/posts_from_main_page.feature
+++ b/features/mobile/posts_from_main_page.feature
@@ -30,7 +30,7 @@ Feature: posting from the mobile main page
 
   Scenario: post a photo without text
     Given I visit the mobile publisher page
-    When I attach the file "spec/fixtures/button.png" to hidden "file" within "#file-upload-publisher"
+    When I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload-publisher"
     Then I should see "button.png completed"
     And I should see an uploaded image within the photo drop zone
     When I press "Share"
@@ -43,9 +43,9 @@ Feature: posting from the mobile main page
 
   Scenario: back out of posting a photo-only post
     Given I visit the mobile publisher page
-    When I confirm the alert after I attach the file "spec/fixtures/bad_urls.txt" to "file" within "#file-upload-publisher"
+    When I confirm the alert after I attach the file "spec/fixtures/bad_urls.txt" to "qqfile" within "#file-upload-publisher"
     Then I should not see an uploaded image within the photo drop zone
-    When I attach the file "spec/fixtures/button.png" to hidden "file" within "#file-upload-publisher"
+    When I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload-publisher"
     And I should see "button.png completed"
     And I click to delete the first uploaded photo
     Then I should not see an uploaded image within the photo drop zone
@@ -53,8 +53,8 @@ Feature: posting from the mobile main page
   Scenario: back out of uploading a picture when another has been attached
     Given I visit the mobile publisher page
     And I append "I am eating yogurt" to the mobile publisher
-    And I attach the file "spec/fixtures/button.gif" to hidden "file" within "#file-upload-publisher"
-    And I attach the file "spec/fixtures/button.png" to hidden "file" within "#file-upload-publisher"
+    And I attach the file "spec/fixtures/button.gif" to hidden "qqfile" within "#file-upload-publisher"
+    And I attach the file "spec/fixtures/button.png" to hidden "qqfile" within "#file-upload-publisher"
     And I click to delete the first uploaded photo
     Then I should see an uploaded image within the photo drop zone
     And the text area wrapper mobile should be with attachments
diff --git a/features/support/publishing_cuke_helpers.rb b/features/support/publishing_cuke_helpers.rb
index 5d4ce7675a..046f8e3322 100644
--- a/features/support/publishing_cuke_helpers.rb
+++ b/features/support/publishing_cuke_helpers.rb
@@ -19,9 +19,9 @@ module PublishingCukeHelpers
   end
 
   def upload_file_with_publisher(path)
-    page.execute_script(%q{$("input[name='file']").css("opacity", '1');})
+    page.execute_script(%q{$("input[name='qqfile']").css("opacity", '1');})
     with_scope("#publisher_textarea_wrapper") do
-      attach_file("file", Rails.root.join(path).to_s)
+      attach_file("qqfile", Rails.root.join(path).to_s)
       # wait for the image to be ready
       page.assert_selector(".publisher_photo.loading", count: 0)
     end
diff --git a/lib/assets/javascripts/fileuploader-custom.js b/lib/assets/javascripts/fileuploader-custom.js
deleted file mode 100644
index 5b5879f526..0000000000
--- a/lib/assets/javascripts/fileuploader-custom.js
+++ /dev/null
@@ -1,1267 +0,0 @@
-// @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2-or-Later
-/**
- * http://github.com/valums/file-uploader
- *
- * Multiple file upload component with progress-bar, drag-and-drop.
- * © 2010 Andrew Valums ( andrew(at)valums.com )
- *
- * Licensed under GNU GPL 2 or later, see license.txt.
- * Modified by Diaspora
- */
-
-//
-// Helper functions
-//
-
-var qq = qq || {};
-
-/**
- * Adds all missing properties from second obj to first obj
- */
-qq.extend = function(first, second){
-    for (var prop in second){
-        first[prop] = second[prop];
-    }
-};
-
-/**
- * Searches for a given element in the array, returns -1 if it is not present.
- * @param {Number} [from] The index at which to begin the search
- */
-qq.indexOf = function(arr, elt, from){
-    if (arr.indexOf) { return arr.indexOf(elt, from); }
-
-    from = from || 0;
-    var len = arr.length;
-
-    if (from < 0) { from += len; }
-
-    for (; from < len; from++){
-        if (from in arr && arr[from] === elt){
-            return from;
-        }
-    }
-    return -1;
-};
-
-qq.getUniqueId = (function(){
-    var id = 0;
-    return function(){ return id++; };
-})();
-
-//
-// Events
-
-qq.attach = function(element, type, fn){
-    if (element.addEventListener){
-        element.addEventListener(type, fn, false);
-    } else if (element.attachEvent){
-        element.attachEvent('on' + type, fn);
-    }
-};
-qq.detach = function(element, type, fn){
-    if (element.removeEventListener){
-        element.removeEventListener(type, fn, false);
-    } else if (element.attachEvent){
-        element.detachEvent('on' + type, fn);
-    }
-};
-
-qq.preventDefault = function(e){
-    if (e.preventDefault){
-        e.preventDefault();
-    } else{
-        e.returnValue = false;
-    }
-};
-
-//
-// Node manipulations
-
-/**
- * Insert node a before node b.
- */
-qq.insertBefore = function(a, b){
-    b.parentNode.insertBefore(a, b);
-};
-qq.remove = function(element){
-    element.parentNode.removeChild(element);
-};
-
-qq.contains = function(parent, descendant){
-    // compareposition returns false in this case
-    if (parent == descendant) { return true; }
-
-    if (parent.contains){
-        return parent.contains(descendant);
-    } else {
-        return !!(descendant.compareDocumentPosition(parent) & 8);
-    }
-};
-
-/**
- * Creates and returns element from html string
- * Uses innerHTML to create an element
- */
-qq.toElement = (function(){
-    var div = document.createElement('div');
-    return function(html){
-        div.innerHTML = html;
-        var element = div.firstChild;
-        div.removeChild(element);
-        return element;
-    };
-})();
-
-//
-// Node properties and attributes
-
-/**
- * Sets styles for an element.
- * Fixes opacity in IE6-8.
- */
-qq.css = function(element, styles){
-    if (styles.opacity !== null){
-        if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
-            styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
-        }
-    }
-    qq.extend(element.style, styles);
-};
-qq.hasClass = function(element, name){
-    var re = new RegExp('(^| )' + name + '( |$)');
-    return re.test(element.className);
-};
-qq.addClass = function(element, name){
-    if (!qq.hasClass(element, name)){
-        element.className += ' ' + name;
-    }
-};
-qq.removeClass = function(element, name){
-    var re = new RegExp('(^| )' + name + '( |$)');
-    element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
-};
-qq.setText = function(element, text){
-    element.innerText = text;
-    element.textContent = text;
-};
-
-//
-// Selecting elements
-
-qq.children = function(element){
-    var children = [],
-    child = element.firstChild;
-
-    while (child){
-        if (child.nodeType == 1){
-            children.push(child);
-        }
-        child = child.nextSibling;
-    }
-
-    return children;
-};
-
-qq.getByClass = function(element, className){
-    if (element.querySelectorAll){
-        return element.querySelectorAll('.' + className);
-    }
-
-    var result = [];
-    var candidates = element.getElementsByTagName("*");
-    var len = candidates.length;
-
-    for (var i = 0; i < len; i++){
-        if (qq.hasClass(candidates[i], className)){
-            result.push(candidates[i]);
-        }
-    }
-    return result;
-};
-
-/**
- * obj2url() takes a json-object as argument and generates
- * a querystring. pretty much like jQuery.param()
- *
- * how to use:
- *
- *    `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
- *
- * will result in:
- *
- *    `http://any.url/upload?otherParam=value&a=b&c=d`
- *
- * @param  Object JSON-Object
- * @param  String current querystring-part
- * @return String encoded querystring
- */
-qq.obj2url = function(obj, temp, prefixDone){
-    var uristrings = [],
-        prefix = '&',
-        add = function(nextObj, i){
-            var nextTemp = temp
-                ? (/\[\]$/.test(temp)) // prevent double-encoding
-                   ? temp
-                   : temp+'['+i+']'
-                : i;
-            if ((nextTemp != 'undefined') && (i != 'undefined')) {
-                uristrings.push(
-                    (typeof nextObj === 'object')
-                        ? qq.obj2url(nextObj, nextTemp, true)
-                        : (Object.prototype.toString.call(nextObj) === '[object Function]')
-                            ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
-                            : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
-                );
-            }
-        };
-
-    if (!prefixDone && temp) {
-      prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
-      uristrings.push(temp);
-      uristrings.push(qq.obj2url(obj));
-    } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
-        // we wont use a for-in-loop on an array (performance)
-        for (var i = 0, len = obj.length; i < len; ++i){
-            add(obj[i], i);
-        }
-    } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
-        // for anything else but a scalar, we will use for-in-loop
-        for (var i in obj){
-            add(obj[i], i);
-        }
-    } else {
-        uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
-    }
-
-    return uristrings.join(prefix)
-                     .replace(/^&/, '')
-                     .replace(/%20/g, '+');
-};
-
-//
-//
-// Uploader Classes
-//
-//
-
-var qq = qq || {};
-
-/**
- * Creates upload button, validates upload, but doesn't create file list or dd.
- */
-qq.FileUploaderBasic = function(o){
-    this._options = {
-        // set to true to see the server response
-        debug: false,
-        action: '/server/upload',
-        params: {},
-        button: null,
-        multiple: true,
-        maxConnections: 3,
-        // validation
-        allowedExtensions: [],
-        sizeLimit: 0,
-        minSizeLimit: 0,
-        // events
-        // return false to cancel submit
-        onSubmit: function(id, fileName){},
-        onProgress: function(id, fileName, loaded, total){},
-        onComplete: function(id, fileName, responseJSON){},
-        onAllComplete: function(completed_files){},
-        onCancel: function(id, fileName){},
-        // messages
-        messages: {
-            typeError: "{file} has invalid extension. Only {extensions} are allowed.",
-            sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
-            minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
-            emptyError: "{file} is empty, please select files again without it.",
-            onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
-        },
-        showMessage: function(message){
-            alert(message);
-        }
-    };
-    qq.extend(this._options, o);
-
-    // number of files being uploaded
-    this._filesInProgress = 0;
-    this._handler = this._createUploadHandler();
-
-    if (this._options.button){
-        this._button = this._createUploadButton(this._options.button);
-    }
-
-    this._preventLeaveInProgress();
-};
-
-qq.FileUploaderBasic.prototype = {
-    setParams: function(params){
-        this._options.params = params;
-    },
-    getInProgress: function(){
-        return this._filesInProgress;
-    },
-    _createUploadButton: function(element){
-        var self = this;
-
-        return new qq.UploadButton({
-            element: element,
-            multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
-            onChange: function(input){
-                self._onInputChange(input);
-            }
-        });
-    },
-    _createUploadHandler: function(){
-        var self = this,
-            handlerClass;
-
-        if(qq.UploadHandlerXhr.isSupported()){
-            handlerClass = 'UploadHandlerXhr';
-        } else {
-            handlerClass = 'UploadHandlerForm';
-        }
-
-        var handler = new qq[handlerClass]({
-            debug: this._options.debug,
-            action: this._options.action,
-            maxConnections: this._options.maxConnections,
-            onProgress: function(id, fileName, loaded, total){
-                self._onProgress(id, fileName, loaded, total);
-                self._options.onProgress(id, fileName, loaded, total);
-            },
-            onComplete: function(id, fileName, result){
-                self._onComplete(id, fileName, result);
-                self._options.onComplete(id, fileName, result);
-            },
-            onAllComplete: function(completed_files){
-                self._options.onAllComplete(completed_files);
-            },
-            onCancel: function(id, fileName){
-                self._onCancel(id, fileName);
-                self._options.onCancel(id, fileName);
-            }
-        });
-
-        return handler;
-    },
-    _preventLeaveInProgress: function(){
-        var self = this;
-
-        qq.attach(window, 'beforeunload', function(e){
-            if (!self._filesInProgress){return;}
-
-            var e = e || window.event;
-            // for ie, ff
-            e.returnValue = self._options.messages.onLeave;
-            // for webkit
-            return self._options.messages.onLeave;
-        });
-    },
-    _onSubmit: function(id, fileName){
-        this._filesInProgress++;
-    },
-    _onProgress: function(id, fileName, loaded, total){
-    },
-    _onComplete: function(id, fileName, result){
-        this._filesInProgress--;
-        if (result.error){
-            this._options.showMessage(result.error);
-        }
-    },
-    _onCancel: function(id, fileName){
-        this._filesInProgress--;
-    },
-    _onInputChange: function(input){
-        if (this._handler instanceof qq.UploadHandlerXhr){
-            this._uploadFileList(input.files);
-        } else {
-            if (this._validateFile(input)){
-                this._uploadFile(input);
-            }
-        }
-        this._button.reset();
-    },
-    _uploadFileList: function(files){
-        for (var i=0; i<files.length; i++){
-            if ( !this._validateFile(files[i])){
-                return;
-            }
-        }
-
-        for (var i=0; i<files.length; i++){
-            this._uploadFile(files[i]);
-        }
-    },
-    _uploadFile: function(fileContainer){
-        var id = this._handler.add(fileContainer);
-        var fileName = this._handler.getName(id);
-
-        if (this._options.onSubmit(id, fileName) !== false){
-            this._onSubmit(id, fileName);
-            this._handler.upload(id, this._options.params);
-        }
-    },
-    _validateFile: function(file){
-        var name, size;
-
-        if (file.value){
-            // it is a file input
-            // get input value and remove path to normalize
-            name = file.value.replace(/.*(\/|\\)/, "");
-        } else {
-            // fix missing properties in Safari
-            name = file.fileName != null ? file.fileName : file.name;
-            size = file.fileSize != null ? file.fileSize : file.size;
-        }
-
-        if (! this._isAllowedExtension(name)){
-            this._error('typeError', name);
-            return false;
-
-        } else if (size === 0){
-            this._error('emptyError', name);
-            return false;
-
-        } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){
-            this._error('sizeError', name);
-            return false;
-
-        } else if (size && size < this._options.minSizeLimit){
-            this._error('minSizeError', name);
-            return false;
-        }
-
-        return true;
-    },
-    _error: function(code, fileName){
-        var message = this._options.messages[code];
-        function r(name, replacement){ message = message.replace(name, replacement); }
-
-        r('{file}', this._formatFileName(fileName));
-        r('{extensions}', this._options.allowedExtensions.join(', '));
-        r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
-        r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
-
-        this._options.showMessage(message);
-    },
-    _formatFileName: function(name){
-        if (name.length > 33){
-            name = name.slice(0, 19) + '...' + name.slice(-13);
-        }
-        return name;
-    },
-    _isAllowedExtension: function(fileName){
-        var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
-        var allowed = this._options.allowedExtensions;
-
-        if (!allowed.length){return true;}
-
-        for (var i=0; i<allowed.length; i++){
-            if (allowed[i].toLowerCase() == ext){ return true;}
-        }
-
-        return false;
-    },
-    _formatSize: function(bytes){
-        var i = -1;
-        do {
-            bytes = bytes / 1024;
-            i++;
-        } while (bytes > 99);
-
-        return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
-    }
-};
-
-
-/**
- * Class that creates upload widget with drag-and-drop and file list
- * @inherits qq.FileUploaderBasic
- */
-qq.FileUploader = function(o){
-    // call parent constructor
-    qq.FileUploaderBasic.apply(this, arguments);
-
-    // additional options
-    qq.extend(this._options, {
-        element: null,
-        // if set, will be used instead of qq-upload-list in template
-        listElement: null,
-
-        template: '<div class="qq-uploader">' +
-                '<div class="qq-upload-drop-area"><span>Drop files here to upload</span></div>' +
-                '<div class="qq-upload-button">Upload a file</div>' +
-                '<ul class="qq-upload-list"></ul>' +
-             '</div>',
-
-        // template for one item in file list
-        fileTemplate: '<li>' +
-                '<span class="qq-upload-file"></span>' +
-                '<span class="qq-upload-spinner"></span>' +
-                '<span class="qq-upload-size"></span>' +
-                '<a class="qq-upload-cancel" href="#">Cancel</a>' +
-                '<span class="qq-upload-failed-text">Failed</span>' +
-            '</li>',
-
-        classes: {
-            // used to get elements from templates
-            button: 'qq-upload-button',
-            drop: 'qq-upload-drop-area',
-            dropActive: 'qq-upload-drop-area-active',
-            list: 'qq-upload-list',
-
-            file: 'qq-upload-file',
-            spinner: 'qq-upload-spinner',
-            size: 'qq-upload-size',
-            cancel: 'qq-upload-cancel',
-
-            // added to list item when upload completes
-            // used in css to hide progress spinner
-            success: 'qq-upload-success',
-            fail: 'qq-upload-fail'
-        }
-    });
-    // overwrite options with user supplied
-    qq.extend(this._options, o);
-
-    this._element = this._options.element;
-    this._element.innerHTML = this._options.template;
-    this._listElement = this._options.listElement || this._find(this._element, 'list');
-
-    this._classes = this._options.classes;
-
-    this._button = this._createUploadButton(this._find(this._element, 'button'));
-
-    this._bindCancelEvent();
-    this._setupDragDrop();
-};
-
-// inherit from Basic Uploader
-qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
-
-qq.extend(qq.FileUploader.prototype, {
-    /**
-     * Gets one of the elements listed in this._options.classes
-     **/
-    _find: function(parent, type){
-        var element = qq.getByClass(parent, this._options.classes[type])[0];
-        if (!element){
-            throw new Error('element not found ' + type);
-        }
-
-        return element;
-    },
-    _setupDragDrop: function(){
-        var self = this,
-            dropArea = this._find(this._element, 'drop');
-
-        var dz = new qq.UploadDropZone({
-            element: dropArea,
-            onEnter: function(e){
-                qq.addClass(dropArea, self._classes.dropActive);
-                e.stopPropagation();
-            },
-            onLeave: function(e){
-                e.stopPropagation();
-            },
-            onLeaveNotDescendants: function(e){
-                qq.removeClass(dropArea, self._classes.dropActive);
-            },
-            onDrop: function(e){
-                dropArea.style.display = 'none';
-                qq.removeClass(dropArea, self._classes.dropActive);
-                self._uploadFileList(e.dataTransfer.files);
-            }
-        });
-
-        dropArea.style.display = 'none';
-
-        qq.attach(document, 'dragenter', function(e){
-            if (!dz._isValidFileDrag(e)) return;
-
-            dropArea.style.display = 'block';
-        });
-        qq.attach(document, 'dragleave', function(e){
-            if (!dz._isValidFileDrag(e)) return;
-
-            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
-            // only fire when leaving document out
-            if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){
-                dropArea.style.display = 'none';
-            }
-        });
-    },
-    _onSubmit: function(id, fileName){
-        qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
-        this._addToList(id, fileName);
-    },
-    _onProgress: function(id, fileName, loaded, total){
-        qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
-
-        var item = this._getItemByFileId(id);
-        var size = this._find(item, 'size');
-        size.style.display = 'inline';
-
-        var text;
-        if (loaded != total){
-            text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
-        } else {
-            text = this._formatSize(total);
-        }
-
-        qq.setText(size, text);
-    },
-    _onComplete: function(id, fileName, result){
-        qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
-
-        // mark completed
-        var item = this._getItemByFileId(id);
-        qq.remove(this._find(item, 'cancel'));
-        qq.remove(this._find(item, 'spinner'));
-
-        if (result.success){
-            qq.addClass(item, this._classes.success);
-        } else {
-            qq.addClass(item, this._classes.fail);
-        }
-    },
-    _addToList: function(id, fileName){
-        var item = qq.toElement(this._options.fileTemplate);
-        item.qqFileId = id;
-
-        var fileElement = this._find(item, 'file');
-        qq.setText(fileElement, this._formatFileName(fileName));
-        this._find(item, 'size').style.display = 'none';
-
-        this._listElement.appendChild(item);
-    },
-    _getItemByFileId: function(id){
-        var item = this._listElement.firstChild;
-
-        // there can't be txt nodes in dynamically created list
-        // and we can  use nextSibling
-        while (item){
-            if (item.qqFileId == id) return item;
-            item = item.nextSibling;
-        }
-    },
-    /**
-     * delegate click event for cancel link
-     **/
-    _bindCancelEvent: function(){
-        var self = this,
-            list = this._listElement;
-
-        qq.attach(list, 'click', function(e){
-            e = e || window.event;
-            var target = e.target || e.srcElement;
-
-            if (qq.hasClass(target, self._classes.cancel)){
-                qq.preventDefault(e);
-
-                var item = target.parentNode;
-                self._handler.cancel(item.qqFileId);
-                qq.remove(item);
-            }
-        });
-    }
-});
-
-qq.UploadDropZone = function(o){
-    this._options = {
-        element: null,
-        onEnter: function(e){},
-        onLeave: function(e){},
-        // is not fired when leaving element by hovering descendants
-        onLeaveNotDescendants: function(e){},
-        onDrop: function(e){}
-    };
-    qq.extend(this._options, o);
-
-    this._element = this._options.element;
-
-    this._disableDropOutside();
-    this._attachEvents();
-};
-
-qq.UploadDropZone.prototype = {
-    _disableDropOutside: function(e){
-        // run only once for all instances
-        if (!qq.UploadDropZone.dropOutsideDisabled ){
-
-            qq.attach(document, 'dragover', function(e){
-                if (e.dataTransfer){
-                    e.dataTransfer.dropEffect = 'none';
-                    e.preventDefault();
-                }
-            });
-
-            qq.UploadDropZone.dropOutsideDisabled = true;
-        }
-    },
-    _attachEvents: function(){
-        var self = this;
-
-        qq.attach(self._element, 'dragover', function(e){
-            if (!self._isValidFileDrag(e)) return;
-
-            var effect = e.dataTransfer.effectAllowed;
-            if (effect == 'move' || effect == 'linkMove'){
-                e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
-            } else {
-                e.dataTransfer.dropEffect = 'copy'; // for Chrome
-            }
-
-            e.stopPropagation();
-            e.preventDefault();
-        });
-
-        qq.attach(self._element, 'dragenter', function(e){
-            if (!self._isValidFileDrag(e)) return;
-
-            self._options.onEnter(e);
-        });
-
-        qq.attach(self._element, 'dragleave', function(e){
-            if (!self._isValidFileDrag(e)) return;
-
-            self._options.onLeave(e);
-
-            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
-            // do not fire when moving a mouse over a descendant
-            if (qq.contains(this, relatedTarget)) return;
-
-            self._options.onLeaveNotDescendants(e);
-        });
-
-        qq.attach(self._element, 'drop', function(e){
-            if (!self._isValidFileDrag(e)) return;
-
-            e.preventDefault();
-            self._options.onDrop(e);
-        });
-    },
-    _isValidFileDrag: function(e){
-        var dt = e.dataTransfer,
-            // do not check dt.types.contains in webkit, because it crashes safari 4
-            isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;
-
-        // dt.effectAllowed is none in Safari 5
-        // dt.types.contains check is for firefox
-        return dt && dt.effectAllowed != 'none' &&
-            (dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));
-
-    }
-};
-
-qq.UploadButton = function(o){
-    this._options = {
-        element: null,
-        // if set to true adds multiple attribute to file input
-        multiple: false,
-        // name attribute of file input
-        name: 'file',
-        onChange: function(input){},
-        hoverClass: 'qq-upload-button-hover',
-        focusClass: 'qq-upload-button-focus'
-    };
-
-    qq.extend(this._options, o);
-
-    this._element = this._options.element;
-
-    // make button suitable container for input
-    qq.css(this._element, {
-        position: 'relative',
-        overflow: 'hidden',
-        // Make sure browse button is in the right side
-        // in Internet Explorer
-        direction: 'ltr'
-    });
-
-    this._input = this._createInput();
-};
-
-qq.UploadButton.prototype = {
-    /* returns file input element */
-    getInput: function(){
-        return this._input;
-    },
-    /* cleans/recreates the file input */
-    reset: function(){
-        if (this._input.parentNode){
-            qq.remove(this._input);
-        }
-
-        qq.removeClass(this._element, this._options.focusClass);
-        this._input = this._createInput();
-    },
-    _createInput: function(){
-        var input = document.createElement("input");
-
-        if (this._options.multiple){
-            input.setAttribute("multiple", "multiple");
-        }
-
-        input.setAttribute("type", "file");
-        input.setAttribute("name", this._options.name);
-
-        qq.css(input, {
-            position: 'absolute',
-            // in Opera only 'browse' button
-            // is clickable and it is located at
-            // the right side of the input
-            right: 0,
-            top: 0,
-            //fontFamily: 'Arial',
-            // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
-            //fontSize: '118px',
-            margin: 0,
-            padding: 0,
-            opacity: 0
-        });
-
-        this._element.appendChild(input);
-
-        var self = this;
-        qq.attach(input, 'change', function(){
-            self._options.onChange(input);
-        });
-
-        qq.attach(input, 'mouseover', function(){
-            qq.addClass(self._element, self._options.hoverClass);
-        });
-        qq.attach(input, 'mouseout', function(){
-            qq.removeClass(self._element, self._options.hoverClass);
-        });
-        qq.attach(input, 'focus', function(){
-            qq.addClass(self._element, self._options.focusClass);
-        });
-        qq.attach(input, 'blur', function(){
-            qq.removeClass(self._element, self._options.focusClass);
-        });
-
-        // IE and Opera, unfortunately have 2 tab stops on file input
-        // which is unacceptable in our case, disable keyboard access
-        if (window.attachEvent){
-            // it is IE or Opera
-            input.setAttribute('tabIndex', "-1");
-        }
-
-        return input;
-    }
-};
-
-/**
- * Class for uploading files, uploading itself is handled by child classes
- */
-qq.UploadHandlerAbstract = function(o){
-    this._options = {
-        debug: false,
-        action: '/upload.php',
-        // maximum number of concurrent uploads
-        maxConnections: 999,
-        onProgress: function(id, fileName, loaded, total){},
-        onComplete: function(id, fileName, response){},
-        onAllComplete: function(completed_files){},
-        onCancel: function(id, fileName){}
-    };
-    qq.extend(this._options, o);
-
-    this._queue = [];
-    // params for files in queue
-    this._params = [];
-    this._completed_files = [];
-};
-qq.UploadHandlerAbstract.prototype = {
-    log: function(str){
-        if (this._options.debug && window.console) console.log('[uploader] ' + str);
-    },
-    /**
-     * Adds file or file input to the queue
-     * @returns id
-     **/
-    add: function(file){},
-    /**
-     * Sends the file identified by id and additional query params to the server
-     */
-    upload: function(id, params){
-        var len = this._queue.push(id);
-
-        var copy = {};
-        qq.extend(copy, params);
-        this._params[id] = copy;
-
-        // if too many active uploads, wait...
-        if (len <= this._options.maxConnections){
-            this._upload(id, this._params[id]);
-        }
-    },
-    /**
-     * Cancels file upload by id
-     */
-    cancel: function(id){
-        this._cancel(id);
-        this._dequeue(id);
-    },
-    /**
-     * Cancells all uploads
-     */
-    cancelAll: function(){
-        for (var i=0; i<this._queue.length; i++){
-            this._cancel(this._queue[i]);
-        }
-        this._queue = [];
-    },
-    /**
-     * Returns name of the file identified by id
-     */
-    getName: function(id){},
-    /**
-     * Returns size of the file identified by id
-     */
-    getSize: function(id){},
-    /**
-     * Returns id of files being uploaded or
-     * waiting for their turn
-     */
-    getQueue: function(){
-        return this._queue;
-    },
-    /**
-     * Actual upload method
-     */
-    _upload: function(id){},
-    /**
-     * Actual cancel method
-     */
-    _cancel: function(id){},
-    /**
-     * Removes element from queue, starts upload of next
-     */
-    _dequeue: function(id){
-        var i = qq.indexOf(this._queue, id);
-        this._queue.splice(i, 1);
-
-        var max = this._options.maxConnections;
-
-        if (this._queue.length >= max){
-            var nextId = this._queue[max-1];
-            this._upload(nextId, this._params[nextId]);
-        }
-
-        if (this._queue.length == 0){
-            this._onAllComplete();
-        }
-    },
-    _onAllComplete: function(){
-        this._options.onAllComplete(this._completed_files);
-    }
-};
-
-/**
- * Class for uploading files using form and iframe
- * @inherits qq.UploadHandlerAbstract
- */
-qq.UploadHandlerForm = function(o){
-    qq.UploadHandlerAbstract.apply(this, arguments);
-
-    this._inputs = {};
-};
-// @inherits qq.UploadHandlerAbstract
-qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
-
-qq.extend(qq.UploadHandlerForm.prototype, {
-    add: function(fileInput){
-        fileInput.setAttribute('name', 'qqfile');
-        var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
-
-        this._inputs[id] = fileInput;
-
-        // remove file input from DOM
-        if (fileInput.parentNode){
-            qq.remove(fileInput);
-        }
-
-        return id;
-    },
-    getName: function(id){
-        // get input value and remove path to normalize
-        return this._inputs[id].value.replace(/.*(\/|\\)/, "");
-    },
-    _cancel: function(id){
-        this._options.onCancel(id, this.getName(id));
-
-        delete this._inputs[id];
-
-        var iframe = document.getElementById(id);
-        if (iframe){
-            // to cancel request set src to something else
-            // we use src="javascript:false;" because it doesn't
-            // trigger ie6 prompt on https
-            iframe.setAttribute('src', 'javascript:false;');
-
-            qq.remove(iframe);
-        }
-    },
-    _upload: function(id, params){
-        var input = this._inputs[id];
-
-        if (!input){
-            throw new Error('file with passed id was not added, or already uploaded or cancelled');
-        }
-
-        var fileName = this.getName(id);
-
-        var iframe = this._createIframe(id);
-        var form = this._createForm(iframe, params);
-        form.appendChild(input);
-        $(form).append($('<input type="hidden" name="authenticity_token" value="'+$("meta[name='csrf-token']").attr("content")+'"/>'));
-
-        var self = this;
-        this._attachLoadEvent(iframe, function(){
-            self.log('iframe loaded');
-
-            var response = self._getIframeContentJSON(iframe);
-
-            self._options.onComplete(id, fileName, response);
-            self._dequeue(id);
-
-            delete self._inputs[id];
-            // timeout added to fix busy state in FF3.6
-            setTimeout(function(){
-                qq.remove(iframe);
-            }, 1);
-        });
-
-        form.submit();
-        qq.remove(form);
-
-        return id;
-    },
-    _attachLoadEvent: function(iframe, callback){
-        qq.attach(iframe, 'load', function(){
-            // when we remove iframe from dom
-            // the request stops, but in IE load
-            // event fires
-            if (!iframe.parentNode){
-                return;
-            }
-
-            // fixing Opera 10.53
-            if (iframe.contentDocument &&
-                iframe.contentDocument.body &&
-                iframe.contentDocument.body.innerHTML == "false"){
-                // In Opera event is fired second time
-                // when body.innerHTML changed from false
-                // to server response approx. after 1 sec
-                // when we upload file with iframe
-                return;
-            }
-
-            callback();
-        });
-    },
-    /**
-     * Returns json object received by iframe from server.
-     */
-    _getIframeContentJSON: function(iframe){
-        // iframe.contentWindow.document - for IE<7
-        var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
-            response;
-
-        this.log("converting iframe's innerHTML to JSON");
-        this.log("innerHTML = " + doc.body.innerHTML);
-
-        try {
-            response = eval("(" + doc.body.innerHTML + ")");
-        } catch(err){
-            response = {};
-        }
-
-        return response;
-    },
-    /**
-     * Creates iframe with unique name
-     */
-    _createIframe: function(id){
-        // We can't use following code as the name attribute
-        // won't be properly registered in IE6, and new window
-        // on form submit will open
-        // var iframe = document.createElement('iframe');
-        // iframe.setAttribute('name', id);
-
-        var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
-        // src="javascript:false;" removes ie6 prompt on https
-
-        iframe.setAttribute('id', id);
-
-        iframe.style.display = 'none';
-        document.body.appendChild(iframe);
-
-        return iframe;
-    },
-    /**
-     * Creates form, that will be submitted to iframe
-     */
-    _createForm: function(iframe, params){
-        // We can't use the following code in IE6
-        // var form = document.createElement('form');
-        // form.setAttribute('method', 'post');
-        // form.setAttribute('enctype', 'multipart/form-data');
-        // Because in this case file won't be attached to request
-        var form = qq.toElement('<form method="post" enctype="multipart/form-data"></form>');
-
-        var queryString = qq.obj2url(params, this._options.action);
-
-        form.setAttribute('action', queryString);
-        form.setAttribute('target', iframe.name);
-        form.style.display = 'none';
-        document.body.appendChild(form);
-
-        return form;
-    }
-});
-
-/**
- * Class for uploading files using xhr
- * @inherits qq.UploadHandlerAbstract
- */
-qq.UploadHandlerXhr = function(o){
-    qq.UploadHandlerAbstract.apply(this, arguments);
-
-    this._files = [];
-    this._xhrs = [];
-
-    // current loaded size in bytes for each file
-    this._loaded = [];
-};
-
-// static method
-qq.UploadHandlerXhr.isSupported = function(){
-    var input = document.createElement('input');
-    input.type = 'file';
-
-    return (
-        'multiple' in input &&
-        typeof File != "undefined" &&
-        typeof (new XMLHttpRequest()).upload != "undefined" );
-};
-
-// @inherits qq.UploadHandlerAbstract
-qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
-
-qq.extend(qq.UploadHandlerXhr.prototype, {
-    /**
-     * Adds file to the queue
-     * Returns id to use with upload, cancel
-     **/
-    add: function(file){
-        if (!(file instanceof File)){
-            throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
-        }
-
-        return this._files.push(file) - 1;
-    },
-    getName: function(id){
-        var file = this._files[id];
-        // fix missing name in Safari 4
-        return file.fileName != null ? file.fileName : file.name;
-    },
-    getSize: function(id){
-        var file = this._files[id];
-        return file.fileSize != null ? file.fileSize : file.size;
-    },
-    /**
-     * Returns uploaded bytes for file identified by id
-     */
-    getLoaded: function(id){
-        return this._loaded[id] || 0;
-    },
-    /**
-     * Sends the file identified by id and additional query params to the server
-     * @param {Object} params name-value string pairs
-     */
-    _upload: function(id, params){
-        var file = this._files[id],
-            name = this.getName(id),
-            size = this.getSize(id);
-
-        this._loaded[id] = 0;
-
-        var xhr = this._xhrs[id] = new XMLHttpRequest();
-        var self = this;
-
-        xhr.upload.onprogress = function(e){
-            if (e.lengthComputable){
-                self._loaded[id] = e.loaded;
-                self._options.onProgress(id, name, e.loaded, e.total);
-            }
-        };
-
-        xhr.onreadystatechange = function(){
-            if (xhr.readyState == 4){
-                self._onComplete(id, xhr);
-            }
-        };
-
-        // build query string
-        params = params || {};
-        params['qqfile'] = name;
-        var queryString = qq.obj2url(params, this._options.action);
-
-        xhr.open("POST", queryString, true);
-        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-        xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
-        xhr.setRequestHeader("Content-Type", "application/octet-stream");
-        xhr.setRequestHeader("X-CSRF-Token", $("meta[name='csrf-token']").attr("content"));
-        xhr.setRequestHeader("Accept", "application/json");
-        xhr.send(file);
-    },
-    _onComplete: function(id, xhr){
-        // the request was aborted/cancelled
-        if (!this._files[id]) return;
-
-        var name = this.getName(id);
-        var size = this.getSize(id);
-
-        this._options.onProgress(id, name, size, size);
-
-        if (xhr.status == 200){
-            this.log("xhr - server response received");
-            this.log("responseText = " + xhr.responseText);
-
-            var response;
-
-            try {
-                response = eval("(" + xhr.responseText + ")");
-            } catch(err){
-                response = {};
-            }
-
-            this._completed_files.push({file: this._files[id], response: response});
-            this._options.onComplete(id, name, response);
-
-        } else {
-            this._completed_files.push({file: this._files[id], response: {}});
-            this._options.onComplete(id, name, {});
-        }
-
-        this._files[id] = null;
-        this._xhrs[id] = null;
-        this._dequeue(id);
-    },
-    _cancel: function(id){
-        this._options.onCancel(id, this.getName(id));
-
-        this._files[id] = null;
-
-        if (this._xhrs[id]){
-            this._xhrs[id].abort();
-            this._xhrs[id] = null;
-        }
-    }
-});
-// @license-end
diff --git a/spec/javascripts/app/views/publisher_view_spec.js b/spec/javascripts/app/views/publisher_view_spec.js
index fb527b1869..c672b33fae 100644
--- a/spec/javascripts/app/views/publisher_view_spec.js
+++ b/spec/javascripts/app/views/publisher_view_spec.js
@@ -512,11 +512,11 @@ describe("app.views.Publisher", function() {
       );
     });
 
-    it('initializes the file uploader plugin', function() {
-      spyOn(qq, 'FileUploaderBasic');
+    it("initializes the FineUploader plugin", function() {
+      spyOn(qq, "FineUploaderBasic");
       new app.views.Publisher();
 
-      expect(qq.FileUploaderBasic).toHaveBeenCalled();
+      expect(qq.FineUploaderBasic).toHaveBeenCalled();
     });
 
     context('event handlers', function() {
-- 
GitLab