├── .gitignore ├── Gemfile ├── README.md ├── Rakefile ├── app └── assets │ ├── images │ ├── loading.gif │ └── progressbar.gif │ ├── javascripts │ └── jquery-fileupload │ │ ├── angularjs.js │ │ ├── basic-plus.js │ │ ├── basic.js │ │ ├── cors │ │ ├── jquery.postmessage-transport.js │ │ └── jquery.xdr-transport.js │ │ ├── index.js │ │ ├── jquery-ui.js │ │ ├── jquery.fileupload-angular.js │ │ ├── jquery.fileupload-audio.js │ │ ├── jquery.fileupload-image.js │ │ ├── jquery.fileupload-jquery-ui.js │ │ ├── jquery.fileupload-process.js │ │ ├── jquery.fileupload-ui.js │ │ ├── jquery.fileupload-validate.js │ │ ├── jquery.fileupload-video.js │ │ ├── jquery.fileupload.js │ │ ├── jquery.iframe-transport.js │ │ ├── locale.js │ │ └── vendor │ │ ├── canvas-to-blob.js │ │ ├── jquery.ui.widget.js │ │ ├── load-image.all.min.js │ │ └── tmpl.js │ └── stylesheets │ ├── jquery.fileupload-noscript.scss │ ├── jquery.fileupload-ui-noscript.scss │ ├── jquery.fileupload-ui.scss │ └── jquery.fileupload.scss ├── jquery-fileupload-rails.gemspec └── lib ├── jquery-fileupload-rails.rb └── jquery └── fileupload └── rails ├── engine.rb ├── middleware.rb ├── upload.rb └── version.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .ruby-version 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in jquery-fileupload-rails.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery File Upload for Rails 2 | 3 | [jQuery-File-Plugin](https://github.com/blueimp/jQuery-File-Upload) is a file upload plugin written by [Sebastian Tschan](https://github.com/blueimp). jQuery File Upload features multiple file selection, drag&drop support, progress bars and preview images for jQuery. Supports cross-domain, chunked and resumable file uploads and client-side image resizing. 4 | 5 | jquery-fileupload-rails is a library that integrates jQuery File Upload for Rails 3.1 Asset Pipeline (Rails 3.2 supported). 6 | 7 | ## Plugin versions 8 | 9 | * jQuery File Upload Plugin v9.12.5 10 | 11 | ## Installing Gem 12 | 13 | gem "jquery-fileupload-rails" 14 | 15 | ## Using the javascripts 16 | 17 | Require jquery-fileupload in your app/assets/application.js file. 18 | 19 | //= require jquery-fileupload 20 | 21 | The snippet above will add the following js files to the manifest file. 22 | 23 | //= require jquery-fileupload/vendor/jquery.ui.widget 24 | //= require jquery-fileupload/vendor/tmpl 25 | //= require jquery-fileupload/vendor/load-image.all.min 26 | //= require jquery-fileupload/vendor/canvas-to-blob 27 | //= require jquery-fileupload/jquery.iframe-transport 28 | //= require jquery-fileupload/jquery.fileupload 29 | //= require jquery-fileupload/jquery.fileupload-process 30 | //= require jquery-fileupload/jquery.fileupload-image 31 | //= require jquery-fileupload/jquery.fileupload-audio 32 | //= require jquery-fileupload/jquery.fileupload-video 33 | //= require jquery-fileupload/jquery.fileupload-validate 34 | //= require jquery-fileupload/jquery.fileupload-ui 35 | //= require jquery-fileupload/locale 36 | //= require jquery-fileupload/jquery.fileupload-angular 37 | //= require jquery-fileupload/jquery.fileupload-jquery-ui 38 | //= require jquery-fileupload/cors/jquery.postmessage-transport 39 | //= require jquery-fileupload/cors/jquery.xdr-transport 40 | 41 | If you only need the basic files, just add the code below to your application.js file. [Basic setup guide](https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin) 42 | 43 | //= require jquery-fileupload/basic 44 | 45 | The basic setup only includes the following files: 46 | 47 | //= require jquery-fileupload/vendor/jquery.ui.widget 48 | //= require jquery-fileupload/jquery.iframe-transport 49 | //= require jquery-fileupload/jquery.fileupload 50 | 51 | You can also require the following to get the js from the Basic-Plus, AngularJS and jQuery UI Examples: 52 | 53 | //= require jquery-fileupload/basic-plus 54 | 55 | //= require jquery-fileupload/angularjs 56 | 57 | //= require jquery-fileupload/jquery-ui 58 | 59 | ## Using the stylesheet 60 | 61 | Require the stylesheet file to app/assets/stylesheets/application.css 62 | 63 | *= require jquery.fileupload 64 | *= require jquery.fileupload-ui 65 | 66 | There are also noscript styles for Browsers with Javascript disabled, to use them create a noscript.css and add it to your precompile-list and layout inside a noscript tag: 67 | 68 | *= require jquery.fileupload-noscript 69 | *= require jquery.fileupload-ui-noscript 70 | 71 | ## Using the middleware 72 | 73 | The `jquery.iframe-transport` fallback transport has some special caveats regarding the response data type, http status, and character encodings. `jquery-fileupload-rails` includes a middleware that handles these inconsistencies seamlessly. If you decide to use it, create an initializer that adds the middleware to your application's middleware stack. 74 | 75 | ```ruby 76 | Rails.application.config.middleware.use JQuery::FileUpload::Rails::Middleware 77 | ``` 78 | 79 | ## Example apps 80 | [jquery-fileupload-rails-paperclip-example](https://github.com/tors/jquery-fileupload-rails-paperclip-example): jQuery File Upload in Rails 3.2 with Paperclip and Bootstrap 81 | 82 | [rails-resumable-jquery-fileupload](https://github.com/vgantchev/rails-resumable-jquery-fileupload): resumable (chunked) uploads with jQuery File Upload in Rails 4.2 using Paperclip 83 | 84 | You can also check out Ryan Bate's RailsCast [jQuery File Upload episode](http://railscasts.com/episodes/381-jquery-file-upload). 85 | 86 | 87 | ## Thanks 88 | Thanks to [Sebastian Tschan](https://github.com/blueimp) for writing an awesome file upload plugin. 89 | 90 | ## License 91 | Copyright (c) 2012 Tors Dalid 92 | 93 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 94 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 95 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 96 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require 'bundler' 3 | Bundler::GemHelper.install_tasks 4 | 5 | desc "Bundle the gem" 6 | task :bundle do 7 | sh('bundle install') 8 | sh 'gem build *.gemspec' 9 | sh 'gem install *.gem' 10 | sh 'rm *.gem' 11 | end 12 | 13 | task(:default).clear 14 | task :default => :bundle 15 | 16 | -------------------------------------------------------------------------------- /app/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tors/jquery-fileupload-rails/456422809749394181d4fb7f901f42ba74c99a4f/app/assets/images/loading.gif -------------------------------------------------------------------------------- /app/assets/images/progressbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tors/jquery-fileupload-rails/456422809749394181d4fb7f901f42ba74c99a4f/app/assets/images/progressbar.gif -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/angularjs.js: -------------------------------------------------------------------------------- 1 | //= require jquery-fileupload/vendor/jquery.ui.widget 2 | //= require jquery-fileupload/vendor/load-image.all.min 3 | //= require jquery-fileupload/vendor/canvas-to-blob 4 | //= require jquery-fileupload/jquery.iframe-transport 5 | //= require jquery-fileupload/jquery.fileupload 6 | //= require jquery-fileupload/jquery.fileupload-process 7 | //= require jquery-fileupload/jquery.fileupload-image 8 | //= require jquery-fileupload/jquery.fileupload-audio 9 | //= require jquery-fileupload/jquery.fileupload-video 10 | //= require jquery-fileupload/jquery.fileupload-validate 11 | //= require jquery-fileupload/jquery.fileupload-angular 12 | //= require jquery-fileupload/locale 13 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/basic-plus.js: -------------------------------------------------------------------------------- 1 | //= require jquery-fileupload/vendor/jquery.ui.widget 2 | //= require jquery-fileupload/vendor/load-image.all.min 3 | //= require jquery-fileupload/vendor/canvas-to-blob 4 | //= require jquery-fileupload/jquery.iframe-transport 5 | //= require jquery-fileupload/jquery.fileupload 6 | //= require jquery-fileupload/jquery.fileupload-process 7 | //= require jquery-fileupload/jquery.fileupload-image 8 | //= require jquery-fileupload/jquery.fileupload-audio 9 | //= require jquery-fileupload/jquery.fileupload-video 10 | //= require jquery-fileupload/jquery.fileupload-validate 11 | //= require jquery-fileupload/locale 12 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/basic.js: -------------------------------------------------------------------------------- 1 | //= require jquery-fileupload/vendor/jquery.ui.widget 2 | //= require jquery-fileupload/jquery.iframe-transport 3 | //= require jquery-fileupload/jquery.fileupload 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/cors/jquery.postmessage-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery postMessage Transport Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, require, window, document */ 13 | 14 | ;(function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define(['jquery'], factory); 19 | } else if (typeof exports === 'object') { 20 | // Node/CommonJS: 21 | factory(require('jquery')); 22 | } else { 23 | // Browser globals: 24 | factory(window.jQuery); 25 | } 26 | }(function ($) { 27 | 'use strict'; 28 | 29 | var counter = 0, 30 | names = [ 31 | 'accepts', 32 | 'cache', 33 | 'contents', 34 | 'contentType', 35 | 'crossDomain', 36 | 'data', 37 | 'dataType', 38 | 'headers', 39 | 'ifModified', 40 | 'mimeType', 41 | 'password', 42 | 'processData', 43 | 'timeout', 44 | 'traditional', 45 | 'type', 46 | 'url', 47 | 'username' 48 | ], 49 | convert = function (p) { 50 | return p; 51 | }; 52 | 53 | $.ajaxSetup({ 54 | converters: { 55 | 'postmessage text': convert, 56 | 'postmessage json': convert, 57 | 'postmessage html': convert 58 | } 59 | }); 60 | 61 | $.ajaxTransport('postmessage', function (options) { 62 | if (options.postMessage && window.postMessage) { 63 | var iframe, 64 | loc = $('').prop('href', options.postMessage)[0], 65 | target = loc.protocol + '//' + loc.host, 66 | xhrUpload = options.xhr().upload; 67 | // IE always includes the port for the host property of a link 68 | // element, but not in the location.host or origin property for the 69 | // default http port 80 and https port 443, so we strip it: 70 | if (/^(http:\/\/.+:80)|(https:\/\/.+:443)$/.test(target)) { 71 | target = target.replace(/:(80|443)$/, ''); 72 | } 73 | return { 74 | send: function (_, completeCallback) { 75 | counter += 1; 76 | var message = { 77 | id: 'postmessage-transport-' + counter 78 | }, 79 | eventName = 'message.' + message.id; 80 | iframe = $( 81 | '' 84 | ).bind('load', function () { 85 | $.each(names, function (i, name) { 86 | message[name] = options[name]; 87 | }); 88 | message.dataType = message.dataType.replace('postmessage ', ''); 89 | $(window).bind(eventName, function (e) { 90 | e = e.originalEvent; 91 | var data = e.data, 92 | ev; 93 | if (e.origin === target && data.id === message.id) { 94 | if (data.type === 'progress') { 95 | ev = document.createEvent('Event'); 96 | ev.initEvent(data.type, false, true); 97 | $.extend(ev, data); 98 | xhrUpload.dispatchEvent(ev); 99 | } else { 100 | completeCallback( 101 | data.status, 102 | data.statusText, 103 | {postmessage: data.result}, 104 | data.headers 105 | ); 106 | iframe.remove(); 107 | $(window).unbind(eventName); 108 | } 109 | } 110 | }); 111 | iframe[0].contentWindow.postMessage( 112 | message, 113 | target 114 | ); 115 | }).appendTo(document.body); 116 | }, 117 | abort: function () { 118 | if (iframe) { 119 | iframe.remove(); 120 | } 121 | } 122 | }; 123 | } 124 | }); 125 | 126 | })); 127 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/cors/jquery.xdr-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery XDomainRequest Transport Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | * 11 | * Based on Julian Aubourg's ajaxHooks xdr.js: 12 | * https://github.com/jaubourg/ajaxHooks/ 13 | */ 14 | 15 | /* global define, require, window, XDomainRequest */ 16 | 17 | ;(function (factory) { 18 | 'use strict'; 19 | if (typeof define === 'function' && define.amd) { 20 | // Register as an anonymous AMD module: 21 | define(['jquery'], factory); 22 | } else if (typeof exports === 'object') { 23 | // Node/CommonJS: 24 | factory(require('jquery')); 25 | } else { 26 | // Browser globals: 27 | factory(window.jQuery); 28 | } 29 | }(function ($) { 30 | 'use strict'; 31 | if (window.XDomainRequest && !$.support.cors) { 32 | $.ajaxTransport(function (s) { 33 | if (s.crossDomain && s.async) { 34 | if (s.timeout) { 35 | s.xdrTimeout = s.timeout; 36 | delete s.timeout; 37 | } 38 | var xdr; 39 | return { 40 | send: function (headers, completeCallback) { 41 | var addParamChar = /\?/.test(s.url) ? '&' : '?'; 42 | function callback(status, statusText, responses, responseHeaders) { 43 | xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; 44 | xdr = null; 45 | completeCallback(status, statusText, responses, responseHeaders); 46 | } 47 | xdr = new XDomainRequest(); 48 | // XDomainRequest only supports GET and POST: 49 | if (s.type === 'DELETE') { 50 | s.url = s.url + addParamChar + '_method=DELETE'; 51 | s.type = 'POST'; 52 | } else if (s.type === 'PUT') { 53 | s.url = s.url + addParamChar + '_method=PUT'; 54 | s.type = 'POST'; 55 | } else if (s.type === 'PATCH') { 56 | s.url = s.url + addParamChar + '_method=PATCH'; 57 | s.type = 'POST'; 58 | } 59 | xdr.open(s.type, s.url); 60 | xdr.onload = function () { 61 | callback( 62 | 200, 63 | 'OK', 64 | {text: xdr.responseText}, 65 | 'Content-Type: ' + xdr.contentType 66 | ); 67 | }; 68 | xdr.onerror = function () { 69 | callback(404, 'Not Found'); 70 | }; 71 | if (s.xdrTimeout) { 72 | xdr.ontimeout = function () { 73 | callback(0, 'timeout'); 74 | }; 75 | xdr.timeout = s.xdrTimeout; 76 | } 77 | xdr.send((s.hasContent && s.data) || null); 78 | }, 79 | abort: function () { 80 | if (xdr) { 81 | xdr.onerror = $.noop(); 82 | xdr.abort(); 83 | } 84 | } 85 | }; 86 | } 87 | }); 88 | } 89 | })); 90 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/index.js: -------------------------------------------------------------------------------- 1 | //= require jquery-fileupload/vendor/jquery.ui.widget 2 | //= require jquery-fileupload/vendor/tmpl 3 | //= require jquery-fileupload/vendor/load-image.all.min 4 | //= require jquery-fileupload/vendor/canvas-to-blob 5 | //= require jquery-fileupload/jquery.iframe-transport 6 | //= require jquery-fileupload/jquery.fileupload 7 | //= require jquery-fileupload/jquery.fileupload-process 8 | //= require jquery-fileupload/jquery.fileupload-image 9 | //= require jquery-fileupload/jquery.fileupload-audio 10 | //= require jquery-fileupload/jquery.fileupload-video 11 | //= require jquery-fileupload/jquery.fileupload-validate 12 | //= require jquery-fileupload/jquery.fileupload-ui 13 | //= require jquery-fileupload/locale 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery-ui.js: -------------------------------------------------------------------------------- 1 | //= require jquery-fileupload/vendor/tmpl 2 | //= require jquery-fileupload/vendor/load-image.all.min 3 | //= require jquery-fileupload/vendor/canvas-to-blob 4 | //= require jquery-fileupload/jquery.iframe-transport 5 | //= require jquery-fileupload/jquery.fileupload 6 | //= require jquery-fileupload/jquery.fileupload-process 7 | //= require jquery-fileupload/jquery.fileupload-image 8 | //= require jquery-fileupload/jquery.fileupload-audio 9 | //= require jquery-fileupload/jquery.fileupload-video 10 | //= require jquery-fileupload/jquery.fileupload-validate 11 | //= require jquery-fileupload/jquery.fileupload-ui 12 | //= require jquery-fileupload/jquery.fileupload-jquery-ui 13 | //= require jquery-fileupload/locale 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery.fileupload-angular.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload AngularJS Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, angular */ 14 | 15 | ;(function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'angular', 22 | './jquery.fileupload-image', 23 | './jquery.fileupload-audio', 24 | './jquery.fileupload-video', 25 | './jquery.fileupload-validate' 26 | ], factory); 27 | } else { 28 | factory(); 29 | } 30 | }(function () { 31 | 'use strict'; 32 | 33 | angular.module('blueimp.fileupload', []) 34 | 35 | // The fileUpload service provides configuration options 36 | // for the fileUpload directive and default handlers for 37 | // File Upload events: 38 | .provider('fileUpload', function () { 39 | var scopeEvalAsync = function (expression) { 40 | var scope = angular.element(this) 41 | .fileupload('option', 'scope'); 42 | // Schedule a new $digest cycle if not already inside of one 43 | // and evaluate the given expression: 44 | scope.$evalAsync(expression); 45 | }, 46 | addFileMethods = function (scope, data) { 47 | var files = data.files, 48 | file = files[0]; 49 | angular.forEach(files, function (file, index) { 50 | file._index = index; 51 | file.$state = function () { 52 | return data.state(); 53 | }; 54 | file.$processing = function () { 55 | return data.processing(); 56 | }; 57 | file.$progress = function () { 58 | return data.progress(); 59 | }; 60 | file.$response = function () { 61 | return data.response(); 62 | }; 63 | }); 64 | file.$submit = function () { 65 | if (!file.error) { 66 | return data.submit(); 67 | } 68 | }; 69 | file.$cancel = function () { 70 | return data.abort(); 71 | }; 72 | }, 73 | $config; 74 | $config = this.defaults = { 75 | handleResponse: function (e, data) { 76 | var files = data.result && data.result.files; 77 | if (files) { 78 | data.scope.replace(data.files, files); 79 | } else if (data.errorThrown || 80 | data.textStatus === 'error') { 81 | data.files[0].error = data.errorThrown || 82 | data.textStatus; 83 | } 84 | }, 85 | add: function (e, data) { 86 | if (e.isDefaultPrevented()) { 87 | return false; 88 | } 89 | var scope = data.scope, 90 | filesCopy = []; 91 | angular.forEach(data.files, function (file) { 92 | filesCopy.push(file); 93 | }); 94 | scope.$parent.$applyAsync(function () { 95 | addFileMethods(scope, data); 96 | var method = scope.option('prependFiles') ? 97 | 'unshift' : 'push'; 98 | Array.prototype[method].apply(scope.queue, data.files); 99 | }); 100 | data.process(function () { 101 | return scope.process(data); 102 | }).always(function () { 103 | scope.$parent.$applyAsync(function () { 104 | addFileMethods(scope, data); 105 | scope.replace(filesCopy, data.files); 106 | }); 107 | }).then(function () { 108 | if ((scope.option('autoUpload') || 109 | data.autoUpload) && 110 | data.autoUpload !== false) { 111 | data.submit(); 112 | } 113 | }); 114 | }, 115 | done: function (e, data) { 116 | if (e.isDefaultPrevented()) { 117 | return false; 118 | } 119 | var that = this; 120 | data.scope.$apply(function () { 121 | data.handleResponse.call(that, e, data); 122 | }); 123 | }, 124 | fail: function (e, data) { 125 | if (e.isDefaultPrevented()) { 126 | return false; 127 | } 128 | var that = this, 129 | scope = data.scope; 130 | if (data.errorThrown === 'abort') { 131 | scope.clear(data.files); 132 | return; 133 | } 134 | scope.$apply(function () { 135 | data.handleResponse.call(that, e, data); 136 | }); 137 | }, 138 | stop: scopeEvalAsync, 139 | processstart: scopeEvalAsync, 140 | processstop: scopeEvalAsync, 141 | getNumberOfFiles: function () { 142 | var scope = this.scope; 143 | return scope.queue.length - scope.processing(); 144 | }, 145 | dataType: 'json', 146 | autoUpload: false 147 | }; 148 | this.$get = [ 149 | function () { 150 | return { 151 | defaults: $config 152 | }; 153 | } 154 | ]; 155 | }) 156 | 157 | // Format byte numbers to readable presentations: 158 | .provider('formatFileSizeFilter', function () { 159 | var $config = { 160 | // Byte units following the IEC format 161 | // http://en.wikipedia.org/wiki/Kilobyte 162 | units: [ 163 | {size: 1000000000, suffix: ' GB'}, 164 | {size: 1000000, suffix: ' MB'}, 165 | {size: 1000, suffix: ' KB'} 166 | ] 167 | }; 168 | this.defaults = $config; 169 | this.$get = function () { 170 | return function (bytes) { 171 | if (!angular.isNumber(bytes)) { 172 | return ''; 173 | } 174 | var unit = true, 175 | i = 0, 176 | prefix, 177 | suffix; 178 | while (unit) { 179 | unit = $config.units[i]; 180 | prefix = unit.prefix || ''; 181 | suffix = unit.suffix || ''; 182 | if (i === $config.units.length - 1 || bytes >= unit.size) { 183 | return prefix + (bytes / unit.size).toFixed(2) + suffix; 184 | } 185 | i += 1; 186 | } 187 | }; 188 | }; 189 | }) 190 | 191 | // The FileUploadController initializes the fileupload widget and 192 | // provides scope methods to control the File Upload functionality: 193 | .controller('FileUploadController', [ 194 | '$scope', '$element', '$attrs', '$window', 'fileUpload', 195 | function ($scope, $element, $attrs, $window, fileUpload) { 196 | var uploadMethods = { 197 | progress: function () { 198 | return $element.fileupload('progress'); 199 | }, 200 | active: function () { 201 | return $element.fileupload('active'); 202 | }, 203 | option: function (option, data) { 204 | if (arguments.length === 1) { 205 | return $element.fileupload('option', option); 206 | } 207 | $element.fileupload('option', option, data); 208 | }, 209 | add: function (data) { 210 | return $element.fileupload('add', data); 211 | }, 212 | send: function (data) { 213 | return $element.fileupload('send', data); 214 | }, 215 | process: function (data) { 216 | return $element.fileupload('process', data); 217 | }, 218 | processing: function (data) { 219 | return $element.fileupload('processing', data); 220 | } 221 | }; 222 | $scope.disabled = !$window.jQuery.support.fileInput; 223 | $scope.queue = $scope.queue || []; 224 | $scope.clear = function (files) { 225 | var queue = this.queue, 226 | i = queue.length, 227 | file = files, 228 | length = 1; 229 | if (angular.isArray(files)) { 230 | file = files[0]; 231 | length = files.length; 232 | } 233 | while (i) { 234 | i -= 1; 235 | if (queue[i] === file) { 236 | return queue.splice(i, length); 237 | } 238 | } 239 | }; 240 | $scope.replace = function (oldFiles, newFiles) { 241 | var queue = this.queue, 242 | file = oldFiles[0], 243 | i, 244 | j; 245 | for (i = 0; i < queue.length; i += 1) { 246 | if (queue[i] === file) { 247 | for (j = 0; j < newFiles.length; j += 1) { 248 | queue[i + j] = newFiles[j]; 249 | } 250 | return; 251 | } 252 | } 253 | }; 254 | $scope.applyOnQueue = function (method) { 255 | var list = this.queue.slice(0), 256 | i, 257 | file; 258 | for (i = 0; i < list.length; i += 1) { 259 | file = list[i]; 260 | if (file[method]) { 261 | file[method](); 262 | } 263 | } 264 | }; 265 | $scope.submit = function () { 266 | this.applyOnQueue('$submit'); 267 | }; 268 | $scope.cancel = function () { 269 | this.applyOnQueue('$cancel'); 270 | }; 271 | // Add upload methods to the scope: 272 | angular.extend($scope, uploadMethods); 273 | // The fileupload widget will initialize with 274 | // the options provided via "data-"-parameters, 275 | // as well as those given via options object: 276 | $element.fileupload(angular.extend( 277 | {scope: $scope}, 278 | fileUpload.defaults 279 | )).on('fileuploadadd', function (e, data) { 280 | data.scope = $scope; 281 | }).on('fileuploadfail', function (e, data) { 282 | if (data.errorThrown === 'abort') { 283 | return; 284 | } 285 | if (data.dataType && 286 | data.dataType.indexOf('json') === data.dataType.length - 4) { 287 | try { 288 | data.result = angular.fromJson(data.jqXHR.responseText); 289 | } catch (ignore) {} 290 | } 291 | }).on([ 292 | 'fileuploadadd', 293 | 'fileuploadsubmit', 294 | 'fileuploadsend', 295 | 'fileuploaddone', 296 | 'fileuploadfail', 297 | 'fileuploadalways', 298 | 'fileuploadprogress', 299 | 'fileuploadprogressall', 300 | 'fileuploadstart', 301 | 'fileuploadstop', 302 | 'fileuploadchange', 303 | 'fileuploadpaste', 304 | 'fileuploaddrop', 305 | 'fileuploaddragover', 306 | 'fileuploadchunksend', 307 | 'fileuploadchunkdone', 308 | 'fileuploadchunkfail', 309 | 'fileuploadchunkalways', 310 | 'fileuploadprocessstart', 311 | 'fileuploadprocess', 312 | 'fileuploadprocessdone', 313 | 'fileuploadprocessfail', 314 | 'fileuploadprocessalways', 315 | 'fileuploadprocessstop' 316 | ].join(' '), function (e, data) { 317 | $scope.$parent.$applyAsync(function () { 318 | if ($scope.$emit(e.type, data).defaultPrevented) { 319 | e.preventDefault(); 320 | } 321 | }); 322 | }).on('remove', function () { 323 | // Remove upload methods from the scope, 324 | // when the widget is removed: 325 | var method; 326 | for (method in uploadMethods) { 327 | if (uploadMethods.hasOwnProperty(method)) { 328 | delete $scope[method]; 329 | } 330 | } 331 | }); 332 | // Observe option changes: 333 | $scope.$watch( 334 | $attrs.fileUpload, 335 | function (newOptions) { 336 | if (newOptions) { 337 | $element.fileupload('option', newOptions); 338 | } 339 | } 340 | ); 341 | } 342 | ]) 343 | 344 | // Provide File Upload progress feedback: 345 | .controller('FileUploadProgressController', [ 346 | '$scope', '$attrs', '$parse', 347 | function ($scope, $attrs, $parse) { 348 | var fn = $parse($attrs.fileUploadProgress), 349 | update = function () { 350 | var progress = fn($scope); 351 | if (!progress || !progress.total) { 352 | return; 353 | } 354 | $scope.num = Math.floor( 355 | progress.loaded / progress.total * 100 356 | ); 357 | }; 358 | update(); 359 | $scope.$watch( 360 | $attrs.fileUploadProgress + '.loaded', 361 | function (newValue, oldValue) { 362 | if (newValue !== oldValue) { 363 | update(); 364 | } 365 | } 366 | ); 367 | } 368 | ]) 369 | 370 | // Display File Upload previews: 371 | .controller('FileUploadPreviewController', [ 372 | '$scope', '$element', '$attrs', 373 | function ($scope, $element, $attrs) { 374 | $scope.$watch( 375 | $attrs.fileUploadPreview + '.preview', 376 | function (preview) { 377 | $element.empty(); 378 | if (preview) { 379 | $element.append(preview); 380 | } 381 | } 382 | ); 383 | } 384 | ]) 385 | 386 | .directive('fileUpload', function () { 387 | return { 388 | controller: 'FileUploadController', 389 | scope: true 390 | }; 391 | }) 392 | 393 | .directive('fileUploadProgress', function () { 394 | return { 395 | controller: 'FileUploadProgressController', 396 | scope: true 397 | }; 398 | }) 399 | 400 | .directive('fileUploadPreview', function () { 401 | return { 402 | controller: 'FileUploadPreviewController' 403 | }; 404 | }) 405 | 406 | // Enhance the HTML5 download attribute to 407 | // allow drag&drop of files to the desktop: 408 | .directive('download', function () { 409 | return function (scope, elm) { 410 | elm.on('dragstart', function (e) { 411 | try { 412 | e.originalEvent.dataTransfer.setData( 413 | 'DownloadURL', 414 | [ 415 | 'application/octet-stream', 416 | elm.prop('download'), 417 | elm.prop('href') 418 | ].join(':') 419 | ); 420 | } catch (ignore) {} 421 | }); 422 | }; 423 | }); 424 | 425 | })); 426 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery.fileupload-audio.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Audio Preview Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window, document */ 14 | 15 | ;(function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | './jquery.fileupload-process' 23 | ], factory); 24 | } else if (typeof exports === 'object') { 25 | // Node/CommonJS: 26 | factory( 27 | require('jquery'), 28 | require('load-image') 29 | ); 30 | } else { 31 | // Browser globals: 32 | factory( 33 | window.jQuery, 34 | window.loadImage 35 | ); 36 | } 37 | }(function ($, loadImage) { 38 | 'use strict'; 39 | 40 | // Prepend to the default processQueue: 41 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 42 | { 43 | action: 'loadAudio', 44 | // Use the action as prefix for the "@" options: 45 | prefix: true, 46 | fileTypes: '@', 47 | maxFileSize: '@', 48 | disabled: '@disableAudioPreview' 49 | }, 50 | { 51 | action: 'setAudio', 52 | name: '@audioPreviewName', 53 | disabled: '@disableAudioPreview' 54 | } 55 | ); 56 | 57 | // The File Upload Audio Preview plugin extends the fileupload widget 58 | // with audio preview functionality: 59 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 60 | 61 | options: { 62 | // The regular expression for the types of audio files to load, 63 | // matched against the file type: 64 | loadAudioFileTypes: /^audio\/.*$/ 65 | }, 66 | 67 | _audioElement: document.createElement('audio'), 68 | 69 | processActions: { 70 | 71 | // Loads the audio file given via data.files and data.index 72 | // as audio element if the browser supports playing it. 73 | // Accepts the options fileTypes (regular expression) 74 | // and maxFileSize (integer) to limit the files to load: 75 | loadAudio: function (data, options) { 76 | if (options.disabled) { 77 | return data; 78 | } 79 | var file = data.files[data.index], 80 | url, 81 | audio; 82 | if (this._audioElement.canPlayType && 83 | this._audioElement.canPlayType(file.type) && 84 | ($.type(options.maxFileSize) !== 'number' || 85 | file.size <= options.maxFileSize) && 86 | (!options.fileTypes || 87 | options.fileTypes.test(file.type))) { 88 | url = loadImage.createObjectURL(file); 89 | if (url) { 90 | audio = this._audioElement.cloneNode(false); 91 | audio.src = url; 92 | audio.controls = true; 93 | data.audio = audio; 94 | return data; 95 | } 96 | } 97 | return data; 98 | }, 99 | 100 | // Sets the audio element as a property of the file object: 101 | setAudio: function (data, options) { 102 | if (data.audio && !options.disabled) { 103 | data.files[data.index][options.name || 'preview'] = data.audio; 104 | } 105 | return data; 106 | } 107 | 108 | } 109 | 110 | }); 111 | 112 | })); 113 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery.fileupload-image.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Image Preview & Resize Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window, Blob */ 14 | 15 | ;(function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | 'load-image-meta', 23 | 'load-image-exif', 24 | 'canvas-to-blob', 25 | './jquery.fileupload-process' 26 | ], factory); 27 | } else if (typeof exports === 'object') { 28 | // Node/CommonJS: 29 | factory( 30 | require('jquery'), 31 | require('load-image') 32 | ); 33 | } else { 34 | // Browser globals: 35 | factory( 36 | window.jQuery, 37 | window.loadImage 38 | ); 39 | } 40 | }(function ($, loadImage) { 41 | 'use strict'; 42 | 43 | // Prepend to the default processQueue: 44 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 45 | { 46 | action: 'loadImageMetaData', 47 | disableImageHead: '@', 48 | disableExif: '@', 49 | disableExifThumbnail: '@', 50 | disableExifSub: '@', 51 | disableExifGps: '@', 52 | disabled: '@disableImageMetaDataLoad' 53 | }, 54 | { 55 | action: 'loadImage', 56 | // Use the action as prefix for the "@" options: 57 | prefix: true, 58 | fileTypes: '@', 59 | maxFileSize: '@', 60 | noRevoke: '@', 61 | disabled: '@disableImageLoad' 62 | }, 63 | { 64 | action: 'resizeImage', 65 | // Use "image" as prefix for the "@" options: 66 | prefix: 'image', 67 | maxWidth: '@', 68 | maxHeight: '@', 69 | minWidth: '@', 70 | minHeight: '@', 71 | crop: '@', 72 | orientation: '@', 73 | forceResize: '@', 74 | disabled: '@disableImageResize' 75 | }, 76 | { 77 | action: 'saveImage', 78 | quality: '@imageQuality', 79 | type: '@imageType', 80 | disabled: '@disableImageResize' 81 | }, 82 | { 83 | action: 'saveImageMetaData', 84 | disabled: '@disableImageMetaDataSave' 85 | }, 86 | { 87 | action: 'resizeImage', 88 | // Use "preview" as prefix for the "@" options: 89 | prefix: 'preview', 90 | maxWidth: '@', 91 | maxHeight: '@', 92 | minWidth: '@', 93 | minHeight: '@', 94 | crop: '@', 95 | orientation: '@', 96 | thumbnail: '@', 97 | canvas: '@', 98 | disabled: '@disableImagePreview' 99 | }, 100 | { 101 | action: 'setImage', 102 | name: '@imagePreviewName', 103 | disabled: '@disableImagePreview' 104 | }, 105 | { 106 | action: 'deleteImageReferences', 107 | disabled: '@disableImageReferencesDeletion' 108 | } 109 | ); 110 | 111 | // The File Upload Resize plugin extends the fileupload widget 112 | // with image resize functionality: 113 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 114 | 115 | options: { 116 | // The regular expression for the types of images to load: 117 | // matched against the file type: 118 | loadImageFileTypes: /^image\/(gif|jpeg|png|svg\+xml)$/, 119 | // The maximum file size of images to load: 120 | loadImageMaxFileSize: 10000000, // 10MB 121 | // The maximum width of resized images: 122 | imageMaxWidth: 1920, 123 | // The maximum height of resized images: 124 | imageMaxHeight: 1080, 125 | // Defines the image orientation (1-8) or takes the orientation 126 | // value from Exif data if set to true: 127 | imageOrientation: false, 128 | // Define if resized images should be cropped or only scaled: 129 | imageCrop: false, 130 | // Disable the resize image functionality by default: 131 | disableImageResize: true, 132 | // The maximum width of the preview images: 133 | previewMaxWidth: 80, 134 | // The maximum height of the preview images: 135 | previewMaxHeight: 80, 136 | // Defines the preview orientation (1-8) or takes the orientation 137 | // value from Exif data if set to true: 138 | previewOrientation: true, 139 | // Create the preview using the Exif data thumbnail: 140 | previewThumbnail: true, 141 | // Define if preview images should be cropped or only scaled: 142 | previewCrop: false, 143 | // Define if preview images should be resized as canvas elements: 144 | previewCanvas: true 145 | }, 146 | 147 | processActions: { 148 | 149 | // Loads the image given via data.files and data.index 150 | // as img element, if the browser supports the File API. 151 | // Accepts the options fileTypes (regular expression) 152 | // and maxFileSize (integer) to limit the files to load: 153 | loadImage: function (data, options) { 154 | if (options.disabled) { 155 | return data; 156 | } 157 | var that = this, 158 | file = data.files[data.index], 159 | dfd = $.Deferred(); 160 | if (($.type(options.maxFileSize) === 'number' && 161 | file.size > options.maxFileSize) || 162 | (options.fileTypes && 163 | !options.fileTypes.test(file.type)) || 164 | !loadImage( 165 | file, 166 | function (img) { 167 | if (img.src) { 168 | data.img = img; 169 | } 170 | dfd.resolveWith(that, [data]); 171 | }, 172 | options 173 | )) { 174 | return data; 175 | } 176 | return dfd.promise(); 177 | }, 178 | 179 | // Resizes the image given as data.canvas or data.img 180 | // and updates data.canvas or data.img with the resized image. 181 | // Also stores the resized image as preview property. 182 | // Accepts the options maxWidth, maxHeight, minWidth, 183 | // minHeight, canvas and crop: 184 | resizeImage: function (data, options) { 185 | if (options.disabled || !(data.canvas || data.img)) { 186 | return data; 187 | } 188 | options = $.extend({canvas: true}, options); 189 | var that = this, 190 | dfd = $.Deferred(), 191 | img = (options.canvas && data.canvas) || data.img, 192 | resolve = function (newImg) { 193 | if (newImg && (newImg.width !== img.width || 194 | newImg.height !== img.height || 195 | options.forceResize)) { 196 | data[newImg.getContext ? 'canvas' : 'img'] = newImg; 197 | } 198 | data.preview = newImg; 199 | dfd.resolveWith(that, [data]); 200 | }, 201 | thumbnail; 202 | if (data.exif) { 203 | if (options.orientation === true) { 204 | options.orientation = data.exif.get('Orientation'); 205 | } 206 | if (options.thumbnail) { 207 | thumbnail = data.exif.get('Thumbnail'); 208 | if (thumbnail) { 209 | loadImage(thumbnail, resolve, options); 210 | return dfd.promise(); 211 | } 212 | } 213 | // Prevent orienting the same image twice: 214 | if (data.orientation) { 215 | delete options.orientation; 216 | } else { 217 | data.orientation = options.orientation; 218 | } 219 | } 220 | if (img) { 221 | resolve(loadImage.scale(img, options)); 222 | return dfd.promise(); 223 | } 224 | return data; 225 | }, 226 | 227 | // Saves the processed image given as data.canvas 228 | // inplace at data.index of data.files: 229 | saveImage: function (data, options) { 230 | if (!data.canvas || options.disabled) { 231 | return data; 232 | } 233 | var that = this, 234 | file = data.files[data.index], 235 | dfd = $.Deferred(); 236 | if (data.canvas.toBlob) { 237 | data.canvas.toBlob( 238 | function (blob) { 239 | if (!blob.name) { 240 | if (file.type === blob.type) { 241 | blob.name = file.name; 242 | } else if (file.name) { 243 | blob.name = file.name.replace( 244 | /\.\w+$/, 245 | '.' + blob.type.substr(6) 246 | ); 247 | } 248 | } 249 | // Don't restore invalid meta data: 250 | if (file.type !== blob.type) { 251 | delete data.imageHead; 252 | } 253 | // Store the created blob at the position 254 | // of the original file in the files list: 255 | data.files[data.index] = blob; 256 | dfd.resolveWith(that, [data]); 257 | }, 258 | options.type || file.type, 259 | options.quality 260 | ); 261 | } else { 262 | return data; 263 | } 264 | return dfd.promise(); 265 | }, 266 | 267 | loadImageMetaData: function (data, options) { 268 | if (options.disabled) { 269 | return data; 270 | } 271 | var that = this, 272 | dfd = $.Deferred(); 273 | loadImage.parseMetaData(data.files[data.index], function (result) { 274 | $.extend(data, result); 275 | dfd.resolveWith(that, [data]); 276 | }, options); 277 | return dfd.promise(); 278 | }, 279 | 280 | saveImageMetaData: function (data, options) { 281 | if (!(data.imageHead && data.canvas && 282 | data.canvas.toBlob && !options.disabled)) { 283 | return data; 284 | } 285 | var file = data.files[data.index], 286 | blob = new Blob([ 287 | data.imageHead, 288 | // Resized images always have a head size of 20 bytes, 289 | // including the JPEG marker and a minimal JFIF header: 290 | this._blobSlice.call(file, 20) 291 | ], {type: file.type}); 292 | blob.name = file.name; 293 | data.files[data.index] = blob; 294 | return data; 295 | }, 296 | 297 | // Sets the resized version of the image as a property of the 298 | // file object, must be called after "saveImage": 299 | setImage: function (data, options) { 300 | if (data.preview && !options.disabled) { 301 | data.files[data.index][options.name || 'preview'] = data.preview; 302 | } 303 | return data; 304 | }, 305 | 306 | deleteImageReferences: function (data, options) { 307 | if (!options.disabled) { 308 | delete data.img; 309 | delete data.canvas; 310 | delete data.preview; 311 | delete data.imageHead; 312 | } 313 | return data; 314 | } 315 | 316 | } 317 | 318 | }); 319 | 320 | })); 321 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery.fileupload-jquery-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload jQuery UI Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window */ 14 | 15 | ;(function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define(['jquery', './jquery.fileupload-ui'], factory); 20 | } else if (typeof exports === 'object') { 21 | // Node/CommonJS: 22 | factory(require('jquery')); 23 | } else { 24 | // Browser globals: 25 | factory(window.jQuery); 26 | } 27 | }(function ($) { 28 | 'use strict'; 29 | 30 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 31 | 32 | options: { 33 | processdone: function (e, data) { 34 | data.context.find('.start').button('enable'); 35 | }, 36 | progress: function (e, data) { 37 | if (data.context) { 38 | data.context.find('.progress').progressbar( 39 | 'option', 40 | 'value', 41 | parseInt(data.loaded / data.total * 100, 10) 42 | ); 43 | } 44 | }, 45 | progressall: function (e, data) { 46 | var $this = $(this); 47 | $this.find('.fileupload-progress') 48 | .find('.progress').progressbar( 49 | 'option', 50 | 'value', 51 | parseInt(data.loaded / data.total * 100, 10) 52 | ).end() 53 | .find('.progress-extended').each(function () { 54 | $(this).html( 55 | ($this.data('blueimp-fileupload') || 56 | $this.data('fileupload')) 57 | ._renderExtendedProgress(data) 58 | ); 59 | }); 60 | } 61 | }, 62 | 63 | _renderUpload: function (func, files) { 64 | var node = this._super(func, files), 65 | showIconText = $(window).width() > 480; 66 | node.find('.progress').empty().progressbar(); 67 | node.find('.start').button({ 68 | icons: {primary: 'ui-icon-circle-arrow-e'}, 69 | text: showIconText 70 | }); 71 | node.find('.cancel').button({ 72 | icons: {primary: 'ui-icon-cancel'}, 73 | text: showIconText 74 | }); 75 | if (node.hasClass('fade')) { 76 | node.hide(); 77 | } 78 | return node; 79 | }, 80 | 81 | _renderDownload: function (func, files) { 82 | var node = this._super(func, files), 83 | showIconText = $(window).width() > 480; 84 | node.find('.delete').button({ 85 | icons: {primary: 'ui-icon-trash'}, 86 | text: showIconText 87 | }); 88 | if (node.hasClass('fade')) { 89 | node.hide(); 90 | } 91 | return node; 92 | }, 93 | 94 | _startHandler: function (e) { 95 | $(e.currentTarget).button('disable'); 96 | this._super(e); 97 | }, 98 | 99 | _transition: function (node) { 100 | var deferred = $.Deferred(); 101 | if (node.hasClass('fade')) { 102 | node.fadeToggle( 103 | this.options.transitionDuration, 104 | this.options.transitionEasing, 105 | function () { 106 | deferred.resolveWith(node); 107 | } 108 | ); 109 | } else { 110 | deferred.resolveWith(node); 111 | } 112 | return deferred; 113 | }, 114 | 115 | _create: function () { 116 | this._super(); 117 | this.element 118 | .find('.fileupload-buttonbar') 119 | .find('.fileinput-button').each(function () { 120 | var input = $(this).find('input:file').detach(); 121 | $(this) 122 | .button({icons: {primary: 'ui-icon-plusthick'}}) 123 | .append(input); 124 | }) 125 | .end().find('.start') 126 | .button({icons: {primary: 'ui-icon-circle-arrow-e'}}) 127 | .end().find('.cancel') 128 | .button({icons: {primary: 'ui-icon-cancel'}}) 129 | .end().find('.delete') 130 | .button({icons: {primary: 'ui-icon-trash'}}) 131 | .end().find('.progress').progressbar(); 132 | }, 133 | 134 | _destroy: function () { 135 | this.element 136 | .find('.fileupload-buttonbar') 137 | .find('.fileinput-button').each(function () { 138 | var input = $(this).find('input:file').detach(); 139 | $(this) 140 | .button('destroy') 141 | .append(input); 142 | }) 143 | .end().find('.start') 144 | .button('destroy') 145 | .end().find('.cancel') 146 | .button('destroy') 147 | .end().find('.delete') 148 | .button('destroy') 149 | .end().find('.progress').progressbar('destroy'); 150 | this._super(); 151 | } 152 | 153 | }); 154 | 155 | })); 156 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery.fileupload-process.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Processing Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2012, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window */ 14 | 15 | ;(function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | './jquery.fileupload' 22 | ], factory); 23 | } else if (typeof exports === 'object') { 24 | // Node/CommonJS: 25 | factory(require('jquery')); 26 | } else { 27 | // Browser globals: 28 | factory( 29 | window.jQuery 30 | ); 31 | } 32 | }(function ($) { 33 | 'use strict'; 34 | 35 | var originalAdd = $.blueimp.fileupload.prototype.options.add; 36 | 37 | // The File Upload Processing plugin extends the fileupload widget 38 | // with file processing functionality: 39 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 40 | 41 | options: { 42 | // The list of processing actions: 43 | processQueue: [ 44 | /* 45 | { 46 | action: 'log', 47 | type: 'debug' 48 | } 49 | */ 50 | ], 51 | add: function (e, data) { 52 | var $this = $(this); 53 | data.process(function () { 54 | return $this.fileupload('process', data); 55 | }); 56 | originalAdd.call(this, e, data); 57 | } 58 | }, 59 | 60 | processActions: { 61 | /* 62 | log: function (data, options) { 63 | console[options.type]( 64 | 'Processing "' + data.files[data.index].name + '"' 65 | ); 66 | } 67 | */ 68 | }, 69 | 70 | _processFile: function (data, originalData) { 71 | var that = this, 72 | dfd = $.Deferred().resolveWith(that, [data]), 73 | chain = dfd.promise(); 74 | this._trigger('process', null, data); 75 | $.each(data.processQueue, function (i, settings) { 76 | var func = function (data) { 77 | if (originalData.errorThrown) { 78 | return $.Deferred() 79 | .rejectWith(that, [originalData]).promise(); 80 | } 81 | return that.processActions[settings.action].call( 82 | that, 83 | data, 84 | settings 85 | ); 86 | }; 87 | chain = chain.then(func, settings.always && func); 88 | }); 89 | chain 90 | .done(function () { 91 | that._trigger('processdone', null, data); 92 | that._trigger('processalways', null, data); 93 | }) 94 | .fail(function () { 95 | that._trigger('processfail', null, data); 96 | that._trigger('processalways', null, data); 97 | }); 98 | return chain; 99 | }, 100 | 101 | // Replaces the settings of each processQueue item that 102 | // are strings starting with an "@", using the remaining 103 | // substring as key for the option map, 104 | // e.g. "@autoUpload" is replaced with options.autoUpload: 105 | _transformProcessQueue: function (options) { 106 | var processQueue = []; 107 | $.each(options.processQueue, function () { 108 | var settings = {}, 109 | action = this.action, 110 | prefix = this.prefix === true ? action : this.prefix; 111 | $.each(this, function (key, value) { 112 | if ($.type(value) === 'string' && 113 | value.charAt(0) === '@') { 114 | settings[key] = options[ 115 | value.slice(1) || (prefix ? prefix + 116 | key.charAt(0).toUpperCase() + key.slice(1) : key) 117 | ]; 118 | } else { 119 | settings[key] = value; 120 | } 121 | 122 | }); 123 | processQueue.push(settings); 124 | }); 125 | options.processQueue = processQueue; 126 | }, 127 | 128 | // Returns the number of files currently in the processsing queue: 129 | processing: function () { 130 | return this._processing; 131 | }, 132 | 133 | // Processes the files given as files property of the data parameter, 134 | // returns a Promise object that allows to bind callbacks: 135 | process: function (data) { 136 | var that = this, 137 | options = $.extend({}, this.options, data); 138 | if (options.processQueue && options.processQueue.length) { 139 | this._transformProcessQueue(options); 140 | if (this._processing === 0) { 141 | this._trigger('processstart'); 142 | } 143 | $.each(data.files, function (index) { 144 | var opts = index ? $.extend({}, options) : options, 145 | func = function () { 146 | if (data.errorThrown) { 147 | return $.Deferred() 148 | .rejectWith(that, [data]).promise(); 149 | } 150 | return that._processFile(opts, data); 151 | }; 152 | opts.index = index; 153 | that._processing += 1; 154 | that._processingQueue = that._processingQueue.then(func, func) 155 | .always(function () { 156 | that._processing -= 1; 157 | if (that._processing === 0) { 158 | that._trigger('processstop'); 159 | } 160 | }); 161 | }); 162 | } 163 | return this._processingQueue; 164 | }, 165 | 166 | _create: function () { 167 | this._super(); 168 | this._processing = 0; 169 | this._processingQueue = $.Deferred().resolveWith(this) 170 | .promise(); 171 | } 172 | 173 | }); 174 | 175 | })); 176 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery.fileupload-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload User Interface Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window */ 14 | 15 | ;(function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'tmpl', 22 | './jquery.fileupload-image', 23 | './jquery.fileupload-audio', 24 | './jquery.fileupload-video', 25 | './jquery.fileupload-validate' 26 | ], factory); 27 | } else if (typeof exports === 'object') { 28 | // Node/CommonJS: 29 | factory( 30 | require('jquery'), 31 | require('tmpl') 32 | ); 33 | } else { 34 | // Browser globals: 35 | factory( 36 | window.jQuery, 37 | window.tmpl 38 | ); 39 | } 40 | }(function ($, tmpl) { 41 | 'use strict'; 42 | 43 | $.blueimp.fileupload.prototype._specialOptions.push( 44 | 'filesContainer', 45 | 'uploadTemplateId', 46 | 'downloadTemplateId' 47 | ); 48 | 49 | // The UI version extends the file upload widget 50 | // and adds complete user interface interaction: 51 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 52 | 53 | options: { 54 | // By default, files added to the widget are uploaded as soon 55 | // as the user clicks on the start buttons. To enable automatic 56 | // uploads, set the following option to true: 57 | autoUpload: false, 58 | // The ID of the upload template: 59 | uploadTemplateId: 'template-upload', 60 | // The ID of the download template: 61 | downloadTemplateId: 'template-download', 62 | // The container for the list of files. If undefined, it is set to 63 | // an element with class "files" inside of the widget element: 64 | filesContainer: undefined, 65 | // By default, files are appended to the files container. 66 | // Set the following option to true, to prepend files instead: 67 | prependFiles: false, 68 | // The expected data type of the upload response, sets the dataType 69 | // option of the $.ajax upload requests: 70 | dataType: 'json', 71 | 72 | // Error and info messages: 73 | messages: { 74 | unknownError: 'Unknown error' 75 | }, 76 | 77 | // Function returning the current number of files, 78 | // used by the maxNumberOfFiles validation: 79 | getNumberOfFiles: function () { 80 | return this.filesContainer.children() 81 | .not('.processing').length; 82 | }, 83 | 84 | // Callback to retrieve the list of files from the server response: 85 | getFilesFromResponse: function (data) { 86 | if (data.result && $.isArray(data.result.files)) { 87 | return data.result.files; 88 | } 89 | return []; 90 | }, 91 | 92 | // The add callback is invoked as soon as files are added to the fileupload 93 | // widget (via file input selection, drag & drop or add API call). 94 | // See the basic file upload widget for more information: 95 | add: function (e, data) { 96 | if (e.isDefaultPrevented()) { 97 | return false; 98 | } 99 | var $this = $(this), 100 | that = $this.data('blueimp-fileupload') || 101 | $this.data('fileupload'), 102 | options = that.options; 103 | data.context = that._renderUpload(data.files) 104 | .data('data', data) 105 | .addClass('processing'); 106 | options.filesContainer[ 107 | options.prependFiles ? 'prepend' : 'append' 108 | ](data.context); 109 | that._forceReflow(data.context); 110 | that._transition(data.context); 111 | data.process(function () { 112 | return $this.fileupload('process', data); 113 | }).always(function () { 114 | data.context.each(function (index) { 115 | $(this).find('.size').text( 116 | that._formatFileSize(data.files[index].size) 117 | ); 118 | }).removeClass('processing'); 119 | that._renderPreviews(data); 120 | }).done(function () { 121 | data.context.find('.start').prop('disabled', false); 122 | if ((that._trigger('added', e, data) !== false) && 123 | (options.autoUpload || data.autoUpload) && 124 | data.autoUpload !== false) { 125 | data.submit(); 126 | } 127 | }).fail(function () { 128 | if (data.files.error) { 129 | data.context.each(function (index) { 130 | var error = data.files[index].error; 131 | if (error) { 132 | $(this).find('.error').text(error); 133 | } 134 | }); 135 | } 136 | }); 137 | }, 138 | // Callback for the start of each file upload request: 139 | send: function (e, data) { 140 | if (e.isDefaultPrevented()) { 141 | return false; 142 | } 143 | var that = $(this).data('blueimp-fileupload') || 144 | $(this).data('fileupload'); 145 | if (data.context && data.dataType && 146 | data.dataType.substr(0, 6) === 'iframe') { 147 | // Iframe Transport does not support progress events. 148 | // In lack of an indeterminate progress bar, we set 149 | // the progress to 100%, showing the full animated bar: 150 | data.context 151 | .find('.progress').addClass( 152 | !$.support.transition && 'progress-animated' 153 | ) 154 | .attr('aria-valuenow', 100) 155 | .children().first().css( 156 | 'width', 157 | '100%' 158 | ); 159 | } 160 | return that._trigger('sent', e, data); 161 | }, 162 | // Callback for successful uploads: 163 | done: function (e, data) { 164 | if (e.isDefaultPrevented()) { 165 | return false; 166 | } 167 | var that = $(this).data('blueimp-fileupload') || 168 | $(this).data('fileupload'), 169 | getFilesFromResponse = data.getFilesFromResponse || 170 | that.options.getFilesFromResponse, 171 | files = getFilesFromResponse(data), 172 | template, 173 | deferred; 174 | if (data.context) { 175 | data.context.each(function (index) { 176 | var file = files[index] || 177 | {error: 'Empty file upload result'}; 178 | deferred = that._addFinishedDeferreds(); 179 | that._transition($(this)).done( 180 | function () { 181 | var node = $(this); 182 | template = that._renderDownload([file]) 183 | .replaceAll(node); 184 | that._forceReflow(template); 185 | that._transition(template).done( 186 | function () { 187 | data.context = $(this); 188 | that._trigger('completed', e, data); 189 | that._trigger('finished', e, data); 190 | deferred.resolve(); 191 | } 192 | ); 193 | } 194 | ); 195 | }); 196 | } else { 197 | template = that._renderDownload(files)[ 198 | that.options.prependFiles ? 'prependTo' : 'appendTo' 199 | ](that.options.filesContainer); 200 | that._forceReflow(template); 201 | deferred = that._addFinishedDeferreds(); 202 | that._transition(template).done( 203 | function () { 204 | data.context = $(this); 205 | that._trigger('completed', e, data); 206 | that._trigger('finished', e, data); 207 | deferred.resolve(); 208 | } 209 | ); 210 | } 211 | }, 212 | // Callback for failed (abort or error) uploads: 213 | fail: function (e, data) { 214 | if (e.isDefaultPrevented()) { 215 | return false; 216 | } 217 | var that = $(this).data('blueimp-fileupload') || 218 | $(this).data('fileupload'), 219 | template, 220 | deferred; 221 | if (data.context) { 222 | data.context.each(function (index) { 223 | if (data.errorThrown !== 'abort') { 224 | var file = data.files[index]; 225 | file.error = file.error || data.errorThrown || 226 | data.i18n('unknownError'); 227 | deferred = that._addFinishedDeferreds(); 228 | that._transition($(this)).done( 229 | function () { 230 | var node = $(this); 231 | template = that._renderDownload([file]) 232 | .replaceAll(node); 233 | that._forceReflow(template); 234 | that._transition(template).done( 235 | function () { 236 | data.context = $(this); 237 | that._trigger('failed', e, data); 238 | that._trigger('finished', e, data); 239 | deferred.resolve(); 240 | } 241 | ); 242 | } 243 | ); 244 | } else { 245 | deferred = that._addFinishedDeferreds(); 246 | that._transition($(this)).done( 247 | function () { 248 | $(this).remove(); 249 | that._trigger('failed', e, data); 250 | that._trigger('finished', e, data); 251 | deferred.resolve(); 252 | } 253 | ); 254 | } 255 | }); 256 | } else if (data.errorThrown !== 'abort') { 257 | data.context = that._renderUpload(data.files)[ 258 | that.options.prependFiles ? 'prependTo' : 'appendTo' 259 | ](that.options.filesContainer) 260 | .data('data', data); 261 | that._forceReflow(data.context); 262 | deferred = that._addFinishedDeferreds(); 263 | that._transition(data.context).done( 264 | function () { 265 | data.context = $(this); 266 | that._trigger('failed', e, data); 267 | that._trigger('finished', e, data); 268 | deferred.resolve(); 269 | } 270 | ); 271 | } else { 272 | that._trigger('failed', e, data); 273 | that._trigger('finished', e, data); 274 | that._addFinishedDeferreds().resolve(); 275 | } 276 | }, 277 | // Callback for upload progress events: 278 | progress: function (e, data) { 279 | if (e.isDefaultPrevented()) { 280 | return false; 281 | } 282 | var progress = Math.floor(data.loaded / data.total * 100); 283 | if (data.context) { 284 | data.context.each(function () { 285 | $(this).find('.progress') 286 | .attr('aria-valuenow', progress) 287 | .children().first().css( 288 | 'width', 289 | progress + '%' 290 | ); 291 | }); 292 | } 293 | }, 294 | // Callback for global upload progress events: 295 | progressall: function (e, data) { 296 | if (e.isDefaultPrevented()) { 297 | return false; 298 | } 299 | var $this = $(this), 300 | progress = Math.floor(data.loaded / data.total * 100), 301 | globalProgressNode = $this.find('.fileupload-progress'), 302 | extendedProgressNode = globalProgressNode 303 | .find('.progress-extended'); 304 | if (extendedProgressNode.length) { 305 | extendedProgressNode.html( 306 | ($this.data('blueimp-fileupload') || $this.data('fileupload')) 307 | ._renderExtendedProgress(data) 308 | ); 309 | } 310 | globalProgressNode 311 | .find('.progress') 312 | .attr('aria-valuenow', progress) 313 | .children().first().css( 314 | 'width', 315 | progress + '%' 316 | ); 317 | }, 318 | // Callback for uploads start, equivalent to the global ajaxStart event: 319 | start: function (e) { 320 | if (e.isDefaultPrevented()) { 321 | return false; 322 | } 323 | var that = $(this).data('blueimp-fileupload') || 324 | $(this).data('fileupload'); 325 | that._resetFinishedDeferreds(); 326 | that._transition($(this).find('.fileupload-progress')).done( 327 | function () { 328 | that._trigger('started', e); 329 | } 330 | ); 331 | }, 332 | // Callback for uploads stop, equivalent to the global ajaxStop event: 333 | stop: function (e) { 334 | if (e.isDefaultPrevented()) { 335 | return false; 336 | } 337 | var that = $(this).data('blueimp-fileupload') || 338 | $(this).data('fileupload'), 339 | deferred = that._addFinishedDeferreds(); 340 | $.when.apply($, that._getFinishedDeferreds()) 341 | .done(function () { 342 | that._trigger('stopped', e); 343 | }); 344 | that._transition($(this).find('.fileupload-progress')).done( 345 | function () { 346 | $(this).find('.progress') 347 | .attr('aria-valuenow', '0') 348 | .children().first().css('width', '0%'); 349 | $(this).find('.progress-extended').html(' '); 350 | deferred.resolve(); 351 | } 352 | ); 353 | }, 354 | processstart: function (e) { 355 | if (e.isDefaultPrevented()) { 356 | return false; 357 | } 358 | $(this).addClass('fileupload-processing'); 359 | }, 360 | processstop: function (e) { 361 | if (e.isDefaultPrevented()) { 362 | return false; 363 | } 364 | $(this).removeClass('fileupload-processing'); 365 | }, 366 | // Callback for file deletion: 367 | destroy: function (e, data) { 368 | if (e.isDefaultPrevented()) { 369 | return false; 370 | } 371 | var that = $(this).data('blueimp-fileupload') || 372 | $(this).data('fileupload'), 373 | removeNode = function () { 374 | that._transition(data.context).done( 375 | function () { 376 | $(this).remove(); 377 | that._trigger('destroyed', e, data); 378 | } 379 | ); 380 | }; 381 | if (data.url) { 382 | data.dataType = data.dataType || that.options.dataType; 383 | $.ajax(data).done(removeNode).fail(function () { 384 | that._trigger('destroyfailed', e, data); 385 | }); 386 | } else { 387 | removeNode(); 388 | } 389 | } 390 | }, 391 | 392 | _resetFinishedDeferreds: function () { 393 | this._finishedUploads = []; 394 | }, 395 | 396 | _addFinishedDeferreds: function (deferred) { 397 | if (!deferred) { 398 | deferred = $.Deferred(); 399 | } 400 | this._finishedUploads.push(deferred); 401 | return deferred; 402 | }, 403 | 404 | _getFinishedDeferreds: function () { 405 | return this._finishedUploads; 406 | }, 407 | 408 | // Link handler, that allows to download files 409 | // by drag & drop of the links to the desktop: 410 | _enableDragToDesktop: function () { 411 | var link = $(this), 412 | url = link.prop('href'), 413 | name = link.prop('download'), 414 | type = 'application/octet-stream'; 415 | link.bind('dragstart', function (e) { 416 | try { 417 | e.originalEvent.dataTransfer.setData( 418 | 'DownloadURL', 419 | [type, name, url].join(':') 420 | ); 421 | } catch (ignore) {} 422 | }); 423 | }, 424 | 425 | _formatFileSize: function (bytes) { 426 | if (typeof bytes !== 'number') { 427 | return ''; 428 | } 429 | if (bytes >= 1000000000) { 430 | return (bytes / 1000000000).toFixed(2) + ' GB'; 431 | } 432 | if (bytes >= 1000000) { 433 | return (bytes / 1000000).toFixed(2) + ' MB'; 434 | } 435 | return (bytes / 1000).toFixed(2) + ' KB'; 436 | }, 437 | 438 | _formatBitrate: function (bits) { 439 | if (typeof bits !== 'number') { 440 | return ''; 441 | } 442 | if (bits >= 1000000000) { 443 | return (bits / 1000000000).toFixed(2) + ' Gbit/s'; 444 | } 445 | if (bits >= 1000000) { 446 | return (bits / 1000000).toFixed(2) + ' Mbit/s'; 447 | } 448 | if (bits >= 1000) { 449 | return (bits / 1000).toFixed(2) + ' kbit/s'; 450 | } 451 | return bits.toFixed(2) + ' bit/s'; 452 | }, 453 | 454 | _formatTime: function (seconds) { 455 | var date = new Date(seconds * 1000), 456 | days = Math.floor(seconds / 86400); 457 | days = days ? days + 'd ' : ''; 458 | return days + 459 | ('0' + date.getUTCHours()).slice(-2) + ':' + 460 | ('0' + date.getUTCMinutes()).slice(-2) + ':' + 461 | ('0' + date.getUTCSeconds()).slice(-2); 462 | }, 463 | 464 | _formatPercentage: function (floatValue) { 465 | return (floatValue * 100).toFixed(2) + ' %'; 466 | }, 467 | 468 | _renderExtendedProgress: function (data) { 469 | return this._formatBitrate(data.bitrate) + ' | ' + 470 | this._formatTime( 471 | (data.total - data.loaded) * 8 / data.bitrate 472 | ) + ' | ' + 473 | this._formatPercentage( 474 | data.loaded / data.total 475 | ) + ' | ' + 476 | this._formatFileSize(data.loaded) + ' / ' + 477 | this._formatFileSize(data.total); 478 | }, 479 | 480 | _renderTemplate: function (func, files) { 481 | if (!func) { 482 | return $(); 483 | } 484 | var result = func({ 485 | files: files, 486 | formatFileSize: this._formatFileSize, 487 | options: this.options 488 | }); 489 | if (result instanceof $) { 490 | return result; 491 | } 492 | return $(this.options.templatesContainer).html(result).children(); 493 | }, 494 | 495 | _renderPreviews: function (data) { 496 | data.context.find('.preview').each(function (index, elm) { 497 | $(elm).append(data.files[index].preview); 498 | }); 499 | }, 500 | 501 | _renderUpload: function (files) { 502 | return this._renderTemplate( 503 | this.options.uploadTemplate, 504 | files 505 | ); 506 | }, 507 | 508 | _renderDownload: function (files) { 509 | return this._renderTemplate( 510 | this.options.downloadTemplate, 511 | files 512 | ).find('a[download]').each(this._enableDragToDesktop).end(); 513 | }, 514 | 515 | _startHandler: function (e) { 516 | e.preventDefault(); 517 | var button = $(e.currentTarget), 518 | template = button.closest('.template-upload'), 519 | data = template.data('data'); 520 | button.prop('disabled', true); 521 | if (data && data.submit) { 522 | data.submit(); 523 | } 524 | }, 525 | 526 | _cancelHandler: function (e) { 527 | e.preventDefault(); 528 | var template = $(e.currentTarget) 529 | .closest('.template-upload,.template-download'), 530 | data = template.data('data') || {}; 531 | data.context = data.context || template; 532 | if (data.abort) { 533 | data.abort(); 534 | } else { 535 | data.errorThrown = 'abort'; 536 | this._trigger('fail', e, data); 537 | } 538 | }, 539 | 540 | _deleteHandler: function (e) { 541 | e.preventDefault(); 542 | var button = $(e.currentTarget); 543 | this._trigger('destroy', e, $.extend({ 544 | context: button.closest('.template-download'), 545 | type: 'DELETE' 546 | }, button.data())); 547 | }, 548 | 549 | _forceReflow: function (node) { 550 | return $.support.transition && node.length && 551 | node[0].offsetWidth; 552 | }, 553 | 554 | _transition: function (node) { 555 | var dfd = $.Deferred(); 556 | if ($.support.transition && node.hasClass('fade') && node.is(':visible')) { 557 | node.bind( 558 | $.support.transition.end, 559 | function (e) { 560 | // Make sure we don't respond to other transitions events 561 | // in the container element, e.g. from button elements: 562 | if (e.target === node[0]) { 563 | node.unbind($.support.transition.end); 564 | dfd.resolveWith(node); 565 | } 566 | } 567 | ).toggleClass('in'); 568 | } else { 569 | node.toggleClass('in'); 570 | dfd.resolveWith(node); 571 | } 572 | return dfd; 573 | }, 574 | 575 | _initButtonBarEventHandlers: function () { 576 | var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), 577 | filesList = this.options.filesContainer; 578 | this._on(fileUploadButtonBar.find('.start'), { 579 | click: function (e) { 580 | e.preventDefault(); 581 | filesList.find('.start').click(); 582 | } 583 | }); 584 | this._on(fileUploadButtonBar.find('.cancel'), { 585 | click: function (e) { 586 | e.preventDefault(); 587 | filesList.find('.cancel').click(); 588 | } 589 | }); 590 | this._on(fileUploadButtonBar.find('.delete'), { 591 | click: function (e) { 592 | e.preventDefault(); 593 | filesList.find('.toggle:checked') 594 | .closest('.template-download') 595 | .find('.delete').click(); 596 | fileUploadButtonBar.find('.toggle') 597 | .prop('checked', false); 598 | } 599 | }); 600 | this._on(fileUploadButtonBar.find('.toggle'), { 601 | change: function (e) { 602 | filesList.find('.toggle').prop( 603 | 'checked', 604 | $(e.currentTarget).is(':checked') 605 | ); 606 | } 607 | }); 608 | }, 609 | 610 | _destroyButtonBarEventHandlers: function () { 611 | this._off( 612 | this.element.find('.fileupload-buttonbar') 613 | .find('.start, .cancel, .delete'), 614 | 'click' 615 | ); 616 | this._off( 617 | this.element.find('.fileupload-buttonbar .toggle'), 618 | 'change.' 619 | ); 620 | }, 621 | 622 | _initEventHandlers: function () { 623 | this._super(); 624 | this._on(this.options.filesContainer, { 625 | 'click .start': this._startHandler, 626 | 'click .cancel': this._cancelHandler, 627 | 'click .delete': this._deleteHandler 628 | }); 629 | this._initButtonBarEventHandlers(); 630 | }, 631 | 632 | _destroyEventHandlers: function () { 633 | this._destroyButtonBarEventHandlers(); 634 | this._off(this.options.filesContainer, 'click'); 635 | this._super(); 636 | }, 637 | 638 | _enableFileInputButton: function () { 639 | this.element.find('.fileinput-button input') 640 | .prop('disabled', false) 641 | .parent().removeClass('disabled'); 642 | }, 643 | 644 | _disableFileInputButton: function () { 645 | this.element.find('.fileinput-button input') 646 | .prop('disabled', true) 647 | .parent().addClass('disabled'); 648 | }, 649 | 650 | _initTemplates: function () { 651 | var options = this.options; 652 | options.templatesContainer = this.document[0].createElement( 653 | options.filesContainer.prop('nodeName') 654 | ); 655 | if (tmpl) { 656 | if (options.uploadTemplateId) { 657 | options.uploadTemplate = tmpl(options.uploadTemplateId); 658 | } 659 | if (options.downloadTemplateId) { 660 | options.downloadTemplate = tmpl(options.downloadTemplateId); 661 | } 662 | } 663 | }, 664 | 665 | _initFilesContainer: function () { 666 | var options = this.options; 667 | if (options.filesContainer === undefined) { 668 | options.filesContainer = this.element.find('.files'); 669 | } else if (!(options.filesContainer instanceof $)) { 670 | options.filesContainer = $(options.filesContainer); 671 | } 672 | }, 673 | 674 | _initSpecialOptions: function () { 675 | this._super(); 676 | this._initFilesContainer(); 677 | this._initTemplates(); 678 | }, 679 | 680 | _create: function () { 681 | this._super(); 682 | this._resetFinishedDeferreds(); 683 | if (!$.support.fileInput) { 684 | this._disableFileInputButton(); 685 | } 686 | }, 687 | 688 | enable: function () { 689 | var wasDisabled = false; 690 | if (this.options.disabled) { 691 | wasDisabled = true; 692 | } 693 | this._super(); 694 | if (wasDisabled) { 695 | this.element.find('input, button').prop('disabled', false); 696 | this._enableFileInputButton(); 697 | } 698 | }, 699 | 700 | disable: function () { 701 | if (!this.options.disabled) { 702 | this.element.find('input, button').prop('disabled', true); 703 | this._disableFileInputButton(); 704 | } 705 | this._super(); 706 | } 707 | 708 | }); 709 | 710 | })); 711 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery.fileupload-validate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Validation Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, require, window */ 13 | 14 | ;(function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define([ 19 | 'jquery', 20 | './jquery.fileupload-process' 21 | ], factory); 22 | } else if (typeof exports === 'object') { 23 | // Node/CommonJS: 24 | factory(require('jquery')); 25 | } else { 26 | // Browser globals: 27 | factory( 28 | window.jQuery 29 | ); 30 | } 31 | }(function ($) { 32 | 'use strict'; 33 | 34 | // Append to the default processQueue: 35 | $.blueimp.fileupload.prototype.options.processQueue.push( 36 | { 37 | action: 'validate', 38 | // Always trigger this action, 39 | // even if the previous action was rejected: 40 | always: true, 41 | // Options taken from the global options map: 42 | acceptFileTypes: '@', 43 | maxFileSize: '@', 44 | minFileSize: '@', 45 | maxNumberOfFiles: '@', 46 | disabled: '@disableValidation' 47 | } 48 | ); 49 | 50 | // The File Upload Validation plugin extends the fileupload widget 51 | // with file validation functionality: 52 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 53 | 54 | options: { 55 | /* 56 | // The regular expression for allowed file types, matches 57 | // against either file type or file name: 58 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, 59 | // The maximum allowed file size in bytes: 60 | maxFileSize: 10000000, // 10 MB 61 | // The minimum allowed file size in bytes: 62 | minFileSize: undefined, // No minimal file size 63 | // The limit of files to be uploaded: 64 | maxNumberOfFiles: 10, 65 | */ 66 | 67 | // Function returning the current number of files, 68 | // has to be overriden for maxNumberOfFiles validation: 69 | getNumberOfFiles: $.noop, 70 | 71 | // Error and info messages: 72 | messages: { 73 | maxNumberOfFiles: 'Maximum number of files exceeded', 74 | acceptFileTypes: 'File type not allowed', 75 | maxFileSize: 'File is too large', 76 | minFileSize: 'File is too small' 77 | } 78 | }, 79 | 80 | processActions: { 81 | 82 | validate: function (data, options) { 83 | if (options.disabled) { 84 | return data; 85 | } 86 | var dfd = $.Deferred(), 87 | settings = this.options, 88 | file = data.files[data.index], 89 | fileSize; 90 | if (options.minFileSize || options.maxFileSize) { 91 | fileSize = file.size; 92 | } 93 | if ($.type(options.maxNumberOfFiles) === 'number' && 94 | (settings.getNumberOfFiles() || 0) + data.files.length > 95 | options.maxNumberOfFiles) { 96 | file.error = settings.i18n('maxNumberOfFiles'); 97 | } else if (options.acceptFileTypes && 98 | !(options.acceptFileTypes.test(file.type) || 99 | options.acceptFileTypes.test(file.name))) { 100 | file.error = settings.i18n('acceptFileTypes'); 101 | } else if (fileSize > options.maxFileSize) { 102 | file.error = settings.i18n('maxFileSize'); 103 | } else if ($.type(fileSize) === 'number' && 104 | fileSize < options.minFileSize) { 105 | file.error = settings.i18n('minFileSize'); 106 | } else { 107 | delete file.error; 108 | } 109 | if (file.error || data.files.error) { 110 | data.files.error = true; 111 | dfd.rejectWith(this, [data]); 112 | } else { 113 | dfd.resolveWith(this, [data]); 114 | } 115 | return dfd.promise(); 116 | } 117 | 118 | } 119 | 120 | }); 121 | 122 | })); 123 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery.fileupload-video.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Video Preview Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window, document */ 14 | 15 | ;(function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | './jquery.fileupload-process' 23 | ], factory); 24 | } else if (typeof exports === 'object') { 25 | // Node/CommonJS: 26 | factory( 27 | require('jquery'), 28 | require('load-image') 29 | ); 30 | } else { 31 | // Browser globals: 32 | factory( 33 | window.jQuery, 34 | window.loadImage 35 | ); 36 | } 37 | }(function ($, loadImage) { 38 | 'use strict'; 39 | 40 | // Prepend to the default processQueue: 41 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 42 | { 43 | action: 'loadVideo', 44 | // Use the action as prefix for the "@" options: 45 | prefix: true, 46 | fileTypes: '@', 47 | maxFileSize: '@', 48 | disabled: '@disableVideoPreview' 49 | }, 50 | { 51 | action: 'setVideo', 52 | name: '@videoPreviewName', 53 | disabled: '@disableVideoPreview' 54 | } 55 | ); 56 | 57 | // The File Upload Video Preview plugin extends the fileupload widget 58 | // with video preview functionality: 59 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 60 | 61 | options: { 62 | // The regular expression for the types of video files to load, 63 | // matched against the file type: 64 | loadVideoFileTypes: /^video\/.*$/ 65 | }, 66 | 67 | _videoElement: document.createElement('video'), 68 | 69 | processActions: { 70 | 71 | // Loads the video file given via data.files and data.index 72 | // as video element if the browser supports playing it. 73 | // Accepts the options fileTypes (regular expression) 74 | // and maxFileSize (integer) to limit the files to load: 75 | loadVideo: function (data, options) { 76 | if (options.disabled) { 77 | return data; 78 | } 79 | var file = data.files[data.index], 80 | url, 81 | video; 82 | if (this._videoElement.canPlayType && 83 | this._videoElement.canPlayType(file.type) && 84 | ($.type(options.maxFileSize) !== 'number' || 85 | file.size <= options.maxFileSize) && 86 | (!options.fileTypes || 87 | options.fileTypes.test(file.type))) { 88 | url = loadImage.createObjectURL(file); 89 | if (url) { 90 | video = this._videoElement.cloneNode(false); 91 | video.src = url; 92 | video.controls = true; 93 | data.video = video; 94 | return data; 95 | } 96 | } 97 | return data; 98 | }, 99 | 100 | // Sets the video element as a property of the file object: 101 | setVideo: function (data, options) { 102 | if (data.video && !options.disabled) { 103 | data.files[data.index][options.name || 'preview'] = data.video; 104 | } 105 | return data; 106 | } 107 | 108 | } 109 | 110 | }); 111 | 112 | })); 113 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/jquery.iframe-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Iframe Transport Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, require, window, document */ 13 | 14 | ;(function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define(['jquery'], factory); 19 | } else if (typeof exports === 'object') { 20 | // Node/CommonJS: 21 | factory(require('jquery')); 22 | } else { 23 | // Browser globals: 24 | factory(window.jQuery); 25 | } 26 | }(function ($) { 27 | 'use strict'; 28 | 29 | // Helper variable to create unique names for the transport iframes: 30 | var counter = 0; 31 | 32 | // The iframe transport accepts four additional options: 33 | // options.fileInput: a jQuery collection of file input fields 34 | // options.paramName: the parameter name for the file form data, 35 | // overrides the name property of the file input field(s), 36 | // can be a string or an array of strings. 37 | // options.formData: an array of objects with name and value properties, 38 | // equivalent to the return data of .serializeArray(), e.g.: 39 | // [{name: 'a', value: 1}, {name: 'b', value: 2}] 40 | // options.initialIframeSrc: the URL of the initial iframe src, 41 | // by default set to "javascript:false;" 42 | $.ajaxTransport('iframe', function (options) { 43 | if (options.async) { 44 | // javascript:false as initial iframe src 45 | // prevents warning popups on HTTPS in IE6: 46 | /*jshint scripturl: true */ 47 | var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', 48 | /*jshint scripturl: false */ 49 | form, 50 | iframe, 51 | addParamChar; 52 | return { 53 | send: function (_, completeCallback) { 54 | form = $('
'); 55 | form.attr('accept-charset', options.formAcceptCharset); 56 | addParamChar = /\?/.test(options.url) ? '&' : '?'; 57 | // XDomainRequest only supports GET and POST: 58 | if (options.type === 'DELETE') { 59 | options.url = options.url + addParamChar + '_method=DELETE'; 60 | options.type = 'POST'; 61 | } else if (options.type === 'PUT') { 62 | options.url = options.url + addParamChar + '_method=PUT'; 63 | options.type = 'POST'; 64 | } else if (options.type === 'PATCH') { 65 | options.url = options.url + addParamChar + '_method=PATCH'; 66 | options.type = 'POST'; 67 | } 68 | // IE versions below IE8 cannot set the name property of 69 | // elements that have already been added to the DOM, 70 | // so we set the name along with the iframe HTML markup: 71 | counter += 1; 72 | iframe = $( 73 | '' 75 | ).bind('load', function () { 76 | var fileInputClones, 77 | paramNames = $.isArray(options.paramName) ? 78 | options.paramName : [options.paramName]; 79 | iframe 80 | .unbind('load') 81 | .bind('load', function () { 82 | var response; 83 | // Wrap in a try/catch block to catch exceptions thrown 84 | // when trying to access cross-domain iframe contents: 85 | try { 86 | response = iframe.contents(); 87 | // Google Chrome and Firefox do not throw an 88 | // exception when calling iframe.contents() on 89 | // cross-domain requests, so we unify the response: 90 | if (!response.length || !response[0].firstChild) { 91 | throw new Error(); 92 | } 93 | } catch (e) { 94 | response = undefined; 95 | } 96 | // The complete callback returns the 97 | // iframe content document as response object: 98 | completeCallback( 99 | 200, 100 | 'success', 101 | {'iframe': response} 102 | ); 103 | // Fix for IE endless progress bar activity bug 104 | // (happens on form submits to iframe targets): 105 | $('') 106 | .appendTo(form); 107 | window.setTimeout(function () { 108 | // Removing the form in a setTimeout call 109 | // allows Chrome's developer tools to display 110 | // the response result 111 | form.remove(); 112 | }, 0); 113 | }); 114 | form 115 | .prop('target', iframe.prop('name')) 116 | .prop('action', options.url) 117 | .prop('method', options.type); 118 | if (options.formData) { 119 | $.each(options.formData, function (index, field) { 120 | $('') 121 | .prop('name', field.name) 122 | .val(field.value) 123 | .appendTo(form); 124 | }); 125 | } 126 | if (options.fileInput && options.fileInput.length && 127 | options.type === 'POST') { 128 | fileInputClones = options.fileInput.clone(); 129 | // Insert a clone for each file input field: 130 | options.fileInput.after(function (index) { 131 | return fileInputClones[index]; 132 | }); 133 | if (options.paramName) { 134 | options.fileInput.each(function (index) { 135 | $(this).prop( 136 | 'name', 137 | paramNames[index] || options.paramName 138 | ); 139 | }); 140 | } 141 | // Appending the file input fields to the hidden form 142 | // removes them from their original location: 143 | form 144 | .append(options.fileInput) 145 | .prop('enctype', 'multipart/form-data') 146 | // enctype must be set as encoding for IE: 147 | .prop('encoding', 'multipart/form-data'); 148 | // Remove the HTML5 form attribute from the input(s): 149 | options.fileInput.removeAttr('form'); 150 | } 151 | form.submit(); 152 | // Insert the file input fields at their original location 153 | // by replacing the clones with the originals: 154 | if (fileInputClones && fileInputClones.length) { 155 | options.fileInput.each(function (index, input) { 156 | var clone = $(fileInputClones[index]); 157 | // Restore the original name and form properties: 158 | $(input) 159 | .prop('name', clone.prop('name')) 160 | .attr('form', clone.attr('form')); 161 | clone.replaceWith(input); 162 | }); 163 | } 164 | }); 165 | form.append(iframe).appendTo(document.body); 166 | }, 167 | abort: function () { 168 | if (iframe) { 169 | // javascript:false as iframe src aborts the request 170 | // and prevents warning popups on HTTPS in IE6. 171 | // concat is used to avoid the "Script URL" JSLint error: 172 | iframe 173 | .unbind('load') 174 | .prop('src', initialIframeSrc); 175 | } 176 | if (form) { 177 | form.remove(); 178 | } 179 | } 180 | }; 181 | } 182 | }); 183 | 184 | // The iframe transport returns the iframe content document as response. 185 | // The following adds converters from iframe to text, json, html, xml 186 | // and script. 187 | // Please note that the Content-Type for JSON responses has to be text/plain 188 | // or text/html, if the browser doesn't include application/json in the 189 | // Accept header, else IE will show a download dialog. 190 | // The Content-Type for XML responses on the other hand has to be always 191 | // application/xml or text/xml, so IE properly parses the XML response. 192 | // See also 193 | // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation 194 | $.ajaxSetup({ 195 | converters: { 196 | 'iframe text': function (iframe) { 197 | return iframe && $(iframe[0].body).text(); 198 | }, 199 | 'iframe json': function (iframe) { 200 | return iframe && $.parseJSON($(iframe[0].body).text()); 201 | }, 202 | 'iframe html': function (iframe) { 203 | return iframe && $(iframe[0].body).html(); 204 | }, 205 | 'iframe xml': function (iframe) { 206 | var xmlDoc = iframe && iframe[0]; 207 | return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : 208 | $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || 209 | $(xmlDoc.body).html()); 210 | }, 211 | 'iframe script': function (iframe) { 212 | return iframe && $.globalEval($(iframe[0].body).text()); 213 | } 214 | } 215 | }); 216 | 217 | })); 218 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/locale.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin Localization Example 6.5.1 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2012, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*global window */ 13 | 14 | window.locale = { 15 | "fileupload": { 16 | "errors": { 17 | "maxFileSize": "File is too big", 18 | "minFileSize": "File is too small", 19 | "acceptFileTypes": "Filetype not allowed", 20 | "maxNumberOfFiles": "Max number of files exceeded", 21 | "uploadedBytes": "Uploaded bytes exceed file size", 22 | "emptyResult": "Empty file upload result" 23 | }, 24 | "error": "Error", 25 | "start": "Start", 26 | "cancel": "Cancel", 27 | "destroy": "Delete" 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/vendor/canvas-to-blob.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript Canvas to Blob 2.0.5 3 | * https://github.com/blueimp/JavaScript-Canvas-to-Blob 4 | * 5 | * Copyright 2012, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | * 11 | * Based on stackoverflow user Stoive's code snippet: 12 | * http://stackoverflow.com/q/4998908 13 | */ 14 | 15 | /*jslint nomen: true, regexp: true */ 16 | /*global window, atob, Blob, ArrayBuffer, Uint8Array, define */ 17 | 18 | (function (window) { 19 | 'use strict'; 20 | var CanvasPrototype = window.HTMLCanvasElement && 21 | window.HTMLCanvasElement.prototype, 22 | hasBlobConstructor = window.Blob && (function () { 23 | try { 24 | return Boolean(new Blob()); 25 | } catch (e) { 26 | return false; 27 | } 28 | }()), 29 | hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array && 30 | (function () { 31 | try { 32 | return new Blob([new Uint8Array(100)]).size === 100; 33 | } catch (e) { 34 | return false; 35 | } 36 | }()), 37 | BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || 38 | window.MozBlobBuilder || window.MSBlobBuilder, 39 | dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob && 40 | window.ArrayBuffer && window.Uint8Array && function (dataURI) { 41 | var byteString, 42 | arrayBuffer, 43 | intArray, 44 | i, 45 | mimeString, 46 | bb; 47 | if (dataURI.split(',')[0].indexOf('base64') >= 0) { 48 | // Convert base64 to raw binary data held in a string: 49 | byteString = atob(dataURI.split(',')[1]); 50 | } else { 51 | // Convert base64/URLEncoded data component to raw binary data: 52 | byteString = decodeURIComponent(dataURI.split(',')[1]); 53 | } 54 | // Write the bytes of the string to an ArrayBuffer: 55 | arrayBuffer = new ArrayBuffer(byteString.length); 56 | intArray = new Uint8Array(arrayBuffer); 57 | for (i = 0; i < byteString.length; i += 1) { 58 | intArray[i] = byteString.charCodeAt(i); 59 | } 60 | // Separate out the mime component: 61 | mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 62 | // Write the ArrayBuffer (or ArrayBufferView) to a blob: 63 | if (hasBlobConstructor) { 64 | return new Blob( 65 | [hasArrayBufferViewSupport ? intArray : arrayBuffer], 66 | {type: mimeString} 67 | ); 68 | } 69 | bb = new BlobBuilder(); 70 | bb.append(arrayBuffer); 71 | return bb.getBlob(mimeString); 72 | }; 73 | if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) { 74 | if (CanvasPrototype.mozGetAsFile) { 75 | CanvasPrototype.toBlob = function (callback, type, quality) { 76 | if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) { 77 | callback(dataURLtoBlob(this.toDataURL(type, quality))); 78 | } else { 79 | callback(this.mozGetAsFile('blob', type)); 80 | } 81 | }; 82 | } else if (CanvasPrototype.toDataURL && dataURLtoBlob) { 83 | CanvasPrototype.toBlob = function (callback, type, quality) { 84 | callback(dataURLtoBlob(this.toDataURL(type, quality))); 85 | }; 86 | } 87 | } 88 | if (typeof define === 'function' && define.amd) { 89 | define(function () { 90 | return dataURLtoBlob; 91 | }); 92 | } else { 93 | window.dataURLtoBlob = dataURLtoBlob; 94 | } 95 | }(window)); 96 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/vendor/jquery.ui.widget.js: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.11.4+CommonJS - 2015-08-28 2 | * http://jqueryui.com 3 | * Includes: widget.js 4 | * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ 5 | 6 | (function( factory ) { 7 | if ( typeof define === "function" && define.amd ) { 8 | 9 | // AMD. Register as an anonymous module. 10 | define([ "jquery" ], factory ); 11 | 12 | } else if ( typeof exports === "object" ) { 13 | 14 | // Node/CommonJS 15 | factory( require( "jquery" ) ); 16 | 17 | } else { 18 | 19 | // Browser globals 20 | factory( jQuery ); 21 | } 22 | }(function( $ ) { 23 | /*! 24 | * jQuery UI Widget 1.11.4 25 | * http://jqueryui.com 26 | * 27 | * Copyright jQuery Foundation and other contributors 28 | * Released under the MIT license. 29 | * http://jquery.org/license 30 | * 31 | * http://api.jqueryui.com/jQuery.widget/ 32 | */ 33 | 34 | 35 | var widget_uuid = 0, 36 | widget_slice = Array.prototype.slice; 37 | 38 | $.cleanData = (function( orig ) { 39 | return function( elems ) { 40 | var events, elem, i; 41 | for ( i = 0; (elem = elems[i]) != null; i++ ) { 42 | try { 43 | 44 | // Only trigger remove when necessary to save time 45 | events = $._data( elem, "events" ); 46 | if ( events && events.remove ) { 47 | $( elem ).triggerHandler( "remove" ); 48 | } 49 | 50 | // http://bugs.jquery.com/ticket/8235 51 | } catch ( e ) {} 52 | } 53 | orig( elems ); 54 | }; 55 | })( $.cleanData ); 56 | 57 | $.widget = function( name, base, prototype ) { 58 | var fullName, existingConstructor, constructor, basePrototype, 59 | // proxiedPrototype allows the provided prototype to remain unmodified 60 | // so that it can be used as a mixin for multiple widgets (#8876) 61 | proxiedPrototype = {}, 62 | namespace = name.split( "." )[ 0 ]; 63 | 64 | name = name.split( "." )[ 1 ]; 65 | fullName = namespace + "-" + name; 66 | 67 | if ( !prototype ) { 68 | prototype = base; 69 | base = $.Widget; 70 | } 71 | 72 | // create selector for plugin 73 | $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { 74 | return !!$.data( elem, fullName ); 75 | }; 76 | 77 | $[ namespace ] = $[ namespace ] || {}; 78 | existingConstructor = $[ namespace ][ name ]; 79 | constructor = $[ namespace ][ name ] = function( options, element ) { 80 | // allow instantiation without "new" keyword 81 | if ( !this._createWidget ) { 82 | return new constructor( options, element ); 83 | } 84 | 85 | // allow instantiation without initializing for simple inheritance 86 | // must use "new" keyword (the code above always passes args) 87 | if ( arguments.length ) { 88 | this._createWidget( options, element ); 89 | } 90 | }; 91 | // extend with the existing constructor to carry over any static properties 92 | $.extend( constructor, existingConstructor, { 93 | version: prototype.version, 94 | // copy the object used to create the prototype in case we need to 95 | // redefine the widget later 96 | _proto: $.extend( {}, prototype ), 97 | // track widgets that inherit from this widget in case this widget is 98 | // redefined after a widget inherits from it 99 | _childConstructors: [] 100 | }); 101 | 102 | basePrototype = new base(); 103 | // we need to make the options hash a property directly on the new instance 104 | // otherwise we'll modify the options hash on the prototype that we're 105 | // inheriting from 106 | basePrototype.options = $.widget.extend( {}, basePrototype.options ); 107 | $.each( prototype, function( prop, value ) { 108 | if ( !$.isFunction( value ) ) { 109 | proxiedPrototype[ prop ] = value; 110 | return; 111 | } 112 | proxiedPrototype[ prop ] = (function() { 113 | var _super = function() { 114 | return base.prototype[ prop ].apply( this, arguments ); 115 | }, 116 | _superApply = function( args ) { 117 | return base.prototype[ prop ].apply( this, args ); 118 | }; 119 | return function() { 120 | var __super = this._super, 121 | __superApply = this._superApply, 122 | returnValue; 123 | 124 | this._super = _super; 125 | this._superApply = _superApply; 126 | 127 | returnValue = value.apply( this, arguments ); 128 | 129 | this._super = __super; 130 | this._superApply = __superApply; 131 | 132 | return returnValue; 133 | }; 134 | })(); 135 | }); 136 | constructor.prototype = $.widget.extend( basePrototype, { 137 | // TODO: remove support for widgetEventPrefix 138 | // always use the name + a colon as the prefix, e.g., draggable:start 139 | // don't prefix for widgets that aren't DOM-based 140 | widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name 141 | }, proxiedPrototype, { 142 | constructor: constructor, 143 | namespace: namespace, 144 | widgetName: name, 145 | widgetFullName: fullName 146 | }); 147 | 148 | // If this widget is being redefined then we need to find all widgets that 149 | // are inheriting from it and redefine all of them so that they inherit from 150 | // the new version of this widget. We're essentially trying to replace one 151 | // level in the prototype chain. 152 | if ( existingConstructor ) { 153 | $.each( existingConstructor._childConstructors, function( i, child ) { 154 | var childPrototype = child.prototype; 155 | 156 | // redefine the child widget using the same prototype that was 157 | // originally used, but inherit from the new version of the base 158 | $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); 159 | }); 160 | // remove the list of existing child constructors from the old constructor 161 | // so the old child constructors can be garbage collected 162 | delete existingConstructor._childConstructors; 163 | } else { 164 | base._childConstructors.push( constructor ); 165 | } 166 | 167 | $.widget.bridge( name, constructor ); 168 | 169 | return constructor; 170 | }; 171 | 172 | $.widget.extend = function( target ) { 173 | var input = widget_slice.call( arguments, 1 ), 174 | inputIndex = 0, 175 | inputLength = input.length, 176 | key, 177 | value; 178 | for ( ; inputIndex < inputLength; inputIndex++ ) { 179 | for ( key in input[ inputIndex ] ) { 180 | value = input[ inputIndex ][ key ]; 181 | if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { 182 | // Clone objects 183 | if ( $.isPlainObject( value ) ) { 184 | target[ key ] = $.isPlainObject( target[ key ] ) ? 185 | $.widget.extend( {}, target[ key ], value ) : 186 | // Don't extend strings, arrays, etc. with objects 187 | $.widget.extend( {}, value ); 188 | // Copy everything else by reference 189 | } else { 190 | target[ key ] = value; 191 | } 192 | } 193 | } 194 | } 195 | return target; 196 | }; 197 | 198 | $.widget.bridge = function( name, object ) { 199 | var fullName = object.prototype.widgetFullName || name; 200 | $.fn[ name ] = function( options ) { 201 | var isMethodCall = typeof options === "string", 202 | args = widget_slice.call( arguments, 1 ), 203 | returnValue = this; 204 | 205 | if ( isMethodCall ) { 206 | this.each(function() { 207 | var methodValue, 208 | instance = $.data( this, fullName ); 209 | if ( options === "instance" ) { 210 | returnValue = instance; 211 | return false; 212 | } 213 | if ( !instance ) { 214 | return $.error( "cannot call methods on " + name + " prior to initialization; " + 215 | "attempted to call method '" + options + "'" ); 216 | } 217 | if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { 218 | return $.error( "no such method '" + options + "' for " + name + " widget instance" ); 219 | } 220 | methodValue = instance[ options ].apply( instance, args ); 221 | if ( methodValue !== instance && methodValue !== undefined ) { 222 | returnValue = methodValue && methodValue.jquery ? 223 | returnValue.pushStack( methodValue.get() ) : 224 | methodValue; 225 | return false; 226 | } 227 | }); 228 | } else { 229 | 230 | // Allow multiple hashes to be passed on init 231 | if ( args.length ) { 232 | options = $.widget.extend.apply( null, [ options ].concat(args) ); 233 | } 234 | 235 | this.each(function() { 236 | var instance = $.data( this, fullName ); 237 | if ( instance ) { 238 | instance.option( options || {} ); 239 | if ( instance._init ) { 240 | instance._init(); 241 | } 242 | } else { 243 | $.data( this, fullName, new object( options, this ) ); 244 | } 245 | }); 246 | } 247 | 248 | return returnValue; 249 | }; 250 | }; 251 | 252 | $.Widget = function( /* options, element */ ) {}; 253 | $.Widget._childConstructors = []; 254 | 255 | $.Widget.prototype = { 256 | widgetName: "widget", 257 | widgetEventPrefix: "", 258 | defaultElement: "
", 259 | options: { 260 | disabled: false, 261 | 262 | // callbacks 263 | create: null 264 | }, 265 | _createWidget: function( options, element ) { 266 | element = $( element || this.defaultElement || this )[ 0 ]; 267 | this.element = $( element ); 268 | this.uuid = widget_uuid++; 269 | this.eventNamespace = "." + this.widgetName + this.uuid; 270 | 271 | this.bindings = $(); 272 | this.hoverable = $(); 273 | this.focusable = $(); 274 | 275 | if ( element !== this ) { 276 | $.data( element, this.widgetFullName, this ); 277 | this._on( true, this.element, { 278 | remove: function( event ) { 279 | if ( event.target === element ) { 280 | this.destroy(); 281 | } 282 | } 283 | }); 284 | this.document = $( element.style ? 285 | // element within the document 286 | element.ownerDocument : 287 | // element is window or document 288 | element.document || element ); 289 | this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); 290 | } 291 | 292 | this.options = $.widget.extend( {}, 293 | this.options, 294 | this._getCreateOptions(), 295 | options ); 296 | 297 | this._create(); 298 | this._trigger( "create", null, this._getCreateEventData() ); 299 | this._init(); 300 | }, 301 | _getCreateOptions: $.noop, 302 | _getCreateEventData: $.noop, 303 | _create: $.noop, 304 | _init: $.noop, 305 | 306 | destroy: function() { 307 | this._destroy(); 308 | // we can probably remove the unbind calls in 2.0 309 | // all event bindings should go through this._on() 310 | this.element 311 | .unbind( this.eventNamespace ) 312 | .removeData( this.widgetFullName ) 313 | // support: jquery <1.6.3 314 | // http://bugs.jquery.com/ticket/9413 315 | .removeData( $.camelCase( this.widgetFullName ) ); 316 | this.widget() 317 | .unbind( this.eventNamespace ) 318 | .removeAttr( "aria-disabled" ) 319 | .removeClass( 320 | this.widgetFullName + "-disabled " + 321 | "ui-state-disabled" ); 322 | 323 | // clean up events and states 324 | this.bindings.unbind( this.eventNamespace ); 325 | this.hoverable.removeClass( "ui-state-hover" ); 326 | this.focusable.removeClass( "ui-state-focus" ); 327 | }, 328 | _destroy: $.noop, 329 | 330 | widget: function() { 331 | return this.element; 332 | }, 333 | 334 | option: function( key, value ) { 335 | var options = key, 336 | parts, 337 | curOption, 338 | i; 339 | 340 | if ( arguments.length === 0 ) { 341 | // don't return a reference to the internal hash 342 | return $.widget.extend( {}, this.options ); 343 | } 344 | 345 | if ( typeof key === "string" ) { 346 | // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } 347 | options = {}; 348 | parts = key.split( "." ); 349 | key = parts.shift(); 350 | if ( parts.length ) { 351 | curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); 352 | for ( i = 0; i < parts.length - 1; i++ ) { 353 | curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; 354 | curOption = curOption[ parts[ i ] ]; 355 | } 356 | key = parts.pop(); 357 | if ( arguments.length === 1 ) { 358 | return curOption[ key ] === undefined ? null : curOption[ key ]; 359 | } 360 | curOption[ key ] = value; 361 | } else { 362 | if ( arguments.length === 1 ) { 363 | return this.options[ key ] === undefined ? null : this.options[ key ]; 364 | } 365 | options[ key ] = value; 366 | } 367 | } 368 | 369 | this._setOptions( options ); 370 | 371 | return this; 372 | }, 373 | _setOptions: function( options ) { 374 | var key; 375 | 376 | for ( key in options ) { 377 | this._setOption( key, options[ key ] ); 378 | } 379 | 380 | return this; 381 | }, 382 | _setOption: function( key, value ) { 383 | this.options[ key ] = value; 384 | 385 | if ( key === "disabled" ) { 386 | this.widget() 387 | .toggleClass( this.widgetFullName + "-disabled", !!value ); 388 | 389 | // If the widget is becoming disabled, then nothing is interactive 390 | if ( value ) { 391 | this.hoverable.removeClass( "ui-state-hover" ); 392 | this.focusable.removeClass( "ui-state-focus" ); 393 | } 394 | } 395 | 396 | return this; 397 | }, 398 | 399 | enable: function() { 400 | return this._setOptions({ disabled: false }); 401 | }, 402 | disable: function() { 403 | return this._setOptions({ disabled: true }); 404 | }, 405 | 406 | _on: function( suppressDisabledCheck, element, handlers ) { 407 | var delegateElement, 408 | instance = this; 409 | 410 | // no suppressDisabledCheck flag, shuffle arguments 411 | if ( typeof suppressDisabledCheck !== "boolean" ) { 412 | handlers = element; 413 | element = suppressDisabledCheck; 414 | suppressDisabledCheck = false; 415 | } 416 | 417 | // no element argument, shuffle and use this.element 418 | if ( !handlers ) { 419 | handlers = element; 420 | element = this.element; 421 | delegateElement = this.widget(); 422 | } else { 423 | element = delegateElement = $( element ); 424 | this.bindings = this.bindings.add( element ); 425 | } 426 | 427 | $.each( handlers, function( event, handler ) { 428 | function handlerProxy() { 429 | // allow widgets to customize the disabled handling 430 | // - disabled as an array instead of boolean 431 | // - disabled class as method for disabling individual parts 432 | if ( !suppressDisabledCheck && 433 | ( instance.options.disabled === true || 434 | $( this ).hasClass( "ui-state-disabled" ) ) ) { 435 | return; 436 | } 437 | return ( typeof handler === "string" ? instance[ handler ] : handler ) 438 | .apply( instance, arguments ); 439 | } 440 | 441 | // copy the guid so direct unbinding works 442 | if ( typeof handler !== "string" ) { 443 | handlerProxy.guid = handler.guid = 444 | handler.guid || handlerProxy.guid || $.guid++; 445 | } 446 | 447 | var match = event.match( /^([\w:-]*)\s*(.*)$/ ), 448 | eventName = match[1] + instance.eventNamespace, 449 | selector = match[2]; 450 | if ( selector ) { 451 | delegateElement.delegate( selector, eventName, handlerProxy ); 452 | } else { 453 | element.bind( eventName, handlerProxy ); 454 | } 455 | }); 456 | }, 457 | 458 | _off: function( element, eventName ) { 459 | eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + 460 | this.eventNamespace; 461 | element.unbind( eventName ).undelegate( eventName ); 462 | 463 | // Clear the stack to avoid memory leaks (#10056) 464 | this.bindings = $( this.bindings.not( element ).get() ); 465 | this.focusable = $( this.focusable.not( element ).get() ); 466 | this.hoverable = $( this.hoverable.not( element ).get() ); 467 | }, 468 | 469 | _delay: function( handler, delay ) { 470 | function handlerProxy() { 471 | return ( typeof handler === "string" ? instance[ handler ] : handler ) 472 | .apply( instance, arguments ); 473 | } 474 | var instance = this; 475 | return setTimeout( handlerProxy, delay || 0 ); 476 | }, 477 | 478 | _hoverable: function( element ) { 479 | this.hoverable = this.hoverable.add( element ); 480 | this._on( element, { 481 | mouseenter: function( event ) { 482 | $( event.currentTarget ).addClass( "ui-state-hover" ); 483 | }, 484 | mouseleave: function( event ) { 485 | $( event.currentTarget ).removeClass( "ui-state-hover" ); 486 | } 487 | }); 488 | }, 489 | 490 | _focusable: function( element ) { 491 | this.focusable = this.focusable.add( element ); 492 | this._on( element, { 493 | focusin: function( event ) { 494 | $( event.currentTarget ).addClass( "ui-state-focus" ); 495 | }, 496 | focusout: function( event ) { 497 | $( event.currentTarget ).removeClass( "ui-state-focus" ); 498 | } 499 | }); 500 | }, 501 | 502 | _trigger: function( type, event, data ) { 503 | var prop, orig, 504 | callback = this.options[ type ]; 505 | 506 | data = data || {}; 507 | event = $.Event( event ); 508 | event.type = ( type === this.widgetEventPrefix ? 509 | type : 510 | this.widgetEventPrefix + type ).toLowerCase(); 511 | // the original event may come from any element 512 | // so we need to reset the target on the new event 513 | event.target = this.element[ 0 ]; 514 | 515 | // copy original event properties over to the new event 516 | orig = event.originalEvent; 517 | if ( orig ) { 518 | for ( prop in orig ) { 519 | if ( !( prop in event ) ) { 520 | event[ prop ] = orig[ prop ]; 521 | } 522 | } 523 | } 524 | 525 | this.element.trigger( event, data ); 526 | return !( $.isFunction( callback ) && 527 | callback.apply( this.element[0], [ event ].concat( data ) ) === false || 528 | event.isDefaultPrevented() ); 529 | } 530 | }; 531 | 532 | $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { 533 | $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { 534 | if ( typeof options === "string" ) { 535 | options = { effect: options }; 536 | } 537 | var hasOptions, 538 | effectName = !options ? 539 | method : 540 | options === true || typeof options === "number" ? 541 | defaultEffect : 542 | options.effect || defaultEffect; 543 | options = options || {}; 544 | if ( typeof options === "number" ) { 545 | options = { duration: options }; 546 | } 547 | hasOptions = !$.isEmptyObject( options ); 548 | options.complete = callback; 549 | if ( options.delay ) { 550 | element.delay( options.delay ); 551 | } 552 | if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { 553 | element[ method ]( options ); 554 | } else if ( effectName !== method && element[ effectName ] ) { 555 | element[ effectName ]( options.duration, options.easing, callback ); 556 | } else { 557 | element.queue(function( next ) { 558 | $( this )[ method ](); 559 | if ( callback ) { 560 | callback.call( element[ 0 ] ); 561 | } 562 | next(); 563 | }); 564 | } 565 | }; 566 | }); 567 | 568 | var widget = $.widget; 569 | 570 | 571 | 572 | })); 573 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/vendor/load-image.all.min.js: -------------------------------------------------------------------------------- 1 | !function(e){"use strict";var t=function(e,i,a){var o,r,n=document.createElement("img");if(n.onerror=i,n.onload=function(){!r||a&&a.noRevoke||t.revokeObjectURL(r),i&&i(t.scale(n,a))},t.isInstanceOf("Blob",e)||t.isInstanceOf("File",e))o=r=t.createObjectURL(e),n._type=e.type;else{if("string"!=typeof e)return!1;o=e,a&&a.crossOrigin&&(n.crossOrigin=a.crossOrigin)}return o?(n.src=o,n):t.readFile(e,function(e){var t=e.target;t&&t.result?n.src=t.result:i&&i(e)})},i=window.createObjectURL&&window||window.URL&&URL.revokeObjectURL&&URL||window.webkitURL&&webkitURL;t.isInstanceOf=function(e,t){return Object.prototype.toString.call(t)==="[object "+e+"]"},t.transformCoordinates=function(){},t.getTransformedOptions=function(e,t){var i,a,o,r,n=t.aspectRatio;if(!n)return t;i={};for(a in t)t.hasOwnProperty(a)&&(i[a]=t[a]);return i.crop=!0,o=e.naturalWidth||e.width,r=e.naturalHeight||e.height,o/r>n?(i.maxWidth=r*n,i.maxHeight=r):(i.maxWidth=o,i.maxHeight=o/n),i},t.renderImageToCanvas=function(e,t,i,a,o,r,n,s,l,d){return e.getContext("2d").drawImage(t,i,a,o,r,n,s,l,d),e},t.hasCanvasOption=function(e){return e.canvas||e.crop||!!e.aspectRatio},t.scale=function(e,i){function a(){var e=Math.max((s||y)/y,(l||v)/v);e>1&&(y*=e,v*=e)}function o(){var e=Math.min((r||y)/y,(n||v)/v);1>e&&(y*=e,v*=e)}i=i||{};var r,n,s,l,d,u,c,g,f,h,m,p=document.createElement("canvas"),S=e.getContext||t.hasCanvasOption(i)&&p.getContext,b=e.naturalWidth||e.width,x=e.naturalHeight||e.height,y=b,v=x;if(S&&(i=t.getTransformedOptions(e,i),c=i.left||0,g=i.top||0,i.sourceWidth?(d=i.sourceWidth,void 0!==i.right&&void 0===i.left&&(c=b-d-i.right)):d=b-c-(i.right||0),i.sourceHeight?(u=i.sourceHeight,void 0!==i.bottom&&void 0===i.top&&(g=x-u-i.bottom)):u=x-g-(i.bottom||0),y=d,v=u),r=i.maxWidth,n=i.maxHeight,s=i.minWidth,l=i.minHeight,S&&r&&n&&i.crop?(y=r,v=n,m=d/u-r/n,0>m?(u=n*d/r,void 0===i.top&&void 0===i.bottom&&(g=(x-u)/2)):m>0&&(d=r*u/n,void 0===i.left&&void 0===i.right&&(c=(b-d)/2))):((i.contain||i.cover)&&(s=r=r||s,l=n=n||l),i.cover?(o(),a()):(a(),o())),S){if(f=i.pixelRatio,f>1&&(p.style.width=y+"px",p.style.height=v+"px",y*=f,v*=f,p.getContext("2d").scale(f,f)),h=i.downsamplingRatio,h>0&&1>h&&d>y&&u>v)for(;d*h>y;)p.width=d*h,p.height=u*h,t.renderImageToCanvas(p,e,c,g,d,u,0,0,p.width,p.height),d=p.width,u=p.height,e=document.createElement("canvas"),e.width=d,e.height=u,t.renderImageToCanvas(e,p,0,0,d,u,0,0,d,u);return p.width=y,p.height=v,t.transformCoordinates(p,i),t.renderImageToCanvas(p,e,c,g,d,u,0,0,y,v)}return e.width=y,e.height=v,e},t.createObjectURL=function(e){return i?i.createObjectURL(e):!1},t.revokeObjectURL=function(e){return i?i.revokeObjectURL(e):!1},t.readFile=function(e,t,i){if(window.FileReader){var a=new FileReader;if(a.onload=a.onerror=t,i=i||"readAsDataURL",a[i])return a[i](e),a}return!1},"function"==typeof define&&define.amd?define(function(){return t}):"object"==typeof module&&module.exports?module.exports=t:e.loadImage=t}(window),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image"],e):e("object"==typeof module&&module.exports?require("./load-image"):window.loadImage)}(function(e){"use strict";var t=e.hasCanvasOption,i=e.transformCoordinates,a=e.getTransformedOptions;e.hasCanvasOption=function(i){return!!i.orientation||t.call(e,i)},e.transformCoordinates=function(t,a){i.call(e,t,a);var o=t.getContext("2d"),r=t.width,n=t.height,s=t.style.width,l=t.style.height,d=a.orientation;if(d&&!(d>8))switch(d>4&&(t.width=n,t.height=r,t.style.width=l,t.style.height=s),d){case 2:o.translate(r,0),o.scale(-1,1);break;case 3:o.translate(r,n),o.rotate(Math.PI);break;case 4:o.translate(0,n),o.scale(1,-1);break;case 5:o.rotate(.5*Math.PI),o.scale(1,-1);break;case 6:o.rotate(.5*Math.PI),o.translate(0,-n);break;case 7:o.rotate(.5*Math.PI),o.translate(r,-n),o.scale(-1,1);break;case 8:o.rotate(-.5*Math.PI),o.translate(-r,0)}},e.getTransformedOptions=function(t,i){var o,r,n=a.call(e,t,i),s=n.orientation;if(!s||s>8||1===s)return n;o={};for(r in n)n.hasOwnProperty(r)&&(o[r]=n[r]);switch(n.orientation){case 2:o.left=n.right,o.right=n.left;break;case 3:o.left=n.right,o.top=n.bottom,o.right=n.left,o.bottom=n.top;break;case 4:o.top=n.bottom,o.bottom=n.top;break;case 5:o.left=n.top,o.top=n.left,o.right=n.bottom,o.bottom=n.right;break;case 6:o.left=n.top,o.top=n.right,o.right=n.bottom,o.bottom=n.left;break;case 7:o.left=n.bottom,o.top=n.right,o.right=n.top,o.bottom=n.left;break;case 8:o.left=n.bottom,o.top=n.left,o.right=n.top,o.bottom=n.right}return n.orientation>4&&(o.maxWidth=n.maxHeight,o.maxHeight=n.maxWidth,o.minWidth=n.minHeight,o.minHeight=n.minWidth,o.sourceWidth=n.sourceHeight,o.sourceHeight=n.sourceWidth),o}}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image"],e):e("object"==typeof module&&module.exports?require("./load-image"):window.loadImage)}(function(e){"use strict";var t=window.Blob&&(Blob.prototype.slice||Blob.prototype.webkitSlice||Blob.prototype.mozSlice);e.blobSlice=t&&function(){var e=this.slice||this.webkitSlice||this.mozSlice;return e.apply(this,arguments)},e.metaDataParsers={jpeg:{65505:[]}},e.parseMetaData=function(t,i,a){a=a||{};var o=this,r=a.maxMetaDataSize||262144,n={},s=!(window.DataView&&t&&t.size>=12&&"image/jpeg"===t.type&&e.blobSlice);(s||!e.readFile(e.blobSlice.call(t,0,r),function(t){if(t.target.error)return console.log(t.target.error),void i(n);var r,s,l,d,u=t.target.result,c=new DataView(u),g=2,f=c.byteLength-4,h=g;if(65496===c.getUint16(0)){for(;f>g&&(r=c.getUint16(g),r>=65504&&65519>=r||65534===r);){if(s=c.getUint16(g+2)+2,g+s>c.byteLength){console.log("Invalid meta data: Invalid segment size.");break}if(l=e.metaDataParsers.jpeg[r])for(d=0;d6&&(u.slice?n.imageHead=u.slice(0,h):n.imageHead=new Uint8Array(u).subarray(0,h))}else console.log("Invalid JPEG file: Missing JPEG marker.");i(n)},"readAsArrayBuffer"))&&i(n)}}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-meta"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-meta")):e(window.loadImage)}(function(e){"use strict";e.ExifMap=function(){return this},e.ExifMap.prototype.map={Orientation:274},e.ExifMap.prototype.get=function(e){return this[e]||this[this.map[e]]},e.getExifThumbnail=function(e,t,i){var a,o,r;if(!i||t+i>e.byteLength)return void console.log("Invalid Exif data: Invalid thumbnail data.");for(a=[],o=0;i>o;o+=1)r=e.getUint8(t+o),a.push((16>r?"0":"")+r.toString(16));return"data:image/jpeg,%"+a.join("%")},e.exifTagTypes={1:{getValue:function(e,t){return e.getUint8(t)},size:1},2:{getValue:function(e,t){return String.fromCharCode(e.getUint8(t))},size:1,ascii:!0},3:{getValue:function(e,t,i){return e.getUint16(t,i)},size:2},4:{getValue:function(e,t,i){return e.getUint32(t,i)},size:4},5:{getValue:function(e,t,i){return e.getUint32(t,i)/e.getUint32(t+4,i)},size:8},9:{getValue:function(e,t,i){return e.getInt32(t,i)},size:4},10:{getValue:function(e,t,i){return e.getInt32(t,i)/e.getInt32(t+4,i)},size:8}},e.exifTagTypes[7]=e.exifTagTypes[1],e.getExifValue=function(t,i,a,o,r,n){var s,l,d,u,c,g,f=e.exifTagTypes[o];if(!f)return void console.log("Invalid Exif data: Invalid tag type.");if(s=f.size*r,l=s>4?i+t.getUint32(a+8,n):a+8,l+s>t.byteLength)return void console.log("Invalid Exif data: Invalid data offset.");if(1===r)return f.getValue(t,l,n);for(d=[],u=0;r>u;u+=1)d[u]=f.getValue(t,l+u*f.size,n);if(f.ascii){for(c="",u=0;ue.byteLength)return void console.log("Invalid Exif data: Invalid directory offset.");if(r=e.getUint16(i,a),n=i+2+12*r,n+4>e.byteLength)return void console.log("Invalid Exif data: Invalid directory size.");for(s=0;r>s;s+=1)this.parseExifTag(e,t,i+2+12*s,a,o);return e.getUint32(n,a)},e.parseExifData=function(t,i,a,o,r){if(!r.disableExif){var n,s,l,d=i+10;if(1165519206===t.getUint32(i+4)){if(d+8>t.byteLength)return void console.log("Invalid Exif data: Invalid segment size.");if(0!==t.getUint16(i+8))return void console.log("Invalid Exif data: Missing byte alignment offset.");switch(t.getUint16(d)){case 18761:n=!0;break;case 19789:n=!1;break;default:return void console.log("Invalid Exif data: Invalid byte alignment marker.")}if(42!==t.getUint16(d+2,n))return void console.log("Invalid Exif data: Missing TIFF marker.");s=t.getUint32(d+4,n),o.exif=new e.ExifMap,s=e.parseExifTags(t,d,d+s,n,o),s&&!r.disableExifThumbnail&&(l={exif:{}},s=e.parseExifTags(t,d,d+s,n,l),l.exif[513]&&(o.exif.Thumbnail=e.getExifThumbnail(t,d+l.exif[513],l.exif[514]))),o.exif[34665]&&!r.disableExifSub&&e.parseExifTags(t,d,d+o.exif[34665],n,o),o.exif[34853]&&!r.disableExifGps&&e.parseExifTags(t,d,d+o.exif[34853],n,o)}}},e.metaDataParsers.jpeg[65505].push(e.parseExifData)}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-exif"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-exif")):e(window.loadImage)}(function(e){"use strict";e.ExifMap.prototype.tags={256:"ImageWidth",257:"ImageHeight",34665:"ExifIFDPointer",34853:"GPSInfoIFDPointer",40965:"InteroperabilityIFDPointer",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright",36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",42240:"Gamma",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",37520:"SubSecTime",37521:"SubSecTimeOriginal",37522:"SubSecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"PhotographicSensitivity",34856:"OECF",34864:"SensitivityType",34865:"StandardOutputSensitivity",34866:"RecommendedExposureIndex",34867:"ISOSpeed",34868:"ISOSpeedLatitudeyyy",34869:"ISOSpeedLatitudezzz",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRatio",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",42016:"ImageUniqueID",42032:"CameraOwnerName",42033:"BodySerialNumber",42034:"LensSpecification",42035:"LensMake",42036:"LensModel",42037:"LensSerialNumber",0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential",31:"GPSHPositioningError"},e.ExifMap.prototype.stringValues={ExposureProgram:{0:"Undefined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},SensingMethod:{1:"Undefined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},SceneType:{1:"Directly photographed"},CustomRendered:{0:"Normal process",1:"Custom process"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},GainControl:{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},SubjectDistanceRange:{0:"Unknown",1:"Macro",2:"Close view",3:"Distant view"},FileSource:{3:"DSC"},ComponentsConfiguration:{0:"",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"},Orientation:{1:"top-left",2:"top-right",3:"bottom-right",4:"bottom-left",5:"left-top",6:"right-top",7:"right-bottom",8:"left-bottom"}},e.ExifMap.prototype.getText=function(e){var t=this.get(e);switch(e){case"LightSource":case"Flash":case"MeteringMode":case"ExposureProgram":case"SensingMethod":case"SceneCaptureType":case"SceneType":case"CustomRendered":case"WhiteBalance":case"GainControl":case"Contrast":case"Saturation":case"Sharpness":case"SubjectDistanceRange":case"FileSource":case"Orientation":return this.stringValues[e][t];case"ExifVersion":case"FlashpixVersion":return String.fromCharCode(t[0],t[1],t[2],t[3]);case"ComponentsConfiguration":return this.stringValues[e][t[0]]+this.stringValues[e][t[1]]+this.stringValues[e][t[2]]+this.stringValues[e][t[3]];case"GPSVersionID":return t[0]+"."+t[1]+"."+t[2]+"."+t[3]}return String(t)},function(e){var t,i=e.tags,a=e.map;for(t in i)i.hasOwnProperty(t)&&(a[i[t]]=t)}(e.ExifMap.prototype),e.ExifMap.prototype.getAll=function(){var e,t,i={};for(e in this)this.hasOwnProperty(e)&&(t=this.tags[e],t&&(i[t]=this.getText(t)));return i}}); 2 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery-fileupload/vendor/tmpl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript Templates 2.4.1 3 | * https://github.com/blueimp/JavaScript-Templates 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | * 11 | * Inspired by John Resig's JavaScript Micro-Templating: 12 | * http://ejohn.org/blog/javascript-micro-templating/ 13 | */ 14 | 15 | /*jslint evil: true, regexp: true, unparam: true */ 16 | /*global document, define */ 17 | 18 | (function ($) { 19 | "use strict"; 20 | var tmpl = function (str, data) { 21 | var f = !/[^\w\-\.:]/.test(str) ? tmpl.cache[str] = tmpl.cache[str] || 22 | tmpl(tmpl.load(str)) : 23 | new Function( 24 | tmpl.arg + ',tmpl', 25 | "var _e=tmpl.encode" + tmpl.helper + ",_s='" + 26 | str.replace(tmpl.regexp, tmpl.func) + 27 | "';return _s;" 28 | ); 29 | return data ? f(data, tmpl) : function (data) { 30 | return f(data, tmpl); 31 | }; 32 | }; 33 | tmpl.cache = {}; 34 | tmpl.load = function (id) { 35 | return document.getElementById(id).innerHTML; 36 | }; 37 | tmpl.regexp = /([\s'\\])(?!(?:[^{]|\{(?!%))*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g; 38 | tmpl.func = function (s, p1, p2, p3, p4, p5) { 39 | if (p1) { // whitespace, quote and backspace in HTML context 40 | return { 41 | "\n": "\\n", 42 | "\r": "\\r", 43 | "\t": "\\t", 44 | " " : " " 45 | }[p1] || "\\" + p1; 46 | } 47 | if (p2) { // interpolation: {%=prop%}, or unescaped: {%#prop%} 48 | if (p2 === "=") { 49 | return "'+_e(" + p3 + ")+'"; 50 | } 51 | return "'+(" + p3 + "==null?'':" + p3 + ")+'"; 52 | } 53 | if (p4) { // evaluation start tag: {% 54 | return "';"; 55 | } 56 | if (p5) { // evaluation end tag: %} 57 | return "_s+='"; 58 | } 59 | }; 60 | tmpl.encReg = /[<>&"'\x00]/g; 61 | tmpl.encMap = { 62 | "<" : "<", 63 | ">" : ">", 64 | "&" : "&", 65 | "\"" : """, 66 | "'" : "'" 67 | }; 68 | tmpl.encode = function (s) { 69 | /*jshint eqnull:true */ 70 | return (s == null ? "" : "" + s).replace( 71 | tmpl.encReg, 72 | function (c) { 73 | return tmpl.encMap[c] || ""; 74 | } 75 | ); 76 | }; 77 | tmpl.arg = "o"; 78 | tmpl.helper = ",print=function(s,e){_s+=e?(s==null?'':s):_e(s);}" + 79 | ",include=function(s,d){_s+=tmpl(s,d);}"; 80 | if (typeof define === "function" && define.amd) { 81 | define(function () { 82 | return tmpl; 83 | }); 84 | } else { 85 | $.tmpl = tmpl; 86 | } 87 | }(this)); 88 | -------------------------------------------------------------------------------- /app/assets/stylesheets/jquery.fileupload-noscript.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin NoScript CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button input { 14 | position: static; 15 | opacity: 1; 16 | filter: none; 17 | font-size: inherit; 18 | direction: inherit; 19 | } 20 | .fileinput-button span { 21 | display: none; 22 | } 23 | -------------------------------------------------------------------------------- /app/assets/stylesheets/jquery.fileupload-ui-noscript.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload UI Plugin NoScript CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2012, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button i, 14 | .fileupload-buttonbar .delete, 15 | .fileupload-buttonbar .toggle { 16 | display: none; 17 | } 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/jquery.fileupload-ui.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload UI Plugin CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2010, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileupload-buttonbar .btn, 14 | .fileupload-buttonbar .toggle { 15 | margin-bottom: 5px; 16 | } 17 | .progress-animated .progress-bar, 18 | .progress-animated .bar { 19 | background: image-url("progressbar.gif") !important; 20 | filter: none; 21 | } 22 | .fileupload-process { 23 | float: right; 24 | display: none; 25 | } 26 | .fileupload-processing .fileupload-process, 27 | .files .processing .preview { 28 | display: block; 29 | width: 32px; 30 | height: 32px; 31 | background: image-url("loading.gif") center no-repeat; 32 | background-size: contain; 33 | } 34 | .files audio, 35 | .files video { 36 | max-width: 300px; 37 | } 38 | 39 | @media (max-width: 767px) { 40 | .fileupload-buttonbar .toggle, 41 | .files .toggle, 42 | .files .btn span { 43 | display: none; 44 | } 45 | .files .name { 46 | width: 80px; 47 | word-wrap: break-word; 48 | } 49 | .files audio, 50 | .files video { 51 | max-width: 80px; 52 | } 53 | .files img, 54 | .files canvas { 55 | max-width: 100%; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/assets/stylesheets/jquery.fileupload.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button { 14 | position: relative; 15 | overflow: hidden; 16 | display: inline-block; 17 | } 18 | .fileinput-button input { 19 | position: absolute; 20 | top: 0; 21 | right: 0; 22 | margin: 0; 23 | opacity: 0; 24 | -ms-filter: 'alpha(opacity=0)'; 25 | font-size: 200px !important; 26 | direction: ltr; 27 | cursor: pointer; 28 | } 29 | 30 | /* Fixes for IE < 8 */ 31 | @media screen\9 { 32 | .fileinput-button input { 33 | filter: alpha(opacity=0); 34 | font-size: 100%; 35 | height: 100%; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jquery-fileupload-rails.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "jquery/fileupload/rails/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "jquery-fileupload-rails" 7 | s.version = JQuery::FileUpload::Rails::VERSION 8 | s.authors = ["Tors Dalid"] 9 | s.email = ["cletedalid@gmail.com"] 10 | s.homepage = "https://github.com/tors/jquery-fileupload-rails" 11 | s.summary = %q{jQuery File Upload for Rails 3.1+ Asset Pipeline} 12 | s.description = %q{jQuery File Upload by Sebastian Tschan integrated for Rails 3.1+ Asset Pipeline} 13 | 14 | s.rubyforge_project = "jquery-fileupload-rails" 15 | 16 | s.files = Dir["lib/**/*"] + Dir["app/**/*"] + ["Rakefile", "README.md"] 17 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 19 | s.require_paths = ["lib"] 20 | 21 | s.add_dependency 'railties', '>= 3.1' 22 | s.add_dependency 'actionpack', '>= 3.1' 23 | s.add_development_dependency 'rails', '>= 3.1' 24 | s.add_dependency 'sassc' 25 | end 26 | -------------------------------------------------------------------------------- /lib/jquery-fileupload-rails.rb: -------------------------------------------------------------------------------- 1 | module JQuery 2 | module FileUpload 3 | module Rails 4 | require 'jquery/fileupload/rails/engine' if defined?(Rails) 5 | end 6 | end 7 | end 8 | require 'jquery/fileupload/rails/upload' if defined?(Rails) 9 | -------------------------------------------------------------------------------- /lib/jquery/fileupload/rails/engine.rb: -------------------------------------------------------------------------------- 1 | module JQuery 2 | module FileUpload 3 | module Rails 4 | class Engine < ::Rails::Engine 5 | end 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/jquery/fileupload/rails/middleware.rb: -------------------------------------------------------------------------------- 1 | module JQuery 2 | module FileUpload 3 | module Rails 4 | class Middleware 5 | def initialize(app) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | dup._call(env) 11 | end 12 | 13 | def _call(env) 14 | @status, @headers, @response = @app.call(env) 15 | @request = Rack::Request.new(env) 16 | 17 | if iframe_transport? 18 | @headers['Content-Type'] = 'text/html' 19 | [@status, @headers, self] 20 | else 21 | [@status, @headers, @response] 22 | end 23 | end 24 | 25 | def each(&block) 26 | block.call(html_document_left) if iframe_transport? 27 | @response.each(&block) 28 | block.call(html_document_right) if iframe_transport? 29 | end 30 | 31 | def iframe_transport? 32 | @request.params['X-Requested-With'] == 'IFrame' 33 | end 34 | 35 | def html_document_left 36 | "" 41 | end 42 | 43 | def metadata 44 | meta = {} 45 | meta['data-status'] = @response.status if @response.respond_to? :status 46 | meta['data-statusText'] = @response.status_message if @response.respond_to? :status_message 47 | meta['data-type'] = @headers['Content-Type'] if @headers.has_key?('Content-Type') 48 | meta.map {|key,value| "#{key}='#{value}'" }.join(' ') 49 | end 50 | 51 | private 52 | 53 | def method_missing(method, *args) 54 | @response.send(method.intern, *args) 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/jquery/fileupload/rails/upload.rb: -------------------------------------------------------------------------------- 1 | require "jquery/fileupload/rails/engine" 2 | require "jquery/fileupload/rails/version" 3 | require "jquery/fileupload/rails/middleware" 4 | -------------------------------------------------------------------------------- /lib/jquery/fileupload/rails/version.rb: -------------------------------------------------------------------------------- 1 | module JQuery 2 | module FileUpload 3 | module Rails 4 | VERSION = "1.0.0" 5 | end 6 | end 7 | end 8 | --------------------------------------------------------------------------------