diff --git a/app/assets/javascripts/discourse/lib/utilities.js b/app/assets/javascripts/discourse/lib/utilities.js
index 81afac78a6c..243910e019b 100644
--- a/app/assets/javascripts/discourse/lib/utilities.js
+++ b/app/assets/javascripts/discourse/lib/utilities.js
@@ -164,7 +164,9 @@ Discourse.Utilities = {
var upload = files[0];
// CHROME ONLY: if the image was pasted, sets its name to a default one
- if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; }
+ if (typeof Blob !== "undefined" && typeof File !== "undefined") {
+ if (upload instanceof Blob && !(upload instanceof File) && upload.type === "image/png") { upload.name = "blob.png"; }
+ }
var type = Discourse.Utilities.isAnImage(upload.name) ? 'image' : 'attachment';
@@ -287,7 +289,7 @@ Discourse.Utilities = {
// deal with meaningful errors first
if (data.jqXHR) {
switch (data.jqXHR.status) {
- // cancel from the user
+ // cancelled by the user
case 0: return;
// entity too large, usually returned from the web server
diff --git a/app/assets/javascripts/discourse/mixins/upload.js.es6 b/app/assets/javascripts/discourse/mixins/upload.js.es6
index f9b98024f4e..dceab04b34b 100644
--- a/app/assets/javascripts/discourse/mixins/upload.js.es6
+++ b/app/assets/javascripts/discourse/mixins/upload.js.es6
@@ -11,17 +11,15 @@ export default Em.Mixin.create({
},
_initializeUploader: function() {
- // NOTE: we can't cache this as fileupload replaces the input after upload
- // cf. https://github.com/blueimp/jQuery-File-Upload/wiki/Frequently-Asked-Questions#why-is-the-file-input-field-cloned-and-replaced-after-each-selection
- var $upload = this.$('input[type=file]'),
- self = this;
+ var $upload = this.$(),
+ self = this,
+ csrf = Discourse.Session.currentProp("csrfToken");
$upload.fileupload({
- url: this.get('uploadUrl'),
+ url: this.get('uploadUrl') + ".json?authenticity_token=" + encodeURIComponent(csrf),
dataType: "json",
- fileInput: $upload,
- dropZone: this.$(),
- pasteZone: this.$()
+ dropZone: $upload,
+ pasteZone: $upload
});
$upload.on('fileuploadsubmit', function (e, data) {
@@ -39,14 +37,20 @@ export default Em.Mixin.create({
});
$upload.on("fileuploaddone", function(e, data) {
- if(data.result.url) {
- self.uploadDone(data);
- } else {
- if (data.result.message) {
- bootbox.alert(data.result.message);
+ if (data.result) {
+ if (data.result.url) {
+ self.uploadDone(data);
} else {
- bootbox.alert(I18n.t('post.errors.upload'));
+ if (data.result.message) {
+ bootbox.alert(data.result.message);
+ } else if (data.result.length > 0) {
+ bootbox.alert(data.result.join("\n"));
+ } else {
+ bootbox.alert(I18n.t('post.errors.upload'));
+ }
}
+ } else {
+ bootbox.alert(I18n.t('post.errors.upload'));
}
});
@@ -60,12 +64,9 @@ export default Em.Mixin.create({
}.on('didInsertElement'),
_destroyUploader: function() {
- this.$('input[type=file]').fileupload('destroy');
- }.on('willDestroyElement'),
-
- actions: {
- selectFile: function() {
- this.$('input[type=file]').click();
- }
- }
+ var $upload = this.$();
+ try { $upload.fileupload('destroy'); }
+ catch (e) { /* wasn't initialized yet */ }
+ $upload.off();
+ }.on('willDestroyElement')
});
diff --git a/app/assets/javascripts/discourse/templates/components/avatar-uploader.hbs b/app/assets/javascripts/discourse/templates/components/avatar-uploader.hbs
index 028b30eab56..21761c67bd7 100644
--- a/app/assets/javascripts/discourse/templates/components/avatar-uploader.hbs
+++ b/app/assets/javascripts/discourse/templates/components/avatar-uploader.hbs
@@ -1,7 +1,7 @@
-
-
+
{{#if uploading}}
{{i18n 'upload_selector.uploading'}} {{view.uploadProgress}}%
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs b/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs
index 8d0166e7362..8d9b297cf92 100644
--- a/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs
+++ b/app/assets/javascripts/discourse/templates/components/emoji-uploader.hbs
@@ -1,6 +1,6 @@
{{text-field name="name" placeholderKey="admin.emoji.name" value=name}}
-
-
+
+
diff --git a/app/assets/javascripts/discourse/templates/components/image-uploader.hbs b/app/assets/javascripts/discourse/templates/components/image-uploader.hbs
index ccb6a03ae2f..32c0dce1299 100644
--- a/app/assets/javascripts/discourse/templates/components/image-uploader.hbs
+++ b/app/assets/javascripts/discourse/templates/components/image-uploader.hbs
@@ -1,7 +1,9 @@
-
-
+
{{#if backgroundStyle}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/views/composer.js.es6 b/app/assets/javascripts/discourse/views/composer.js.es6
index be2a8c14cc9..7fe4ec8efde 100644
--- a/app/assets/javascripts/discourse/views/composer.js.es6
+++ b/app/assets/javascripts/discourse/views/composer.js.es6
@@ -307,11 +307,14 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
// in case it's still bound somehow
this._unbindUploadTarget();
- var $uploadTarget = $('#reply-control');
+ var $uploadTarget = $('#reply-control'),
+ csrf = Discourse.Session.currentProp('csrfToken'),
+ cancelledByTheUser;
+ // NOTE: we need both the .json extension and the CSRF token as a query parameter for IE9
$uploadTarget.fileupload({
- url: Discourse.getURL('/uploads'),
- dataType: 'json',
+ url: Discourse.getURL('/uploads.json?authenticity_token=' + encodeURIComponent(csrf)),
+ dataType: 'json'
});
// submit - this event is triggered for each upload
@@ -324,22 +327,27 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
// send - this event is triggered when the upload request is about to start
$uploadTarget.on('fileuploadsend', function (e, data) {
+ cancelledByTheUser = false;
// hide the "file selector" modal
self.get('controller').send('closeModal');
- // cf. https://github.com/blueimp/jQuery-File-Upload/wiki/API#how-to-cancel-an-upload
- var jqXHR = data.xhr();
- // need to wait for the link to show up in the DOM
- Em.run.schedule('afterRender', function() {
- // bind on the click event on the cancel link
- $('#cancel-file-upload').on('click', function() {
- // cancel the upload
- self.set('isUploading', false);
- // NOTE: this might trigger a 'fileuploadfail' event with status = 0
- if (jqXHR) jqXHR.abort();
- // unbind
- $(this).off('click');
- });
- });
+ // NOTE: IE9 doesn't support XHR
+ if (data["xhr"]) {
+ var jqHXR = data.xhr();
+ if (jqHXR) {
+ // need to wait for the link to show up in the DOM
+ Em.run.schedule('afterRender', function() {
+ // bind on the click event on the cancel link
+ $('#cancel-file-upload').on('click', function() {
+ // cancel the upload
+ self.set('isUploading', false);
+ // NOTE: this might trigger a 'fileuploadfail' event with status = 0
+ if (jqHXR) { cancelledByTheUser = true; jqHXR.abort(); }
+ // unbind
+ $(this).off('click');
+ });
+ });
+ }
+ }
});
// progress all
@@ -350,14 +358,17 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
// done
$uploadTarget.on('fileuploaddone', function (e, data) {
- // make sure we have a url
- if (data.result.url) {
- var markdown = Discourse.Utilities.getUploadMarkdown(data.result);
- // appends a space at the end of the inserted markdown
- self.addMarkdown(markdown + " ");
- self.set('isUploading', false);
- } else {
- bootbox.alert(I18n.t('post.errors.upload'));
+ if (!cancelledByTheUser) {
+ // make sure we have a url
+ if (data.result.url) {
+ var markdown = Discourse.Utilities.getUploadMarkdown(data.result);
+ // appends a space at the end of the inserted markdown
+ self.addMarkdown(markdown + " ");
+ self.set('isUploading', false);
+ } else {
+ // display the error message sent by the server
+ bootbox.alert(data.result.join("\n"));
+ }
}
});
@@ -365,8 +376,10 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
$uploadTarget.on('fileuploadfail', function (e, data) {
// hide upload status
self.set('isUploading', false);
- // display an error message
- Discourse.Utilities.displayErrorForUpload(data);
+ if (!cancelledByTheUser) {
+ // display an error message
+ Discourse.Utilities.displayErrorForUpload(data);
+ }
});
// contenteditable div hack for getting image paste to upload working in
diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss
index 5c9032b7423..ca0af4639d0 100644
--- a/app/assets/stylesheets/desktop/compose.scss
+++ b/app/assets/stylesheets/desktop/compose.scss
@@ -81,6 +81,9 @@
margin-left: 10px;
}
+// hide cancel upload link on IE9 (not supported)
+.ie9 #cancel-file-upload { display: none; }
+
#reply-control {
.toggle-preview, #draft-status, #file-uploading {
position: absolute;
diff --git a/app/assets/stylesheets/desktop/upload.scss b/app/assets/stylesheets/desktop/upload.scss
index 80d0667cacf..431130be68d 100644
--- a/app/assets/stylesheets/desktop/upload.scss
+++ b/app/assets/stylesheets/desktop/upload.scss
@@ -36,4 +36,7 @@
.image-upload-controls {
padding: 10px;
+ label.btn {
+ padding: 7px 10px 5px 10px;
+ }
}
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index a8f1dad6f6e..4ceb9810952 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -4,17 +4,17 @@ class UploadsController < ApplicationController
def create
file = params[:file] || params[:files].first
-
filesize = File.size(file.tempfile)
upload = Upload.create_for(current_user.id, file.tempfile, file.original_filename, filesize, { content_type: file.content_type })
- if current_user.admin?
+ if upload.errors.empty? && current_user.admin?
retain_hours = params[:retain_hours].to_i
- if retain_hours > 0
- upload.update_columns(retain_hours: retain_hours)
- end
+ upload.update_columns(retain_hours: retain_hours) if retain_hours > 0
end
+ # HACK FOR IE9 to prevent the "download dialog"
+ response.headers["Content-Type"] = "text/plain" if request.user_agent =~ /MSIE 9/
+
if upload.errors.empty?
render_serialized(upload, UploadSerializer, root: false)
else
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index b2ff46cecda..192791818f8 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -441,6 +441,9 @@ class UsersController < ApplicationController
file = params[:file] || params[:files].first
+ # HACK FOR IE9 to prevent the "download dialog"
+ response.headers["Content-Type"] = "text/plain" if request.user_agent =~ /MSIE 9/
+
begin
image = build_user_image_from(file)
rescue Discourse::InvalidParameters
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 33432a28a4b..81ab4e5fe81 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -72,39 +72,23 @@ class Upload < ActiveRecord::Base
# trim the origin if any
upload.origin = options[:origin][0...1000] if options[:origin]
- # deal with width & height for images
+ # check the size of the upload
if FileHelper.is_image?(filename)
- begin
- if filename =~ /\.svg$/i
- svg = Nokogiri::XML(file).at_css("svg")
- width, height = svg["width"].to_i, svg["height"].to_i
- if width == 0 || height == 0
- upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
- else
- upload.width, upload.height = ImageSizer.resize(width, height)
- end
- else
- # fix orientation first
- Upload.fix_image_orientation(file.path)
- # retrieve image info
- image_info = FastImage.new(file, raise_on_failure: true)
- # compute image aspect ratio
- upload.width, upload.height = ImageSizer.resize(*image_info.size)
- end
- # make sure we're at the beginning of the file
- # (FastImage and Nokogiri move the pointer)
- file.rewind
- rescue FastImage::ImageFetchFailure
- upload.errors.add(:base, I18n.t("upload.images.fetch_failure"))
- rescue FastImage::UnknownImageType
- upload.errors.add(:base, I18n.t("upload.images.unknown_image_type"))
- rescue FastImage::SizeNotFound
- upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
+ if SiteSetting.max_image_size_kb > 0 && filesize >= SiteSetting.max_image_size_kb.kilobytes
+ upload.errors.add(:base, I18n.t("upload.images.too_large", max_size_kb: SiteSetting.max_image_size_kb))
+ else
+ # deal with width & height for images
+ upload = Upload.resize_image(filename, file, upload)
+ end
+ else
+ if SiteSetting.max_attachment_size_kb > 0 && filesize >= SiteSetting.max_attachment_size_kb.kilobytes
+ upload.errors.add(:base, I18n.t("upload.attachments.too_large", max_size_kb: SiteSetting.max_attachment_size_kb))
end
-
- return upload unless upload.errors.empty?
end
+ # make sure there is no error
+ return upload unless upload.errors.empty?
+
# create a db record (so we can use the id)
return upload unless upload.save
@@ -122,6 +106,38 @@ class Upload < ActiveRecord::Base
upload
end
+ def self.resize_image(filename, file, upload)
+ begin
+ if filename =~ /\.svg$/i
+ svg = Nokogiri::XML(file).at_css("svg")
+ width, height = svg["width"].to_i, svg["height"].to_i
+ if width == 0 || height == 0
+ upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
+ else
+ upload.width, upload.height = ImageSizer.resize(width, height)
+ end
+ else
+ # fix orientation first
+ Upload.fix_image_orientation(file.path)
+ # retrieve image info
+ image_info = FastImage.new(file, raise_on_failure: true)
+ # compute image aspect ratio
+ upload.width, upload.height = ImageSizer.resize(*image_info.size)
+ end
+ # make sure we're at the beginning of the file
+ # (FastImage and Nokogiri move the pointer)
+ file.rewind
+ rescue FastImage::ImageFetchFailure
+ upload.errors.add(:base, I18n.t("upload.images.fetch_failure"))
+ rescue FastImage::UnknownImageType
+ upload.errors.add(:base, I18n.t("upload.images.unknown_image_type"))
+ rescue FastImage::SizeNotFound
+ upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
+ end
+
+ upload
+ end
+
def self.get_from_url(url)
return if url.blank?
# we store relative urls, so we need to remove any host/cdn
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 533587e3542..f4c61ef136e 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -1,5 +1,6 @@
-
+
+
<%= content_for?(:title) ? yield(:title) + ' - ' + SiteSetting.title : SiteSetting.title %>
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 22ad4af979d..e4166e10d0b 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1828,9 +1828,9 @@ en:
pasted_image_filename: "Pasted image"
store_failure: "Failed to store upload #%{upload_id} for user #%{user_id}."
attachments:
- too_large: "Sorry, the file you are trying to upload is too big (maximum size is %{max_size_kb}%kb)."
+ too_large: "Sorry, the file you are trying to upload is too big (maximum size is %{max_size_kb}KB)."
images:
- too_large: "Sorry, the image you are trying to upload is too big (maximum size is %{max_size_kb}%kb), please resize it and try again."
+ too_large: "Sorry, the image you are trying to upload is too big (maximum size is %{max_size_kb}KB), please resize it and try again."
fetch_failure: "Sorry, there has been an error while fetching the image."
unknown_image_type: "Sorry, but the file you tried to upload doesn't appear to be an image."
size_not_found: "Sorry, but we couldn't determine the size of the image. Maybe your image is corrupted?"
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index fce64613898..092006b38c1 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -84,6 +84,18 @@ describe Upload do
expect(upload.errors.size).to be > 0
end
+ it "generates an error when the image is too large" do
+ SiteSetting.stubs(:max_image_size_kb).returns(1)
+ upload = Upload.create_for(user_id, image, image_filename, image_filesize)
+ expect(upload.errors.size).to be > 0
+ end
+
+ it "generates an error when the attachment is too large" do
+ SiteSetting.stubs(:max_attachment_size_kb).returns(1)
+ upload = Upload.create_for(user_id, attachment, attachment_filename, attachment_filesize)
+ expect(upload.errors.size).to be > 0
+ end
+
it "saves proper information" do
store = {}
Discourse.expects(:store).returns(store)
diff --git a/vendor/assets/javascripts/jquery.fileupload.js b/vendor/assets/javascripts/jquery.fileupload.js
index 833d3532328..e99f3091e6f 100644
--- a/vendor/assets/javascripts/jquery.fileupload.js
+++ b/vendor/assets/javascripts/jquery.fileupload.js
@@ -1,5 +1,5 @@
/*
- * jQuery File Upload Plugin 5.40.3
+ * jQuery File Upload Plugin 5.42.2
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
@@ -10,7 +10,7 @@
*/
/* jshint nomen:false */
-/* global define, window, document, location, Blob, FormData */
+/* global define, require, window, document, location, Blob, FormData */
(function (factory) {
'use strict';
@@ -20,6 +20,12 @@
'jquery',
'jquery.ui.widget'
], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('./vendor/jquery.ui.widget')
+ );
} else {
// Browser globals:
factory(window.jQuery);
@@ -51,6 +57,25 @@
$.support.blobSlice = window.Blob && (Blob.prototype.slice ||
Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
+ // Helper function to create drag handlers for dragover/dragenter/dragleave:
+ function getDragHandler(type) {
+ var isDragOver = type === 'dragover';
+ return function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var dataTransfer = e.dataTransfer;
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
+ this._trigger(
+ type,
+ $.Event(type, {delegatedEvent: e})
+ ) !== false) {
+ e.preventDefault();
+ if (isDragOver) {
+ dataTransfer.dropEffect = 'copy';
+ }
+ }
+ };
+ }
+
// The fileupload widget listens for change events on file input fields defined
// via fileInput setting and paste or drop events of the given dropZone.
// In addition to the default jQuery Widget methods, the fileupload widget
@@ -65,9 +90,9 @@
// The drop target element(s), by the default the complete document.
// Set to null to disable drag & drop support:
dropZone: $(document),
- // The paste target element(s), by the default the complete document.
- // Set to null to disable paste support:
- pasteZone: $(document),
+ // The paste target element(s), by the default undefined.
+ // Set to a DOM node or jQuery object to enable file pasting:
+ pasteZone: undefined,
// The file input field(s), that are listened to for change events.
// If undefined, it is set to the file input fields inside
// of the widget element on plugin initialization.
@@ -1015,8 +1040,11 @@
return result;
},
- _replaceFileInput: function (input) {
- var inputClone = input.clone(true);
+ _replaceFileInput: function (data) {
+ var input = data.fileInput,
+ inputClone = input.clone(true);
+ // Add a reference for the new cloned file input to the data argument:
+ data.fileInputClone = inputClone;
$('').append(inputClone)[0].reset();
// Detaching allows to insert the fileInput on another form
// without loosing the file input value:
@@ -1187,7 +1215,7 @@
this._getFileInputFiles(data.fileInput).always(function (files) {
data.files = files;
if (that.options.replaceFileInput) {
- that._replaceFileInput(data.fileInput);
+ that._replaceFileInput(data);
}
if (that._trigger(
'change',
@@ -1240,24 +1268,21 @@
}
},
- _onDragOver: function (e) {
- e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
- var dataTransfer = e.dataTransfer;
- if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
- this._trigger(
- 'dragover',
- $.Event('dragover', {delegatedEvent: e})
- ) !== false) {
- e.preventDefault();
- dataTransfer.dropEffect = 'copy';
- }
- },
+ _onDragOver: getDragHandler('dragover'),
+
+ _onDragEnter: getDragHandler('dragenter'),
+
+ _onDragLeave: getDragHandler('dragleave'),
_initEventHandlers: function () {
if (this._isXHRUpload(this.options)) {
this._on(this.options.dropZone, {
dragover: this._onDragOver,
- drop: this._onDrop
+ drop: this._onDrop,
+ // event.preventDefault() on dragenter is required for IE10+:
+ dragenter: this._onDragEnter,
+ // dragleave is not required, but added for completeness:
+ dragleave: this._onDragLeave
});
this._on(this.options.pasteZone, {
paste: this._onPaste
@@ -1271,7 +1296,7 @@
},
_destroyEventHandlers: function () {
- this._off(this.options.dropZone, 'dragover drop');
+ this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
this._off(this.options.pasteZone, 'paste');
this._off(this.options.fileInput, 'change');
},
@@ -1319,10 +1344,13 @@
_initDataAttributes: function () {
var that = this,
options = this.options,
- clone = $(this.element[0].cloneNode(false));
+ clone = $(this.element[0].cloneNode(false)),
+ data = clone.data();
+ // Avoid memory leaks:
+ clone.remove();
// Initialize options set via HTML5 data-attributes:
$.each(
- clone.data(),
+ data,
function (key, value) {
var dataAttributeName = 'data-' +
// Convert camelCase to hyphen-ated key:
diff --git a/vendor/assets/javascripts/jquery.iframe-transport.js b/vendor/assets/javascripts/jquery.iframe-transport.js
index 4749f469936..b7581f23f43 100644
--- a/vendor/assets/javascripts/jquery.iframe-transport.js
+++ b/vendor/assets/javascripts/jquery.iframe-transport.js
@@ -1,5 +1,5 @@
/*
- * jQuery Iframe Transport Plugin 1.5
+ * jQuery Iframe Transport Plugin 1.8.3
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
@@ -9,14 +9,16 @@
* http://www.opensource.org/licenses/MIT
*/
-/*jslint unparam: true, nomen: true */
-/*global define, window, document */
+/* global define, require, window, document */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
} else {
// Browser globals:
factory(window.jQuery);
@@ -27,7 +29,7 @@
// Helper variable to create unique names for the transport iframes:
var counter = 0;
- // The iframe transport accepts three additional options:
+ // The iframe transport accepts four additional options:
// options.fileInput: a jQuery collection of file input fields
// options.paramName: the parameter name for the file form data,
// overrides the name property of the file input field(s),
@@ -35,22 +37,41 @@
// options.formData: an array of objects with name and value properties,
// equivalent to the return data of .serializeArray(), e.g.:
// [{name: 'a', value: 1}, {name: 'b', value: 2}]
+ // options.initialIframeSrc: the URL of the initial iframe src,
+ // by default set to "javascript:false;"
$.ajaxTransport('iframe', function (options) {
- if (options.async && (options.type === 'POST' || options.type === 'GET')) {
- var form,
- iframe;
+ if (options.async) {
+ // javascript:false as initial iframe src
+ // prevents warning popups on HTTPS in IE6:
+ /*jshint scripturl: true */
+ var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
+ /*jshint scripturl: false */
+ form,
+ iframe,
+ addParamChar;
return {
send: function (_, completeCallback) {
form = $('');
form.attr('accept-charset', options.formAcceptCharset);
- // javascript:false as initial iframe src
- // prevents warning popups on HTTPS in IE6.
+ addParamChar = /\?/.test(options.url) ? '&' : '?';
+ // XDomainRequest only supports GET and POST:
+ if (options.type === 'DELETE') {
+ options.url = options.url + addParamChar + '_method=DELETE';
+ options.type = 'POST';
+ } else if (options.type === 'PUT') {
+ options.url = options.url + addParamChar + '_method=PUT';
+ options.type = 'POST';
+ } else if (options.type === 'PATCH') {
+ options.url = options.url + addParamChar + '_method=PATCH';
+ options.type = 'POST';
+ }
// IE versions below IE8 cannot set the name property of
// elements that have already been added to the DOM,
// so we set the name along with the iframe HTML markup:
+ counter += 1;
iframe = $(
- ''
+ ''
).bind('load', function () {
var fileInputClones,
paramNames = $.isArray(options.paramName) ?
@@ -81,9 +102,14 @@
);
// Fix for IE endless progress bar activity bug
// (happens on form submits to iframe targets):
- $('')
+ $('')
.appendTo(form);
- form.remove();
+ window.setTimeout(function () {
+ // Removing the form in a setTimeout call
+ // allows Chrome's developer tools to display
+ // the response result
+ form.remove();
+ }, 0);
});
form
.prop('target', iframe.prop('name'))
@@ -119,6 +145,8 @@
.prop('enctype', 'multipart/form-data')
// enctype must be set as encoding for IE:
.prop('encoding', 'multipart/form-data');
+ // Remove the HTML5 form attribute from the input(s):
+ options.fileInput.removeAttr('form');
}
form.submit();
// Insert the file input fields at their original location
@@ -126,7 +154,10 @@
if (fileInputClones && fileInputClones.length) {
options.fileInput.each(function (index, input) {
var clone = $(fileInputClones[index]);
- $(input).prop('name', clone.prop('name'));
+ // Restore the original name and form properties:
+ $(input)
+ .prop('name', clone.prop('name'))
+ .attr('form', clone.attr('form'));
clone.replaceWith(input);
});
}
@@ -140,7 +171,7 @@
// concat is used to avoid the "Script URL" JSLint error:
iframe
.unbind('load')
- .prop('src', 'javascript'.concat(':false;'));
+ .prop('src', initialIframeSrc);
}
if (form) {
form.remove();
@@ -151,20 +182,34 @@
});
// The iframe transport returns the iframe content document as response.
- // The following adds converters from iframe to text, json, html, and script:
+ // The following adds converters from iframe to text, json, html, xml
+ // and script.
+ // Please note that the Content-Type for JSON responses has to be text/plain
+ // or text/html, if the browser doesn't include application/json in the
+ // Accept header, else IE will show a download dialog.
+ // The Content-Type for XML responses on the other hand has to be always
+ // application/xml or text/xml, so IE properly parses the XML response.
+ // See also
+ // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
$.ajaxSetup({
converters: {
'iframe text': function (iframe) {
- return $(iframe[0].body).text();
+ return iframe && $(iframe[0].body).text();
},
'iframe json': function (iframe) {
- return $.parseJSON($(iframe[0].body).text());
+ return iframe && $.parseJSON($(iframe[0].body).text());
},
'iframe html': function (iframe) {
- return $(iframe[0].body).html();
+ return iframe && $(iframe[0].body).html();
+ },
+ 'iframe xml': function (iframe) {
+ var xmlDoc = iframe && iframe[0];
+ return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
+ $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
+ $(xmlDoc.body).html());
},
'iframe script': function (iframe) {
- return $.globalEval($(iframe[0].body).text());
+ return iframe && $.globalEval($(iframe[0].body).text());
}
}
});
diff --git a/vendor/assets/javascripts/jquery.ui.widget.js b/vendor/assets/javascripts/jquery.ui.widget.js
index c430419971e..5ac2ed5a572 100644
--- a/vendor/assets/javascripts/jquery.ui.widget.js
+++ b/vendor/assets/javascripts/jquery.ui.widget.js
@@ -1,6 +1,27 @@
+/*! jQuery UI - v1.11.1+CommonJS - 2014-09-17
+* http://jqueryui.com
+* Includes: widget.js
+* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */
+
+(function( factory ) {
+ if ( typeof define === "function" && define.amd ) {
+
+ // AMD. Register as an anonymous module.
+ define([ "jquery" ], factory );
+
+ } else if (typeof exports === "object") {
+ // Node/CommonJS:
+ factory(require("jquery"));
+
+ } else {
+
+ // Browser globals
+ factory( jQuery );
+ }
+}(function( $ ) {
/*!
- * jQuery UI Widget 1.10.4+amd
- * https://github.com/blueimp/jQuery-File-Upload
+ * jQuery UI Widget 1.11.1
+ * http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
@@ -9,28 +30,28 @@
* http://api.jqueryui.com/jQuery.widget/
*/
-(function (factory) {
- if (typeof define === "function" && define.amd) {
- // Register as an anonymous AMD module:
- define(["jquery"], factory);
- } else {
- // Browser globals:
- factory(jQuery);
- }
-}(function( $, undefined ) {
-var uuid = 0,
- slice = Array.prototype.slice,
- _cleanData = $.cleanData;
-$.cleanData = function( elems ) {
- for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
- try {
- $( elem ).triggerHandler( "remove" );
- // http://bugs.jquery.com/ticket/8235
- } catch( e ) {}
- }
- _cleanData( elems );
-};
+var widget_uuid = 0,
+ widget_slice = Array.prototype.slice;
+
+$.cleanData = (function( orig ) {
+ return function( elems ) {
+ var events, elem, i;
+ for ( i = 0; (elem = elems[i]) != null; i++ ) {
+ try {
+
+ // Only trigger remove when necessary to save time
+ events = $._data( elem, "events" );
+ if ( events && events.remove ) {
+ $( elem ).triggerHandler( "remove" );
+ }
+
+ // http://bugs.jquery.com/ticket/8235
+ } catch( e ) {}
+ }
+ orig( elems );
+ };
+})( $.cleanData );
$.widget = function( name, base, prototype ) {
var fullName, existingConstructor, constructor, basePrototype,
@@ -143,10 +164,12 @@ $.widget = function( name, base, prototype ) {
}
$.widget.bridge( name, constructor );
+
+ return constructor;
};
$.widget.extend = function( target ) {
- var input = slice.call( arguments, 1 ),
+ var input = widget_slice.call( arguments, 1 ),
inputIndex = 0,
inputLength = input.length,
key,
@@ -175,7 +198,7 @@ $.widget.bridge = function( name, object ) {
var fullName = object.prototype.widgetFullName || name;
$.fn[ name ] = function( options ) {
var isMethodCall = typeof options === "string",
- args = slice.call( arguments, 1 ),
+ args = widget_slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
@@ -187,6 +210,10 @@ $.widget.bridge = function( name, object ) {
this.each(function() {
var methodValue,
instance = $.data( this, fullName );
+ if ( options === "instance" ) {
+ returnValue = instance;
+ return false;
+ }
if ( !instance ) {
return $.error( "cannot call methods on " + name + " prior to initialization; " +
"attempted to call method '" + options + "'" );
@@ -206,7 +233,10 @@ $.widget.bridge = function( name, object ) {
this.each(function() {
var instance = $.data( this, fullName );
if ( instance ) {
- instance.option( options || {} )._init();
+ instance.option( options || {} );
+ if ( instance._init ) {
+ instance._init();
+ }
} else {
$.data( this, fullName, new object( options, this ) );
}
@@ -233,7 +263,7 @@ $.Widget.prototype = {
_createWidget: function( options, element ) {
element = $( element || this.defaultElement || this )[ 0 ];
this.element = $( element );
- this.uuid = uuid++;
+ this.uuid = widget_uuid++;
this.eventNamespace = "." + this.widgetName + this.uuid;
this.options = $.widget.extend( {},
this.options,
@@ -276,9 +306,6 @@ $.Widget.prototype = {
// all event bindings should go through this._on()
this.element
.unbind( this.eventNamespace )
- // 1.9 BC for #7810
- // TODO remove dual storage
- .removeData( this.widgetName )
.removeData( this.widgetFullName )
// support: jquery <1.6.3
// http://bugs.jquery.com/ticket/9413
@@ -354,20 +381,23 @@ $.Widget.prototype = {
if ( key === "disabled" ) {
this.widget()
- .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
- .attr( "aria-disabled", value );
- this.hoverable.removeClass( "ui-state-hover" );
- this.focusable.removeClass( "ui-state-focus" );
+ .toggleClass( this.widgetFullName + "-disabled", !!value );
+
+ // If the widget is becoming disabled, then nothing is interactive
+ if ( value ) {
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ }
}
return this;
},
enable: function() {
- return this._setOption( "disabled", false );
+ return this._setOptions({ disabled: false });
},
disable: function() {
- return this._setOption( "disabled", true );
+ return this._setOptions({ disabled: true });
},
_on: function( suppressDisabledCheck, element, handlers ) {
@@ -387,7 +417,6 @@ $.Widget.prototype = {
element = this.element;
delegateElement = this.widget();
} else {
- // accept selectors, DOM elements
element = delegateElement = $( element );
this.bindings = this.bindings.add( element );
}
@@ -412,7 +441,7 @@ $.Widget.prototype = {
handler.guid || handlerProxy.guid || $.guid++;
}
- var match = event.match( /^(\w+)\s*(.*)$/ ),
+ var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
eventName = match[1] + instance.eventNamespace,
selector = match[2];
if ( selector ) {
@@ -527,4 +556,8 @@ $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
};
});
+var widget = $.widget;
+
+
+
}));