mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
FIX: 🐛 upload on IE9 wasn't working :'(
- FIX: make sure we set a default name to a pasted image only on Chrome (the only browser that supports it)
- FIX: use ".json" extension to uploads endpoints since IE9 doesn't pass the correct header
- FIX: pass the CSRF token in a query parameter since IE9 doesn't pass it in the headers
- FIX: display error messages comming from the server when there is one over the default error message
- FIX: HACK around IE9 security issue when clicking a file input via JavaScript (use a label and set `visibility:hidden` on the input)
- FIX: hide the "cancel" upload on IE9 since it's not supported
- FIX: return "text/plain" content-type when uploading a file for IE9 in order to prevent it from displaying the save dialog
- FIX: check the maximum file size on the server 💥
- update jQuery File Upload Plugin to v. 5.42.2
- update JQuery IFram Transport Plugin to v. 1.8.5
- update jQuery UI Widget to v. 1.11.1
This commit is contained in:
parent
053d3120f7
commit
cd2c9edb46
17 changed files with 341 additions and 179 deletions
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<input type="file" accept="image/*" style="display:none" />
|
||||
<button class="btn" {{action "selectFile"}} {{bind-attr disabled="uploading"}} title="{{i18n 'user.change_avatar.upload_title'}}">
|
||||
<i class="fa fa-picture-o"></i> {{uploadButtonText}}
|
||||
</button>
|
||||
<label class="btn" {{bind-attr disabled="uploading"}} title="{{i18n 'user.change_avatar.upload_title'}}">
|
||||
{{fa-icon "picture-o"}} {{uploadButtonText}}
|
||||
<input {{bind-attr disabled="uploading"}} type="file" accept="image/*" style="visibility: hidden; position: absolute;" />
|
||||
</label>
|
||||
{{#if uploading}}
|
||||
<span>{{i18n 'upload_selector.uploading'}} {{view.uploadProgress}}%</span>
|
||||
{{/if}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{{text-field name="name" placeholderKey="admin.emoji.name" value=name}}
|
||||
<input type="file" accept=".png,.gif" style="display:none" />
|
||||
<button {{bind-attr disabled="addDisabled"}} {{action "selectFile"}} class='btn btn-primary'>
|
||||
<label class="btn btn-primary" {{bind-attr disabled="addDisabled"}}>
|
||||
{{fa-icon "plus"}}
|
||||
{{i18n 'admin.emoji.add'}}
|
||||
</button>
|
||||
<input {{bind-attr disabled="addDisabled"}} type="file" accept=".png,.gif" style="visibility: hidden; position: absolute;" />
|
||||
</label>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<input type="file" accept="image/*" style="display:none" />
|
||||
<div class="uploaded-image-preview" class="input-xxlarge" {{bind-attr style="backgroundStyle"}}>
|
||||
<div class="image-upload-controls">
|
||||
<button {{action "selectFile"}} class="btn pad-left no-text">{{fa-icon "picture-o"}}</button>
|
||||
<label class="btn pad-left no-text" {{bind-attr disabled="uploading"}}>
|
||||
{{fa-icon "picture-o"}}
|
||||
<input {{bind-attr disabled="uploading"}} type="file" accept="image/*" style="visibility: hidden; position: absolute;" />
|
||||
</label>
|
||||
{{#if backgroundStyle}}
|
||||
<button {{action "trash"}} class="btn btn-danger pad-left no-text">{{fa-icon "trash-o"}}</button>
|
||||
{{/if}}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -36,4 +36,7 @@
|
|||
|
||||
.image-upload-controls {
|
||||
padding: 10px;
|
||||
label.btn {
|
||||
padding: 7px 10px 5px 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="<%= SiteSetting.default_locale %>" class="<%= html_classes %>">
|
||||
<!--[if IE 9]><html lang="<%= SiteSetting.default_locale %>" class="ie9 <%= html_classes %>"><![endif]-->
|
||||
<!--[if (!IE 9) | (!IE)]><!--><html lang="<%= SiteSetting.default_locale %>" class="<%= html_classes %>"><!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><%= content_for?(:title) ? yield(:title) + ' - ' + SiteSetting.title : SiteSetting.title %></title>
|
||||
|
|
|
@ -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?"
|
||||
|
|
|
@ -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)
|
||||
|
|
76
vendor/assets/javascripts/jquery.fileupload.js
vendored
76
vendor/assets/javascripts/jquery.fileupload.js
vendored
|
@ -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;
|
||||
$('<form></form>').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:
|
||||
|
|
|
@ -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 style="display:none;"></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 = $(
|
||||
'<iframe src="javascript:false;" name="iframe-transport-' +
|
||||
(counter += 1) + '"></iframe>'
|
||||
'<iframe src="' + initialIframeSrc +
|
||||
'" name="iframe-transport-' + counter + '"></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):
|
||||
$('<iframe src="javascript:false;"></iframe>')
|
||||
$('<iframe src="' + initialIframeSrc + '"></iframe>')
|
||||
.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());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
109
vendor/assets/javascripts/jquery.ui.widget.js
vendored
109
vendor/assets/javascripts/jquery.ui.widget.js
vendored
|
@ -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;
|
||||
|
||||
|
||||
|
||||
}));
|
||||
|
|
Loading…
Reference in a new issue