├── .gitignore ├── .npmignore ├── README.md ├── app.js ├── config.js ├── cors.xml ├── package.json ├── public ├── images │ ├── bg.png │ └── circle.png ├── javascripts │ ├── app.js │ ├── foundation.min.js │ ├── jquery.fileupload.js │ ├── jquery.js │ ├── jquery.lettering.js │ ├── jquery.ui.widget.js │ ├── less-1.3.3.min.js │ └── modernizr.foundation.js └── stylesheets │ ├── app.css │ ├── app.less │ ├── font │ ├── BPdotsUnicaseLight-webfont.eot │ ├── BPdotsUnicaseLight-webfont.svg │ ├── BPdotsUnicaseLight-webfont.ttf │ ├── BPdotsUnicaseLight-webfont.woff │ └── font.css │ ├── foundation.css │ └── foundation.min.css ├── routes └── index.js └── views └── index.hjs /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.tmp 10 | *.swp 11 | *.swo 12 | pids 13 | logs 14 | results 15 | 16 | npm-debug.log 17 | node_modules 18 | .idea 19 | 20 | # Compiled CSS. 21 | public/stylesheets/app.css 22 | 23 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.tmp 10 | *.swp 11 | *.swo 12 | config.js 13 | pids 14 | logs 15 | results 16 | 17 | npm-debug.log 18 | node_modules 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dropdot # 2 | 3 | Dropdot is a simple file uploader built on NodeJS, Express and S3 API as the object storage platform. 4 | It supports AWS S3 and DreamHost Objects as storage provider. 5 | 6 | Configuring Dropdot is simple, just follow the steps below: 7 | 8 | **AWS S3 Demo**: http://dropdot.herokuapp.com/ 9 | 10 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 11 | 12 | ## Installation 13 | Clone and install dependencies 14 | 15 | ```bash 16 | $ git clone https://github.com/alfg/dropdot.git 17 | $ cd dropdot 18 | $ npm install 19 | ``` 20 | 21 | Open `config.js` and configure 22 | 23 | ```javascript 24 | module.exports.port = 3000; // App port 25 | module.exports.aws_key = "YourAWSKey"; // AWS Key 26 | module.exports.aws_secret = "YourSuperSecretAWSKey"; // AWS Secret 27 | module.exports.aws_bucket = "NameOfS3Bucket"; // S3 bucket 28 | module.exports.redirect_host = "http://localhost:3000/"; // Redirect page after successful upload 29 | module.exports.host = "YOUR_S3_PROVIDER"; // S3 provider host 30 | module.exports.bucket_dir = "uploads/"; // Subdirectory in S3 bucket where uploads will go 31 | module.exports.max_filesize = 20971520; // Max filesize in bytes (default 20MB) 32 | ``` 33 | 34 | Or set your environment variables: 35 | ```bash 36 | export PORT=3000 37 | export AWS_KEY= 38 | export AWS_SECRET= 39 | export AWS_BUCKET= 40 | export REDIRECT_HOST=http://localhost:3000/ 41 | export HOST= 42 | export BUCKET_DIR=uploads 43 | ``` 44 | 45 | Run the app 46 | ```bash 47 | $ node app.js 48 | ``` 49 | 50 | Load `http://localhost:3000` into the browser 51 | 52 | The app is set, now you need to create and configure your S3 Bucket. 53 | 54 | ## Configuring your S3 Bucket 55 | In order to allow S3 to accept CORS uploads from your app, it needs to be properly configured. 56 | 57 | ## AWS S3 58 | Log into your S3 Console and create a bucket. 59 | 60 | Right-click your bucket and go to properties > permissions > **Edit CORS Configuration**. 61 | 62 | Enter the following: 63 | 64 | ```xml 65 | 66 | 67 | * 68 | PUT 69 | GET 70 | POST 71 | 3000 72 | * 73 | 74 | 75 | ``` 76 | Of course, the * in AllowedOrigin is only for development. Be sure to use your domain when going public. 77 | 78 | ## DreamObjects S3 79 | See: https://help.dreamhost.com/hc/en-us/articles/216201557-How-to-setup-Cross-Origin-Resource-Sharing-CORS-on-DreamObjects 80 | 81 | 82 | ## CNAME your S3 bucket (optional) 83 | 84 | Objects will be available by accessing the public URL directly. Example: 85 | `http://bucket-name.s3.amazonaws.com/uploads/439fbca8-b79b-40e9-8172-4d318737ee14_file.jpg` 86 | 87 | However, if you wish to customize the URL, you can use a CNAME. First, create/rename your bucket to match the 88 | subdomain (`uploads.domain.com`). Then create the CNAME (via your DNS settings) using the exact name of the 89 | bucket/subdomain. 90 | 91 | To be clear, the bucket's name and CNAME should be identical. i.e. `uploads.domain.com` 92 | 93 | You can now access your files as: 94 | `http://uploads.domain.com/uploads/439fbca8-b79b-40e9-8172-4d318737ee14_file.jpg` 95 | 96 | ## That's it! 97 | All done! Now go upload stuff. 98 | 99 | ## License 100 | Dropdot is open-source under the [MIT License][1]. 101 | 102 | ## Credits 103 | Dropdot uses the following technologies, check them out! 104 | * [NodeJS][2] The core backend 105 | * [Express][3] Framework for Node. Serves the JSON policy 106 | * [mime][4] Mime-type detection 107 | * [crypto][5] Used for SHA1, Base64 digestion 108 | * [uuid][6] Generating the unique IDs 109 | * [Amazon S3][8] Cloud Object/File Storage Platform 110 | 111 | Guides that saved me a few gray hairs dealing with CORS: 112 | 113 | * http://pjambet.github.com/blog/direct-upload-to-s3/ 114 | * http://aws.amazon.com/articles/1434 115 | 116 | [1]: http://opensource.org/licenses/MIT 117 | [2]: http://nodejs.org 118 | [3]: http://expressjs.com/ 119 | [4]: https://github.com/broofa/node-mime 120 | [5]: http://nodejs.org/api/crypto.html 121 | [6]: https://github.com/broofa/node-uuid 122 | [7]: http://foundation.zurb.com 123 | [8]: http://aws.amazon.com/s3/ 124 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | routes = require('./routes'), 3 | http = require('http'), 4 | cors = require('cors'), 5 | path = require('path'), 6 | logger = require('morgan'), 7 | config = require('./config'); 8 | 9 | var app = express(); 10 | 11 | app.set('port', process.env.PORT || config.port); 12 | app.set('views', __dirname + '/views'); 13 | app.set('view engine', 'hjs'); 14 | app.use(logger('dev')); 15 | app.use(cors()); 16 | app.use(require('less-middleware')(path.join(__dirname + '/public'))); 17 | app.use(express.static(path.join(__dirname, 'public'))); 18 | 19 | app.get('/', routes.index); 20 | app.get('/signed', routes.signed); 21 | 22 | http.createServer(app).listen(app.get('port'), function(){ 23 | console.log("Express server listening on port " + app.get('port')); 24 | }); 25 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | // All configuration options 2 | 3 | module.exports.port = process.env.PORT || 3000; // App's port 4 | module.exports.aws_key = process.env.AWS_KEY || ""; // AWS Key 5 | module.exports.aws_secret = process.env.AWS_SECRET || ""; // AWS Secret 6 | module.exports.aws_bucket = process.env.AWS_BUCKET || ""; // AWS Bucket 7 | module.exports.redirect_host = process.env.REDIRECT_HOST || "http://localhost:3000/"; // Host to redirect after uploading 8 | module.exports.host = process.env.HOST || "s3.amazonaws.com"; // S3 provider host 9 | module.exports.bucket_dir = process.env.BUCKET_DIR || "uploads/"; 10 | module.exports.max_filesize = 20971520; // Max filesize in bytes (default 20MB) 11 | -------------------------------------------------------------------------------- /cors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | * 4 | PUT 5 | 3000 6 | Content-Type 7 | x-amz-acl 8 | origin 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dropdot", 3 | "description": "Simple file uploading application", 4 | "version": "0.1.0", 5 | "author": "Alf ", 6 | "scripts": { 7 | "start": "node app.js" 8 | }, 9 | "engines": { 10 | "node": ">=6.11.1" 11 | }, 12 | "dependencies": { 13 | "cors": "^2.8.4", 14 | "express": "^4.16.3", 15 | "hjs": "~0.0.6", 16 | "less-middleware": "^3.0.1", 17 | "mime": "^1.6.0", 18 | "moment": "^2.22.2", 19 | "morgan": "^1.9.0", 20 | "mustache": "^2.3.0", 21 | "uuid": "^3.3.2" 22 | }, 23 | "keywords": [ 24 | "uploader", 25 | "app" 26 | ], 27 | "repository": "git://github.com/alfg/dropdot" 28 | } 29 | -------------------------------------------------------------------------------- /public/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/dropdot/558c13ad61cfd71e618dcb289797692762be3499/public/images/bg.png -------------------------------------------------------------------------------- /public/images/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/dropdot/558c13ad61cfd71e618dcb289797692762be3499/public/images/circle.png -------------------------------------------------------------------------------- /public/javascripts/app.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | $('.direct-upload').each(function() { 4 | /* For each file selected, process and upload */ 5 | 6 | var form = $(this) 7 | 8 | $(this).fileupload({ 9 | url: form.attr('action'), // Grabs form's action src 10 | type: 'POST', 11 | autoUpload: true, 12 | dataType: 'xml', // S3's XML response 13 | add: function (event, data) { 14 | $.ajax({ 15 | url: "/signed", 16 | type: 'GET', 17 | dataType: 'json', 18 | data: {title: data.files[0].name}, // Send filename to /signed for the signed response 19 | async: false, 20 | success: function(data) { 21 | // Now that we have our data, we update the form so it contains all 22 | // the needed data to sign the request 23 | form.find('input[name=key]').val(data.key); 24 | form.find('input[name=policy]').val(data.policy); 25 | form.find('input[name=signature]').val(data.signature); 26 | form.find('input[name=Content-Type]').val(data.contentType); 27 | } 28 | }) 29 | data.submit(); 30 | }, 31 | send: function(e, data) { 32 | $('.progress').fadeIn(); // Display widget progress bar 33 | }, 34 | progress: function(e, data){ 35 | $('#circle').addClass('animate'); // Animate the rotating circle when in progress 36 | var percent = Math.round((e.loaded / e.total) * 100) 37 | $('.meter').css('width', percent + '%') // Update progress bar percentage 38 | }, 39 | fail: function(e, data) { 40 | console.log('fail', e); 41 | $('#circle').removeClass('animate'); 42 | }, 43 | success: function(data) { 44 | var url = $(data).find('Location').text(); // Find location value from XML response 45 | $('.share-url').show(); // Show input 46 | $('.share-url').val(url.replace("%2F", "/")); // Update the input with url address 47 | }, 48 | done: function (event, data) { 49 | // When upload is done, fade out progress bar and reset to 0 50 | $('.progress').fadeOut(300, function() { 51 | $('.bar').css('width', 0) 52 | }) 53 | 54 | // Stop circle animation 55 | $('#circle').removeClass('animate'); 56 | }, 57 | }) 58 | }) 59 | 60 | /* Dragover Events on circle */ 61 | var dragging = 0; //Get around chrome bug 62 | $('#drop').on("dragenter", function(e){ 63 | dragging++; 64 | $('#drop').addClass("gloss"); 65 | e.preventDefault(); 66 | return false; 67 | }); 68 | 69 | $('#drop').on("dragover", function(e){ 70 | $('#drop').addClass("gloss"); 71 | e.preventDefault(); 72 | return false; 73 | }); 74 | 75 | $('#drop').on("dragleave", function(e){ 76 | dragging--; 77 | if (dragging === 0) { 78 | $('#drop').removeClass("gloss"); 79 | } 80 | e.preventDefault(); 81 | return false; 82 | }); 83 | $('.footer-link').click( function(){ 84 | $('.footer-text').hide(); 85 | $($(this).attr('href')).fadeIn('fast'); 86 | }); 87 | $(".share-url").focus(function () { 88 | // Select all text on #share-url focus 89 | 90 | "use strict"; 91 | var $this = $(this); 92 | $this.select(); 93 | 94 | // Work around Chrome's little problem 95 | $this.mouseup(function () { 96 | // Prevent further mouseup intervention 97 | $this.unbind("mouseup"); 98 | return false; 99 | }); 100 | }); 101 | 102 | }) 103 | 104 | -------------------------------------------------------------------------------- /public/javascripts/jquery.fileupload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin 5.21.1 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 | /*jslint nomen: true, unparam: true, regexp: true */ 13 | /*global define, window, document, File, Blob, FormData, location */ 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.ui.widget' 22 | ], factory); 23 | } else { 24 | // Browser globals: 25 | factory(window.jQuery); 26 | } 27 | }(function ($) { 28 | 'use strict'; 29 | 30 | // The FileReader API is not actually used, but works as feature detection, 31 | // as e.g. Safari supports XHR file uploads via the FormData API, 32 | // but not non-multipart XHR file uploads: 33 | $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader); 34 | $.support.xhrFormDataFileUpload = !!window.FormData; 35 | 36 | // The form.elements propHook is added to filter serialized elements 37 | // to not include file inputs in jQuery 1.9.0. 38 | // This hooks directly into jQuery.fn.serializeArray. 39 | // For more info, see http://bugs.jquery.com/ticket/13306 40 | $.propHooks.elements = { 41 | get: function (form) { 42 | if ($.nodeName(form, 'form')) { 43 | return $.grep(form.elements, function (elem) { 44 | return !$.nodeName(elem, 'input') || elem.type !== 'file'; 45 | }); 46 | } 47 | return null; 48 | } 49 | }; 50 | 51 | // The fileupload widget listens for change events on file input fields defined 52 | // via fileInput setting and paste or drop events of the given dropZone. 53 | // In addition to the default jQuery Widget methods, the fileupload widget 54 | // exposes the "add" and "send" methods, to add or directly send files using 55 | // the fileupload API. 56 | // By default, files added via file input selection, paste, drag & drop or 57 | // "add" method are uploaded immediately, but it is possible to override 58 | // the "add" callback option to queue file uploads. 59 | $.widget('blueimp.fileupload', { 60 | 61 | options: { 62 | // The drop target element(s), by the default the complete document. 63 | // Set to null to disable drag & drop support: 64 | dropZone: $(document), 65 | // The paste target element(s), by the default the complete document. 66 | // Set to null to disable paste support: 67 | pasteZone: $(document), 68 | // The file input field(s), that are listened to for change events. 69 | // If undefined, it is set to the file input fields inside 70 | // of the widget element on plugin initialization. 71 | // Set to null to disable the change listener. 72 | fileInput: undefined, 73 | // By default, the file input field is replaced with a clone after 74 | // each input field change event. This is required for iframe transport 75 | // queues and allows change events to be fired for the same file 76 | // selection, but can be disabled by setting the following option to false: 77 | replaceFileInput: true, 78 | // The parameter name for the file form data (the request argument name). 79 | // If undefined or empty, the name property of the file input field is 80 | // used, or "files[]" if the file input name property is also empty, 81 | // can be a string or an array of strings: 82 | paramName: undefined, 83 | // By default, each file of a selection is uploaded using an individual 84 | // request for XHR type uploads. Set to false to upload file 85 | // selections in one request each: 86 | singleFileUploads: true, 87 | // To limit the number of files uploaded with one XHR request, 88 | // set the following option to an integer greater than 0: 89 | limitMultiFileUploads: undefined, 90 | // Set the following option to true to issue all file upload requests 91 | // in a sequential order: 92 | sequentialUploads: false, 93 | // To limit the number of concurrent uploads, 94 | // set the following option to an integer greater than 0: 95 | limitConcurrentUploads: undefined, 96 | // Set the following option to true to force iframe transport uploads: 97 | forceIframeTransport: false, 98 | // Set the following option to the location of a redirect url on the 99 | // origin server, for cross-domain iframe transport uploads: 100 | redirect: undefined, 101 | // The parameter name for the redirect url, sent as part of the form 102 | // data and set to 'redirect' if this option is empty: 103 | redirectParamName: undefined, 104 | // Set the following option to the location of a postMessage window, 105 | // to enable postMessage transport uploads: 106 | postMessage: undefined, 107 | // By default, XHR file uploads are sent as multipart/form-data. 108 | // The iframe transport is always using multipart/form-data. 109 | // Set to false to enable non-multipart XHR uploads: 110 | multipart: true, 111 | // To upload large files in smaller chunks, set the following option 112 | // to a preferred maximum chunk size. If set to 0, null or undefined, 113 | // or the browser does not support the required Blob API, files will 114 | // be uploaded as a whole. 115 | maxChunkSize: undefined, 116 | // When a non-multipart upload or a chunked multipart upload has been 117 | // aborted, this option can be used to resume the upload by setting 118 | // it to the size of the already uploaded bytes. This option is most 119 | // useful when modifying the options object inside of the "add" or 120 | // "send" callbacks, as the options are cloned for each file upload. 121 | uploadedBytes: undefined, 122 | // By default, failed (abort or error) file uploads are removed from the 123 | // global progress calculation. Set the following option to false to 124 | // prevent recalculating the global progress data: 125 | recalculateProgress: true, 126 | // Interval in milliseconds to calculate and trigger progress events: 127 | progressInterval: 100, 128 | // Interval in milliseconds to calculate progress bitrate: 129 | bitrateInterval: 500, 130 | 131 | // Additional form data to be sent along with the file uploads can be set 132 | // using this option, which accepts an array of objects with name and 133 | // value properties, a function returning such an array, a FormData 134 | // object (for XHR file uploads), or a simple object. 135 | // The form of the first fileInput is given as parameter to the function: 136 | formData: function (form) { 137 | return form.serializeArray(); 138 | }, 139 | 140 | // The add callback is invoked as soon as files are added to the fileupload 141 | // widget (via file input selection, drag & drop, paste or add API call). 142 | // If the singleFileUploads option is enabled, this callback will be 143 | // called once for each file in the selection for XHR file uplaods, else 144 | // once for each file selection. 145 | // The upload starts when the submit method is invoked on the data parameter. 146 | // The data object contains a files property holding the added files 147 | // and allows to override plugin options as well as define ajax settings. 148 | // Listeners for this callback can also be bound the following way: 149 | // .bind('fileuploadadd', func); 150 | // data.submit() returns a Promise object and allows to attach additional 151 | // handlers using jQuery's Deferred callbacks: 152 | // data.submit().done(func).fail(func).always(func); 153 | add: function (e, data) { 154 | data.submit(); 155 | }, 156 | 157 | // Other callbacks: 158 | 159 | // Callback for the submit event of each file upload: 160 | // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); 161 | 162 | // Callback for the start of each file upload request: 163 | // send: function (e, data) {}, // .bind('fileuploadsend', func); 164 | 165 | // Callback for successful uploads: 166 | // done: function (e, data) {}, // .bind('fileuploaddone', func); 167 | 168 | // Callback for failed (abort or error) uploads: 169 | // fail: function (e, data) {}, // .bind('fileuploadfail', func); 170 | 171 | // Callback for completed (success, abort or error) requests: 172 | // always: function (e, data) {}, // .bind('fileuploadalways', func); 173 | 174 | // Callback for upload progress events: 175 | // progress: function (e, data) {}, // .bind('fileuploadprogress', func); 176 | 177 | // Callback for global upload progress events: 178 | // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); 179 | 180 | // Callback for uploads start, equivalent to the global ajaxStart event: 181 | // start: function (e) {}, // .bind('fileuploadstart', func); 182 | 183 | // Callback for uploads stop, equivalent to the global ajaxStop event: 184 | // stop: function (e) {}, // .bind('fileuploadstop', func); 185 | 186 | // Callback for change events of the fileInput(s): 187 | // change: function (e, data) {}, // .bind('fileuploadchange', func); 188 | 189 | // Callback for paste events to the pasteZone(s): 190 | // paste: function (e, data) {}, // .bind('fileuploadpaste', func); 191 | 192 | // Callback for drop events of the dropZone(s): 193 | // drop: function (e, data) {}, // .bind('fileuploaddrop', func); 194 | 195 | // Callback for dragover events of the dropZone(s): 196 | // dragover: function (e) {}, // .bind('fileuploaddragover', func); 197 | 198 | // Callback for the start of each chunk upload request: 199 | // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func); 200 | 201 | // Callback for successful chunk uploads: 202 | // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func); 203 | 204 | // Callback for failed (abort or error) chunk uploads: 205 | // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func); 206 | 207 | // Callback for completed (success, abort or error) chunk upload requests: 208 | // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func); 209 | 210 | // The plugin options are used as settings object for the ajax calls. 211 | // The following are jQuery ajax settings required for the file uploads: 212 | processData: false, 213 | contentType: false, 214 | cache: false 215 | }, 216 | 217 | // A list of options that require a refresh after assigning a new value: 218 | _refreshOptionsList: [ 219 | 'fileInput', 220 | 'dropZone', 221 | 'pasteZone', 222 | 'multipart', 223 | 'forceIframeTransport' 224 | ], 225 | 226 | _BitrateTimer: function () { 227 | this.timestamp = +(new Date()); 228 | this.loaded = 0; 229 | this.bitrate = 0; 230 | this.getBitrate = function (now, loaded, interval) { 231 | var timeDiff = now - this.timestamp; 232 | if (!this.bitrate || !interval || timeDiff > interval) { 233 | this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; 234 | this.loaded = loaded; 235 | this.timestamp = now; 236 | } 237 | return this.bitrate; 238 | }; 239 | }, 240 | 241 | _isXHRUpload: function (options) { 242 | return !options.forceIframeTransport && 243 | ((!options.multipart && $.support.xhrFileUpload) || 244 | $.support.xhrFormDataFileUpload); 245 | }, 246 | 247 | _getFormData: function (options) { 248 | var formData; 249 | if (typeof options.formData === 'function') { 250 | return options.formData(options.form); 251 | } 252 | if ($.isArray(options.formData)) { 253 | return options.formData; 254 | } 255 | if (options.formData) { 256 | formData = []; 257 | $.each(options.formData, function (name, value) { 258 | formData.push({name: name, value: value}); 259 | }); 260 | return formData; 261 | } 262 | return []; 263 | }, 264 | 265 | _getTotal: function (files) { 266 | var total = 0; 267 | $.each(files, function (index, file) { 268 | total += file.size || 1; 269 | }); 270 | return total; 271 | }, 272 | 273 | _onProgress: function (e, data) { 274 | if (e.lengthComputable) { 275 | var now = +(new Date()), 276 | total, 277 | loaded; 278 | if (data._time && data.progressInterval && 279 | (now - data._time < data.progressInterval) && 280 | e.loaded !== e.total) { 281 | return; 282 | } 283 | data._time = now; 284 | total = data.total || this._getTotal(data.files); 285 | loaded = parseInt( 286 | e.loaded / e.total * (data.chunkSize || total), 287 | 10 288 | ) + (data.uploadedBytes || 0); 289 | this._loaded += loaded - (data.loaded || data.uploadedBytes || 0); 290 | data.lengthComputable = true; 291 | data.loaded = loaded; 292 | data.total = total; 293 | data.bitrate = data._bitrateTimer.getBitrate( 294 | now, 295 | loaded, 296 | data.bitrateInterval 297 | ); 298 | // Trigger a custom progress event with a total data property set 299 | // to the file size(s) of the current upload and a loaded data 300 | // property calculated accordingly: 301 | this._trigger('progress', e, data); 302 | // Trigger a global progress event for all current file uploads, 303 | // including ajax calls queued for sequential file uploads: 304 | this._trigger('progressall', e, { 305 | lengthComputable: true, 306 | loaded: this._loaded, 307 | total: this._total, 308 | bitrate: this._bitrateTimer.getBitrate( 309 | now, 310 | this._loaded, 311 | data.bitrateInterval 312 | ) 313 | }); 314 | } 315 | }, 316 | 317 | _initProgressListener: function (options) { 318 | var that = this, 319 | xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); 320 | // Accesss to the native XHR object is required to add event listeners 321 | // for the upload progress event: 322 | if (xhr.upload) { 323 | $(xhr.upload).bind('progress', function (e) { 324 | var oe = e.originalEvent; 325 | // Make sure the progress event properties get copied over: 326 | e.lengthComputable = oe.lengthComputable; 327 | e.loaded = oe.loaded; 328 | e.total = oe.total; 329 | that._onProgress(e, options); 330 | }); 331 | options.xhr = function () { 332 | return xhr; 333 | }; 334 | } 335 | }, 336 | 337 | _initXHRData: function (options) { 338 | var formData, 339 | file = options.files[0], 340 | // Ignore non-multipart setting if not supported: 341 | multipart = options.multipart || !$.support.xhrFileUpload, 342 | paramName = options.paramName[0]; 343 | options.headers = options.headers || {}; 344 | if (options.contentRange) { 345 | options.headers['Content-Range'] = options.contentRange; 346 | } 347 | if (!multipart) { 348 | options.headers['Content-Disposition'] = 'attachment; filename="' + 349 | encodeURI(file.name) + '"'; 350 | options.contentType = file.type; 351 | options.data = options.blob || file; 352 | } else if ($.support.xhrFormDataFileUpload) { 353 | if (options.postMessage) { 354 | // window.postMessage does not allow sending FormData 355 | // objects, so we just add the File/Blob objects to 356 | // the formData array and let the postMessage window 357 | // create the FormData object out of this array: 358 | formData = this._getFormData(options); 359 | if (options.blob) { 360 | formData.push({ 361 | name: paramName, 362 | value: options.blob 363 | }); 364 | } else { 365 | $.each(options.files, function (index, file) { 366 | formData.push({ 367 | name: options.paramName[index] || paramName, 368 | value: file 369 | }); 370 | }); 371 | } 372 | } else { 373 | if (options.formData instanceof FormData) { 374 | formData = options.formData; 375 | } else { 376 | formData = new FormData(); 377 | $.each(this._getFormData(options), function (index, field) { 378 | formData.append(field.name, field.value); 379 | }); 380 | } 381 | if (options.blob) { 382 | options.headers['Content-Disposition'] = 'attachment; filename="' + 383 | encodeURI(file.name) + '"'; 384 | formData.append(paramName, options.blob, file.name); 385 | } else { 386 | $.each(options.files, function (index, file) { 387 | // Files are also Blob instances, but some browsers 388 | // (Firefox 3.6) support the File API but not Blobs. 389 | // This check allows the tests to run with 390 | // dummy objects: 391 | if ((window.Blob && file instanceof Blob) || 392 | (window.File && file instanceof File)) { 393 | formData.append( 394 | options.paramName[index] || paramName, 395 | file, 396 | file.name 397 | ); 398 | } 399 | }); 400 | } 401 | } 402 | options.data = formData; 403 | } 404 | // Blob reference is not needed anymore, free memory: 405 | options.blob = null; 406 | }, 407 | 408 | _initIframeSettings: function (options) { 409 | // Setting the dataType to iframe enables the iframe transport: 410 | options.dataType = 'iframe ' + (options.dataType || ''); 411 | // The iframe transport accepts a serialized array as form data: 412 | options.formData = this._getFormData(options); 413 | // Add redirect url to form data on cross-domain uploads: 414 | if (options.redirect && $('').prop('href', options.url) 415 | .prop('host') !== location.host) { 416 | options.formData.push({ 417 | name: options.redirectParamName || 'redirect', 418 | value: options.redirect 419 | }); 420 | } 421 | }, 422 | 423 | _initDataSettings: function (options) { 424 | if (this._isXHRUpload(options)) { 425 | if (!this._chunkedUpload(options, true)) { 426 | if (!options.data) { 427 | this._initXHRData(options); 428 | } 429 | this._initProgressListener(options); 430 | } 431 | if (options.postMessage) { 432 | // Setting the dataType to postmessage enables the 433 | // postMessage transport: 434 | options.dataType = 'postmessage ' + (options.dataType || ''); 435 | } 436 | } else { 437 | this._initIframeSettings(options, 'iframe'); 438 | } 439 | }, 440 | 441 | _getParamName: function (options) { 442 | var fileInput = $(options.fileInput), 443 | paramName = options.paramName; 444 | if (!paramName) { 445 | paramName = []; 446 | fileInput.each(function () { 447 | var input = $(this), 448 | name = input.prop('name') || 'files[]', 449 | i = (input.prop('files') || [1]).length; 450 | while (i) { 451 | paramName.push(name); 452 | i -= 1; 453 | } 454 | }); 455 | if (!paramName.length) { 456 | paramName = [fileInput.prop('name') || 'files[]']; 457 | } 458 | } else if (!$.isArray(paramName)) { 459 | paramName = [paramName]; 460 | } 461 | return paramName; 462 | }, 463 | 464 | _initFormSettings: function (options) { 465 | // Retrieve missing options from the input field and the 466 | // associated form, if available: 467 | if (!options.form || !options.form.length) { 468 | options.form = $(options.fileInput.prop('form')); 469 | // If the given file input doesn't have an associated form, 470 | // use the default widget file input's form: 471 | if (!options.form.length) { 472 | options.form = $(this.options.fileInput.prop('form')); 473 | } 474 | } 475 | options.paramName = this._getParamName(options); 476 | if (!options.url) { 477 | options.url = options.form.prop('action') || location.href; 478 | } 479 | // The HTTP request method must be "POST" or "PUT": 480 | options.type = (options.type || options.form.prop('method') || '') 481 | .toUpperCase(); 482 | if (options.type !== 'POST' && options.type !== 'PUT' && 483 | options.type !== 'PATCH') { 484 | options.type = 'POST'; 485 | } 486 | if (!options.formAcceptCharset) { 487 | options.formAcceptCharset = options.form.attr('accept-charset'); 488 | } 489 | }, 490 | 491 | _getAJAXSettings: function (data) { 492 | var options = $.extend({}, this.options, data); 493 | this._initFormSettings(options); 494 | this._initDataSettings(options); 495 | return options; 496 | }, 497 | 498 | // Maps jqXHR callbacks to the equivalent 499 | // methods of the given Promise object: 500 | _enhancePromise: function (promise) { 501 | promise.success = promise.done; 502 | promise.error = promise.fail; 503 | promise.complete = promise.always; 504 | return promise; 505 | }, 506 | 507 | // Creates and returns a Promise object enhanced with 508 | // the jqXHR methods abort, success, error and complete: 509 | _getXHRPromise: function (resolveOrReject, context, args) { 510 | var dfd = $.Deferred(), 511 | promise = dfd.promise(); 512 | context = context || this.options.context || promise; 513 | if (resolveOrReject === true) { 514 | dfd.resolveWith(context, args); 515 | } else if (resolveOrReject === false) { 516 | dfd.rejectWith(context, args); 517 | } 518 | promise.abort = dfd.promise; 519 | return this._enhancePromise(promise); 520 | }, 521 | 522 | // Parses the Range header from the server response 523 | // and returns the uploaded bytes: 524 | _getUploadedBytes: function (jqXHR) { 525 | var range = jqXHR.getResponseHeader('Range'), 526 | parts = range && range.split('-'), 527 | upperBytesPos = parts && parts.length > 1 && 528 | parseInt(parts[1], 10); 529 | return upperBytesPos && upperBytesPos + 1; 530 | }, 531 | 532 | // Uploads a file in multiple, sequential requests 533 | // by splitting the file up in multiple blob chunks. 534 | // If the second parameter is true, only tests if the file 535 | // should be uploaded in chunks, but does not invoke any 536 | // upload requests: 537 | _chunkedUpload: function (options, testOnly) { 538 | var that = this, 539 | file = options.files[0], 540 | fs = file.size, 541 | ub = options.uploadedBytes = options.uploadedBytes || 0, 542 | mcs = options.maxChunkSize || fs, 543 | slice = file.slice || file.webkitSlice || file.mozSlice, 544 | dfd = $.Deferred(), 545 | promise = dfd.promise(), 546 | jqXHR, 547 | upload; 548 | if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || 549 | options.data) { 550 | return false; 551 | } 552 | if (testOnly) { 553 | return true; 554 | } 555 | if (ub >= fs) { 556 | file.error = 'Uploaded bytes exceed file size'; 557 | return this._getXHRPromise( 558 | false, 559 | options.context, 560 | [null, 'error', file.error] 561 | ); 562 | } 563 | // The chunk upload method: 564 | upload = function () { 565 | // Clone the options object for each chunk upload: 566 | var o = $.extend({}, options); 567 | o.blob = slice.call( 568 | file, 569 | ub, 570 | ub + mcs, 571 | file.type 572 | ); 573 | // Store the current chunk size, as the blob itself 574 | // will be dereferenced after data processing: 575 | o.chunkSize = o.blob.size; 576 | // Expose the chunk bytes position range: 577 | o.contentRange = 'bytes ' + ub + '-' + 578 | (ub + o.chunkSize - 1) + '/' + fs; 579 | // Process the upload data (the blob and potential form data): 580 | that._initXHRData(o); 581 | // Add progress listeners for this chunk upload: 582 | that._initProgressListener(o); 583 | jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) || 584 | that._getXHRPromise(false, o.context)) 585 | .done(function (result, textStatus, jqXHR) { 586 | ub = that._getUploadedBytes(jqXHR) || 587 | (ub + o.chunkSize); 588 | // Create a progress event if upload is done and no progress 589 | // event has been invoked for this chunk, or there has been 590 | // no progress event with loaded equaling total: 591 | if (!o.loaded || o.loaded < o.total) { 592 | that._onProgress($.Event('progress', { 593 | lengthComputable: true, 594 | loaded: ub - o.uploadedBytes, 595 | total: ub - o.uploadedBytes 596 | }), o); 597 | } 598 | options.uploadedBytes = o.uploadedBytes = ub; 599 | o.result = result; 600 | o.textStatus = textStatus; 601 | o.jqXHR = jqXHR; 602 | that._trigger('chunkdone', null, o); 603 | that._trigger('chunkalways', null, o); 604 | if (ub < fs) { 605 | // File upload not yet complete, 606 | // continue with the next chunk: 607 | upload(); 608 | } else { 609 | dfd.resolveWith( 610 | o.context, 611 | [result, textStatus, jqXHR] 612 | ); 613 | } 614 | }) 615 | .fail(function (jqXHR, textStatus, errorThrown) { 616 | o.jqXHR = jqXHR; 617 | o.textStatus = textStatus; 618 | o.errorThrown = errorThrown; 619 | that._trigger('chunkfail', null, o); 620 | that._trigger('chunkalways', null, o); 621 | dfd.rejectWith( 622 | o.context, 623 | [jqXHR, textStatus, errorThrown] 624 | ); 625 | }); 626 | }; 627 | this._enhancePromise(promise); 628 | promise.abort = function () { 629 | return jqXHR.abort(); 630 | }; 631 | upload(); 632 | return promise; 633 | }, 634 | 635 | _beforeSend: function (e, data) { 636 | if (this._active === 0) { 637 | // the start callback is triggered when an upload starts 638 | // and no other uploads are currently running, 639 | // equivalent to the global ajaxStart event: 640 | this._trigger('start'); 641 | // Set timer for global bitrate progress calculation: 642 | this._bitrateTimer = new this._BitrateTimer(); 643 | } 644 | this._active += 1; 645 | // Initialize the global progress values: 646 | this._loaded += data.uploadedBytes || 0; 647 | this._total += this._getTotal(data.files); 648 | }, 649 | 650 | _onDone: function (result, textStatus, jqXHR, options) { 651 | if (!this._isXHRUpload(options) || !options.loaded || 652 | options.loaded < options.total) { 653 | var total = this._getTotal(options.files) || 1; 654 | // Create a progress event for each iframe load, 655 | // or if there has been no progress event with 656 | // loaded equaling total for XHR uploads: 657 | this._onProgress($.Event('progress', { 658 | lengthComputable: true, 659 | loaded: total, 660 | total: total 661 | }), options); 662 | } 663 | options.result = result; 664 | options.textStatus = textStatus; 665 | options.jqXHR = jqXHR; 666 | this._trigger('done', null, options); 667 | }, 668 | 669 | _onFail: function (jqXHR, textStatus, errorThrown, options) { 670 | options.jqXHR = jqXHR; 671 | options.textStatus = textStatus; 672 | options.errorThrown = errorThrown; 673 | this._trigger('fail', null, options); 674 | if (options.recalculateProgress) { 675 | // Remove the failed (error or abort) file upload from 676 | // the global progress calculation: 677 | this._loaded -= options.loaded || options.uploadedBytes || 0; 678 | this._total -= options.total || this._getTotal(options.files); 679 | } 680 | }, 681 | 682 | _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { 683 | // jqXHRorResult, textStatus and jqXHRorError are added to the 684 | // options object via done and fail callbacks 685 | this._active -= 1; 686 | this._trigger('always', null, options); 687 | if (this._active === 0) { 688 | // The stop callback is triggered when all uploads have 689 | // been completed, equivalent to the global ajaxStop event: 690 | this._trigger('stop'); 691 | // Reset the global progress values: 692 | this._loaded = this._total = 0; 693 | this._bitrateTimer = null; 694 | } 695 | }, 696 | 697 | _onSend: function (e, data) { 698 | var that = this, 699 | jqXHR, 700 | aborted, 701 | slot, 702 | pipe, 703 | options = that._getAJAXSettings(data), 704 | send = function () { 705 | that._sending += 1; 706 | // Set timer for bitrate progress calculation: 707 | options._bitrateTimer = new that._BitrateTimer(); 708 | jqXHR = jqXHR || ( 709 | ((aborted || that._trigger('send', e, options) === false) && 710 | that._getXHRPromise(false, options.context, aborted)) || 711 | that._chunkedUpload(options) || $.ajax(options) 712 | ).done(function (result, textStatus, jqXHR) { 713 | that._onDone(result, textStatus, jqXHR, options); 714 | }).fail(function (jqXHR, textStatus, errorThrown) { 715 | that._onFail(jqXHR, textStatus, errorThrown, options); 716 | }).always(function (jqXHRorResult, textStatus, jqXHRorError) { 717 | that._sending -= 1; 718 | that._onAlways( 719 | jqXHRorResult, 720 | textStatus, 721 | jqXHRorError, 722 | options 723 | ); 724 | if (options.limitConcurrentUploads && 725 | options.limitConcurrentUploads > that._sending) { 726 | // Start the next queued upload, 727 | // that has not been aborted: 728 | var nextSlot = that._slots.shift(), 729 | isPending; 730 | while (nextSlot) { 731 | // jQuery 1.6 doesn't provide .state(), 732 | // while jQuery 1.8+ removed .isRejected(): 733 | isPending = nextSlot.state ? 734 | nextSlot.state() === 'pending' : 735 | !nextSlot.isRejected(); 736 | if (isPending) { 737 | nextSlot.resolve(); 738 | break; 739 | } 740 | nextSlot = that._slots.shift(); 741 | } 742 | } 743 | }); 744 | return jqXHR; 745 | }; 746 | this._beforeSend(e, options); 747 | if (this.options.sequentialUploads || 748 | (this.options.limitConcurrentUploads && 749 | this.options.limitConcurrentUploads <= this._sending)) { 750 | if (this.options.limitConcurrentUploads > 1) { 751 | slot = $.Deferred(); 752 | this._slots.push(slot); 753 | pipe = slot.pipe(send); 754 | } else { 755 | pipe = (this._sequence = this._sequence.pipe(send, send)); 756 | } 757 | // Return the piped Promise object, enhanced with an abort method, 758 | // which is delegated to the jqXHR object of the current upload, 759 | // and jqXHR callbacks mapped to the equivalent Promise methods: 760 | pipe.abort = function () { 761 | aborted = [undefined, 'abort', 'abort']; 762 | if (!jqXHR) { 763 | if (slot) { 764 | slot.rejectWith(options.context, aborted); 765 | } 766 | return send(); 767 | } 768 | return jqXHR.abort(); 769 | }; 770 | return this._enhancePromise(pipe); 771 | } 772 | return send(); 773 | }, 774 | 775 | _onAdd: function (e, data) { 776 | var that = this, 777 | result = true, 778 | options = $.extend({}, this.options, data), 779 | limit = options.limitMultiFileUploads, 780 | paramName = this._getParamName(options), 781 | paramNameSet, 782 | paramNameSlice, 783 | fileSet, 784 | i; 785 | if (!(options.singleFileUploads || limit) || 786 | !this._isXHRUpload(options)) { 787 | fileSet = [data.files]; 788 | paramNameSet = [paramName]; 789 | } else if (!options.singleFileUploads && limit) { 790 | fileSet = []; 791 | paramNameSet = []; 792 | for (i = 0; i < data.files.length; i += limit) { 793 | fileSet.push(data.files.slice(i, i + limit)); 794 | paramNameSlice = paramName.slice(i, i + limit); 795 | if (!paramNameSlice.length) { 796 | paramNameSlice = paramName; 797 | } 798 | paramNameSet.push(paramNameSlice); 799 | } 800 | } else { 801 | paramNameSet = paramName; 802 | } 803 | data.originalFiles = data.files; 804 | $.each(fileSet || data.files, function (index, element) { 805 | var newData = $.extend({}, data); 806 | newData.files = fileSet ? element : [element]; 807 | newData.paramName = paramNameSet[index]; 808 | newData.submit = function () { 809 | newData.jqXHR = this.jqXHR = 810 | (that._trigger('submit', e, this) !== false) && 811 | that._onSend(e, this); 812 | return this.jqXHR; 813 | }; 814 | result = that._trigger('add', e, newData); 815 | return result; 816 | }); 817 | return result; 818 | }, 819 | 820 | _replaceFileInput: function (input) { 821 | var inputClone = input.clone(true); 822 | $('
').append(inputClone)[0].reset(); 823 | // Detaching allows to insert the fileInput on another form 824 | // without loosing the file input value: 825 | input.after(inputClone).detach(); 826 | // Avoid memory leaks with the detached file input: 827 | $.cleanData(input.unbind('remove')); 828 | // Replace the original file input element in the fileInput 829 | // elements set with the clone, which has been copied including 830 | // event handlers: 831 | this.options.fileInput = this.options.fileInput.map(function (i, el) { 832 | if (el === input[0]) { 833 | return inputClone[0]; 834 | } 835 | return el; 836 | }); 837 | // If the widget has been initialized on the file input itself, 838 | // override this.element with the file input clone: 839 | if (input[0] === this.element[0]) { 840 | this.element = inputClone; 841 | } 842 | }, 843 | 844 | _handleFileTreeEntry: function (entry, path) { 845 | var that = this, 846 | dfd = $.Deferred(), 847 | errorHandler = function (e) { 848 | if (e && !e.entry) { 849 | e.entry = entry; 850 | } 851 | // Since $.when returns immediately if one 852 | // Deferred is rejected, we use resolve instead. 853 | // This allows valid files and invalid items 854 | // to be returned together in one set: 855 | dfd.resolve([e]); 856 | }, 857 | dirReader; 858 | path = path || ''; 859 | if (entry.isFile) { 860 | if (entry._file) { 861 | // Workaround for Chrome bug #149735 862 | entry._file.relativePath = path; 863 | dfd.resolve(entry._file); 864 | } else { 865 | entry.file(function (file) { 866 | file.relativePath = path; 867 | dfd.resolve(file); 868 | }, errorHandler); 869 | } 870 | } else if (entry.isDirectory) { 871 | dirReader = entry.createReader(); 872 | dirReader.readEntries(function (entries) { 873 | that._handleFileTreeEntries( 874 | entries, 875 | path + entry.name + '/' 876 | ).done(function (files) { 877 | dfd.resolve(files); 878 | }).fail(errorHandler); 879 | }, errorHandler); 880 | } else { 881 | // Return an empy list for file system items 882 | // other than files or directories: 883 | dfd.resolve([]); 884 | } 885 | return dfd.promise(); 886 | }, 887 | 888 | _handleFileTreeEntries: function (entries, path) { 889 | var that = this; 890 | return $.when.apply( 891 | $, 892 | $.map(entries, function (entry) { 893 | return that._handleFileTreeEntry(entry, path); 894 | }) 895 | ).pipe(function () { 896 | return Array.prototype.concat.apply( 897 | [], 898 | arguments 899 | ); 900 | }); 901 | }, 902 | 903 | _getDroppedFiles: function (dataTransfer) { 904 | dataTransfer = dataTransfer || {}; 905 | var items = dataTransfer.items; 906 | if (items && items.length && (items[0].webkitGetAsEntry || 907 | items[0].getAsEntry)) { 908 | return this._handleFileTreeEntries( 909 | $.map(items, function (item) { 910 | var entry; 911 | if (item.webkitGetAsEntry) { 912 | entry = item.webkitGetAsEntry(); 913 | if (entry) { 914 | // Workaround for Chrome bug #149735: 915 | entry._file = item.getAsFile(); 916 | } 917 | return entry; 918 | } 919 | return item.getAsEntry(); 920 | }) 921 | ); 922 | } 923 | return $.Deferred().resolve( 924 | $.makeArray(dataTransfer.files) 925 | ).promise(); 926 | }, 927 | 928 | _getSingleFileInputFiles: function (fileInput) { 929 | fileInput = $(fileInput); 930 | var entries = fileInput.prop('webkitEntries') || 931 | fileInput.prop('entries'), 932 | files, 933 | value; 934 | if (entries && entries.length) { 935 | return this._handleFileTreeEntries(entries); 936 | } 937 | files = $.makeArray(fileInput.prop('files')); 938 | if (!files.length) { 939 | value = fileInput.prop('value'); 940 | if (!value) { 941 | return $.Deferred().resolve([]).promise(); 942 | } 943 | // If the files property is not available, the browser does not 944 | // support the File API and we add a pseudo File object with 945 | // the input value as name with path information removed: 946 | files = [{name: value.replace(/^.*\\/, '')}]; 947 | } else if (files[0].name === undefined && files[0].fileName) { 948 | // File normalization for Safari 4 and Firefox 3: 949 | $.each(files, function (index, file) { 950 | file.name = file.fileName; 951 | file.size = file.fileSize; 952 | }); 953 | } 954 | return $.Deferred().resolve(files).promise(); 955 | }, 956 | 957 | _getFileInputFiles: function (fileInput) { 958 | if (!(fileInput instanceof $) || fileInput.length === 1) { 959 | return this._getSingleFileInputFiles(fileInput); 960 | } 961 | return $.when.apply( 962 | $, 963 | $.map(fileInput, this._getSingleFileInputFiles) 964 | ).pipe(function () { 965 | return Array.prototype.concat.apply( 966 | [], 967 | arguments 968 | ); 969 | }); 970 | }, 971 | 972 | _onChange: function (e) { 973 | var that = this, 974 | data = { 975 | fileInput: $(e.target), 976 | form: $(e.target.form) 977 | }; 978 | this._getFileInputFiles(data.fileInput).always(function (files) { 979 | data.files = files; 980 | if (that.options.replaceFileInput) { 981 | that._replaceFileInput(data.fileInput); 982 | } 983 | if (that._trigger('change', e, data) !== false) { 984 | that._onAdd(e, data); 985 | } 986 | }); 987 | }, 988 | 989 | _onPaste: function (e) { 990 | var cbd = e.originalEvent.clipboardData, 991 | items = (cbd && cbd.items) || [], 992 | data = {files: []}; 993 | $.each(items, function (index, item) { 994 | var file = item.getAsFile && item.getAsFile(); 995 | if (file) { 996 | data.files.push(file); 997 | } 998 | }); 999 | if (this._trigger('paste', e, data) === false || 1000 | this._onAdd(e, data) === false) { 1001 | return false; 1002 | } 1003 | }, 1004 | 1005 | _onDrop: function (e) { 1006 | var that = this, 1007 | dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer, 1008 | data = {}; 1009 | if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { 1010 | e.preventDefault(); 1011 | } 1012 | this._getDroppedFiles(dataTransfer).always(function (files) { 1013 | data.files = files; 1014 | if (that._trigger('drop', e, data) !== false) { 1015 | that._onAdd(e, data); 1016 | } 1017 | }); 1018 | }, 1019 | 1020 | _onDragOver: function (e) { 1021 | var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; 1022 | if (this._trigger('dragover', e) === false) { 1023 | return false; 1024 | } 1025 | if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) { 1026 | dataTransfer.dropEffect = 'copy'; 1027 | e.preventDefault(); 1028 | } 1029 | }, 1030 | 1031 | _initEventHandlers: function () { 1032 | if (this._isXHRUpload(this.options)) { 1033 | this._on(this.options.dropZone, { 1034 | dragover: this._onDragOver, 1035 | drop: this._onDrop 1036 | }); 1037 | this._on(this.options.pasteZone, { 1038 | paste: this._onPaste 1039 | }); 1040 | } 1041 | this._on(this.options.fileInput, { 1042 | change: this._onChange 1043 | }); 1044 | }, 1045 | 1046 | _destroyEventHandlers: function () { 1047 | this._off(this.options.dropZone, 'dragover drop'); 1048 | this._off(this.options.pasteZone, 'paste'); 1049 | this._off(this.options.fileInput, 'change'); 1050 | }, 1051 | 1052 | _setOption: function (key, value) { 1053 | var refresh = $.inArray(key, this._refreshOptionsList) !== -1; 1054 | if (refresh) { 1055 | this._destroyEventHandlers(); 1056 | } 1057 | this._super(key, value); 1058 | if (refresh) { 1059 | this._initSpecialOptions(); 1060 | this._initEventHandlers(); 1061 | } 1062 | }, 1063 | 1064 | _initSpecialOptions: function () { 1065 | var options = this.options; 1066 | if (options.fileInput === undefined) { 1067 | options.fileInput = this.element.is('input[type="file"]') ? 1068 | this.element : this.element.find('input[type="file"]'); 1069 | } else if (!(options.fileInput instanceof $)) { 1070 | options.fileInput = $(options.fileInput); 1071 | } 1072 | if (!(options.dropZone instanceof $)) { 1073 | options.dropZone = $(options.dropZone); 1074 | } 1075 | if (!(options.pasteZone instanceof $)) { 1076 | options.pasteZone = $(options.pasteZone); 1077 | } 1078 | }, 1079 | 1080 | _create: function () { 1081 | var options = this.options; 1082 | // Initialize options set via HTML5 data-attributes: 1083 | $.extend(options, $(this.element[0].cloneNode(false)).data()); 1084 | this._initSpecialOptions(); 1085 | this._slots = []; 1086 | this._sequence = this._getXHRPromise(true); 1087 | this._sending = this._active = this._loaded = this._total = 0; 1088 | this._initEventHandlers(); 1089 | }, 1090 | 1091 | _destroy: function () { 1092 | this._destroyEventHandlers(); 1093 | }, 1094 | 1095 | // This method is exposed to the widget API and allows adding files 1096 | // using the fileupload API. The data parameter accepts an object which 1097 | // must have a files property and can contain additional options: 1098 | // .fileupload('add', {files: filesList}); 1099 | add: function (data) { 1100 | var that = this; 1101 | if (!data || this.options.disabled) { 1102 | return; 1103 | } 1104 | if (data.fileInput && !data.files) { 1105 | this._getFileInputFiles(data.fileInput).always(function (files) { 1106 | data.files = files; 1107 | that._onAdd(null, data); 1108 | }); 1109 | } else { 1110 | data.files = $.makeArray(data.files); 1111 | this._onAdd(null, data); 1112 | } 1113 | }, 1114 | 1115 | // This method is exposed to the widget API and allows sending files 1116 | // using the fileupload API. The data parameter accepts an object which 1117 | // must have a files or fileInput property and can contain additional options: 1118 | // .fileupload('send', {files: filesList}); 1119 | // The method returns a Promise object for the file upload call. 1120 | send: function (data) { 1121 | if (data && !this.options.disabled) { 1122 | if (data.fileInput && !data.files) { 1123 | var that = this, 1124 | dfd = $.Deferred(), 1125 | promise = dfd.promise(), 1126 | jqXHR, 1127 | aborted; 1128 | promise.abort = function () { 1129 | aborted = true; 1130 | if (jqXHR) { 1131 | return jqXHR.abort(); 1132 | } 1133 | dfd.reject(null, 'abort', 'abort'); 1134 | return promise; 1135 | }; 1136 | this._getFileInputFiles(data.fileInput).always( 1137 | function (files) { 1138 | if (aborted) { 1139 | return; 1140 | } 1141 | data.files = files; 1142 | jqXHR = that._onSend(null, data).then( 1143 | function (result, textStatus, jqXHR) { 1144 | dfd.resolve(result, textStatus, jqXHR); 1145 | }, 1146 | function (jqXHR, textStatus, errorThrown) { 1147 | dfd.reject(jqXHR, textStatus, errorThrown); 1148 | } 1149 | ); 1150 | } 1151 | ); 1152 | return this._enhancePromise(promise); 1153 | } 1154 | data.files = $.makeArray(data.files); 1155 | if (data.files.length) { 1156 | return this._onSend(null, data); 1157 | } 1158 | } 1159 | return this._getXHRPromise(false, data && data.context); 1160 | } 1161 | 1162 | }); 1163 | 1164 | })); 1165 | -------------------------------------------------------------------------------- /public/javascripts/jquery.lettering.js: -------------------------------------------------------------------------------- 1 | /*global jQuery */ 2 | /*! 3 | * Lettering.JS 0.6.1 4 | * 5 | * Copyright 2010, Dave Rupert http://daverupert.com 6 | * Released under the WTFPL license 7 | * http://sam.zoy.org/wtfpl/ 8 | * 9 | * Thanks to Paul Irish - http://paulirish.com - for the feedback. 10 | * 11 | * Date: Mon Sep 20 17:14:00 2010 -0600 12 | */ 13 | (function($){ 14 | function injector(t, splitter, klass, after) { 15 | var a = t.text().split(splitter), inject = ''; 16 | if (a.length) { 17 | $(a).each(function(i, item) { 18 | inject += ''+item+''+after; 19 | }); 20 | t.empty().append(inject); 21 | } 22 | } 23 | 24 | var methods = { 25 | init : function() { 26 | 27 | return this.each(function() { 28 | injector($(this), '', 'char', ''); 29 | }); 30 | 31 | }, 32 | 33 | words : function() { 34 | 35 | return this.each(function() { 36 | injector($(this), ' ', 'word', ' '); 37 | }); 38 | 39 | }, 40 | 41 | lines : function() { 42 | 43 | return this.each(function() { 44 | var r = "eefec303079ad17405c889e092e105b0"; 45 | // Because it's hard to split a
tag consistently across browsers, 46 | // (*ahem* IE *ahem*), we replaces all
instances with an md5 hash 47 | // (of the word "split"). If you're trying to use this plugin on that 48 | // md5 hash string, it will fail because you're being ridiculous. 49 | injector($(this).children("br").replaceWith(r).end(), r, 'line', ''); 50 | }); 51 | 52 | } 53 | }; 54 | 55 | $.fn.lettering = function( method ) { 56 | // Method calling logic 57 | if ( method && methods[method] ) { 58 | return methods[ method ].apply( this, [].slice.call( arguments, 1 )); 59 | } else if ( method === 'letters' || ! method ) { 60 | return methods.init.apply( this, [].slice.call( arguments, 0 ) ); // always pass an array 61 | } 62 | $.error( 'Method ' + method + ' does not exist on jQuery.lettering' ); 63 | return this; 64 | }; 65 | 66 | })(jQuery); -------------------------------------------------------------------------------- /public/javascripts/jquery.ui.widget.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI Widget 1.10.0+amd 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * http://api.jqueryui.com/jQuery.widget/ 10 | */ 11 | 12 | (function (factory) { 13 | if (typeof define === "function" && define.amd) { 14 | // Register as an anonymous AMD module: 15 | define(["jquery"], factory); 16 | } else { 17 | // Browser globals: 18 | factory(jQuery); 19 | } 20 | }(function( $, undefined ) { 21 | 22 | var uuid = 0, 23 | slice = Array.prototype.slice, 24 | _cleanData = $.cleanData; 25 | $.cleanData = function( elems ) { 26 | for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 27 | try { 28 | $( elem ).triggerHandler( "remove" ); 29 | // http://bugs.jquery.com/ticket/8235 30 | } catch( e ) {} 31 | } 32 | _cleanData( elems ); 33 | }; 34 | 35 | $.widget = function( name, base, prototype ) { 36 | var fullName, existingConstructor, constructor, basePrototype, 37 | // proxiedPrototype allows the provided prototype to remain unmodified 38 | // so that it can be used as a mixin for multiple widgets (#8876) 39 | proxiedPrototype = {}, 40 | namespace = name.split( "." )[ 0 ]; 41 | 42 | name = name.split( "." )[ 1 ]; 43 | fullName = namespace + "-" + name; 44 | 45 | if ( !prototype ) { 46 | prototype = base; 47 | base = $.Widget; 48 | } 49 | 50 | // create selector for plugin 51 | $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { 52 | return !!$.data( elem, fullName ); 53 | }; 54 | 55 | $[ namespace ] = $[ namespace ] || {}; 56 | existingConstructor = $[ namespace ][ name ]; 57 | constructor = $[ namespace ][ name ] = function( options, element ) { 58 | // allow instantiation without "new" keyword 59 | if ( !this._createWidget ) { 60 | return new constructor( options, element ); 61 | } 62 | 63 | // allow instantiation without initializing for simple inheritance 64 | // must use "new" keyword (the code above always passes args) 65 | if ( arguments.length ) { 66 | this._createWidget( options, element ); 67 | } 68 | }; 69 | // extend with the existing constructor to carry over any static properties 70 | $.extend( constructor, existingConstructor, { 71 | version: prototype.version, 72 | // copy the object used to create the prototype in case we need to 73 | // redefine the widget later 74 | _proto: $.extend( {}, prototype ), 75 | // track widgets that inherit from this widget in case this widget is 76 | // redefined after a widget inherits from it 77 | _childConstructors: [] 78 | }); 79 | 80 | basePrototype = new base(); 81 | // we need to make the options hash a property directly on the new instance 82 | // otherwise we'll modify the options hash on the prototype that we're 83 | // inheriting from 84 | basePrototype.options = $.widget.extend( {}, basePrototype.options ); 85 | $.each( prototype, function( prop, value ) { 86 | if ( !$.isFunction( value ) ) { 87 | proxiedPrototype[ prop ] = value; 88 | return; 89 | } 90 | proxiedPrototype[ prop ] = (function() { 91 | var _super = function() { 92 | return base.prototype[ prop ].apply( this, arguments ); 93 | }, 94 | _superApply = function( args ) { 95 | return base.prototype[ prop ].apply( this, args ); 96 | }; 97 | return function() { 98 | var __super = this._super, 99 | __superApply = this._superApply, 100 | returnValue; 101 | 102 | this._super = _super; 103 | this._superApply = _superApply; 104 | 105 | returnValue = value.apply( this, arguments ); 106 | 107 | this._super = __super; 108 | this._superApply = __superApply; 109 | 110 | return returnValue; 111 | }; 112 | })(); 113 | }); 114 | constructor.prototype = $.widget.extend( basePrototype, { 115 | // TODO: remove support for widgetEventPrefix 116 | // always use the name + a colon as the prefix, e.g., draggable:start 117 | // don't prefix for widgets that aren't DOM-based 118 | widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name 119 | }, proxiedPrototype, { 120 | constructor: constructor, 121 | namespace: namespace, 122 | widgetName: name, 123 | widgetFullName: fullName 124 | }); 125 | 126 | // If this widget is being redefined then we need to find all widgets that 127 | // are inheriting from it and redefine all of them so that they inherit from 128 | // the new version of this widget. We're essentially trying to replace one 129 | // level in the prototype chain. 130 | if ( existingConstructor ) { 131 | $.each( existingConstructor._childConstructors, function( i, child ) { 132 | var childPrototype = child.prototype; 133 | 134 | // redefine the child widget using the same prototype that was 135 | // originally used, but inherit from the new version of the base 136 | $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); 137 | }); 138 | // remove the list of existing child constructors from the old constructor 139 | // so the old child constructors can be garbage collected 140 | delete existingConstructor._childConstructors; 141 | } else { 142 | base._childConstructors.push( constructor ); 143 | } 144 | 145 | $.widget.bridge( name, constructor ); 146 | }; 147 | 148 | $.widget.extend = function( target ) { 149 | var input = slice.call( arguments, 1 ), 150 | inputIndex = 0, 151 | inputLength = input.length, 152 | key, 153 | value; 154 | for ( ; inputIndex < inputLength; inputIndex++ ) { 155 | for ( key in input[ inputIndex ] ) { 156 | value = input[ inputIndex ][ key ]; 157 | if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { 158 | // Clone objects 159 | if ( $.isPlainObject( value ) ) { 160 | target[ key ] = $.isPlainObject( target[ key ] ) ? 161 | $.widget.extend( {}, target[ key ], value ) : 162 | // Don't extend strings, arrays, etc. with objects 163 | $.widget.extend( {}, value ); 164 | // Copy everything else by reference 165 | } else { 166 | target[ key ] = value; 167 | } 168 | } 169 | } 170 | } 171 | return target; 172 | }; 173 | 174 | $.widget.bridge = function( name, object ) { 175 | var fullName = object.prototype.widgetFullName || name; 176 | $.fn[ name ] = function( options ) { 177 | var isMethodCall = typeof options === "string", 178 | args = slice.call( arguments, 1 ), 179 | returnValue = this; 180 | 181 | // allow multiple hashes to be passed on init 182 | options = !isMethodCall && args.length ? 183 | $.widget.extend.apply( null, [ options ].concat(args) ) : 184 | options; 185 | 186 | if ( isMethodCall ) { 187 | this.each(function() { 188 | var methodValue, 189 | instance = $.data( this, fullName ); 190 | if ( !instance ) { 191 | return $.error( "cannot call methods on " + name + " prior to initialization; " + 192 | "attempted to call method '" + options + "'" ); 193 | } 194 | if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { 195 | return $.error( "no such method '" + options + "' for " + name + " widget instance" ); 196 | } 197 | methodValue = instance[ options ].apply( instance, args ); 198 | if ( methodValue !== instance && methodValue !== undefined ) { 199 | returnValue = methodValue && methodValue.jquery ? 200 | returnValue.pushStack( methodValue.get() ) : 201 | methodValue; 202 | return false; 203 | } 204 | }); 205 | } else { 206 | this.each(function() { 207 | var instance = $.data( this, fullName ); 208 | if ( instance ) { 209 | instance.option( options || {} )._init(); 210 | } else { 211 | $.data( this, fullName, new object( options, this ) ); 212 | } 213 | }); 214 | } 215 | 216 | return returnValue; 217 | }; 218 | }; 219 | 220 | $.Widget = function( /* options, element */ ) {}; 221 | $.Widget._childConstructors = []; 222 | 223 | $.Widget.prototype = { 224 | widgetName: "widget", 225 | widgetEventPrefix: "", 226 | defaultElement: "
", 227 | options: { 228 | disabled: false, 229 | 230 | // callbacks 231 | create: null 232 | }, 233 | _createWidget: function( options, element ) { 234 | element = $( element || this.defaultElement || this )[ 0 ]; 235 | this.element = $( element ); 236 | this.uuid = uuid++; 237 | this.eventNamespace = "." + this.widgetName + this.uuid; 238 | this.options = $.widget.extend( {}, 239 | this.options, 240 | this._getCreateOptions(), 241 | options ); 242 | 243 | this.bindings = $(); 244 | this.hoverable = $(); 245 | this.focusable = $(); 246 | 247 | if ( element !== this ) { 248 | $.data( element, this.widgetFullName, this ); 249 | this._on( true, this.element, { 250 | remove: function( event ) { 251 | if ( event.target === element ) { 252 | this.destroy(); 253 | } 254 | } 255 | }); 256 | this.document = $( element.style ? 257 | // element within the document 258 | element.ownerDocument : 259 | // element is window or document 260 | element.document || element ); 261 | this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); 262 | } 263 | 264 | this._create(); 265 | this._trigger( "create", null, this._getCreateEventData() ); 266 | this._init(); 267 | }, 268 | _getCreateOptions: $.noop, 269 | _getCreateEventData: $.noop, 270 | _create: $.noop, 271 | _init: $.noop, 272 | 273 | destroy: function() { 274 | this._destroy(); 275 | // we can probably remove the unbind calls in 2.0 276 | // all event bindings should go through this._on() 277 | this.element 278 | .unbind( this.eventNamespace ) 279 | // 1.9 BC for #7810 280 | // TODO remove dual storage 281 | .removeData( this.widgetName ) 282 | .removeData( this.widgetFullName ) 283 | // support: jquery <1.6.3 284 | // http://bugs.jquery.com/ticket/9413 285 | .removeData( $.camelCase( this.widgetFullName ) ); 286 | this.widget() 287 | .unbind( this.eventNamespace ) 288 | .removeAttr( "aria-disabled" ) 289 | .removeClass( 290 | this.widgetFullName + "-disabled " + 291 | "ui-state-disabled" ); 292 | 293 | // clean up events and states 294 | this.bindings.unbind( this.eventNamespace ); 295 | this.hoverable.removeClass( "ui-state-hover" ); 296 | this.focusable.removeClass( "ui-state-focus" ); 297 | }, 298 | _destroy: $.noop, 299 | 300 | widget: function() { 301 | return this.element; 302 | }, 303 | 304 | option: function( key, value ) { 305 | var options = key, 306 | parts, 307 | curOption, 308 | i; 309 | 310 | if ( arguments.length === 0 ) { 311 | // don't return a reference to the internal hash 312 | return $.widget.extend( {}, this.options ); 313 | } 314 | 315 | if ( typeof key === "string" ) { 316 | // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } 317 | options = {}; 318 | parts = key.split( "." ); 319 | key = parts.shift(); 320 | if ( parts.length ) { 321 | curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); 322 | for ( i = 0; i < parts.length - 1; i++ ) { 323 | curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; 324 | curOption = curOption[ parts[ i ] ]; 325 | } 326 | key = parts.pop(); 327 | if ( value === undefined ) { 328 | return curOption[ key ] === undefined ? null : curOption[ key ]; 329 | } 330 | curOption[ key ] = value; 331 | } else { 332 | if ( value === undefined ) { 333 | return this.options[ key ] === undefined ? null : this.options[ key ]; 334 | } 335 | options[ key ] = value; 336 | } 337 | } 338 | 339 | this._setOptions( options ); 340 | 341 | return this; 342 | }, 343 | _setOptions: function( options ) { 344 | var key; 345 | 346 | for ( key in options ) { 347 | this._setOption( key, options[ key ] ); 348 | } 349 | 350 | return this; 351 | }, 352 | _setOption: function( key, value ) { 353 | this.options[ key ] = value; 354 | 355 | if ( key === "disabled" ) { 356 | this.widget() 357 | .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) 358 | .attr( "aria-disabled", value ); 359 | this.hoverable.removeClass( "ui-state-hover" ); 360 | this.focusable.removeClass( "ui-state-focus" ); 361 | } 362 | 363 | return this; 364 | }, 365 | 366 | enable: function() { 367 | return this._setOption( "disabled", false ); 368 | }, 369 | disable: function() { 370 | return this._setOption( "disabled", true ); 371 | }, 372 | 373 | _on: function( suppressDisabledCheck, element, handlers ) { 374 | var delegateElement, 375 | instance = this; 376 | 377 | // no suppressDisabledCheck flag, shuffle arguments 378 | if ( typeof suppressDisabledCheck !== "boolean" ) { 379 | handlers = element; 380 | element = suppressDisabledCheck; 381 | suppressDisabledCheck = false; 382 | } 383 | 384 | // no element argument, shuffle and use this.element 385 | if ( !handlers ) { 386 | handlers = element; 387 | element = this.element; 388 | delegateElement = this.widget(); 389 | } else { 390 | // accept selectors, DOM elements 391 | element = delegateElement = $( element ); 392 | this.bindings = this.bindings.add( element ); 393 | } 394 | 395 | $.each( handlers, function( event, handler ) { 396 | function handlerProxy() { 397 | // allow widgets to customize the disabled handling 398 | // - disabled as an array instead of boolean 399 | // - disabled class as method for disabling individual parts 400 | if ( !suppressDisabledCheck && 401 | ( instance.options.disabled === true || 402 | $( this ).hasClass( "ui-state-disabled" ) ) ) { 403 | return; 404 | } 405 | return ( typeof handler === "string" ? instance[ handler ] : handler ) 406 | .apply( instance, arguments ); 407 | } 408 | 409 | // copy the guid so direct unbinding works 410 | if ( typeof handler !== "string" ) { 411 | handlerProxy.guid = handler.guid = 412 | handler.guid || handlerProxy.guid || $.guid++; 413 | } 414 | 415 | var match = event.match( /^(\w+)\s*(.*)$/ ), 416 | eventName = match[1] + instance.eventNamespace, 417 | selector = match[2]; 418 | if ( selector ) { 419 | delegateElement.delegate( selector, eventName, handlerProxy ); 420 | } else { 421 | element.bind( eventName, handlerProxy ); 422 | } 423 | }); 424 | }, 425 | 426 | _off: function( element, eventName ) { 427 | eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; 428 | element.unbind( eventName ).undelegate( eventName ); 429 | }, 430 | 431 | _delay: function( handler, delay ) { 432 | function handlerProxy() { 433 | return ( typeof handler === "string" ? instance[ handler ] : handler ) 434 | .apply( instance, arguments ); 435 | } 436 | var instance = this; 437 | return setTimeout( handlerProxy, delay || 0 ); 438 | }, 439 | 440 | _hoverable: function( element ) { 441 | this.hoverable = this.hoverable.add( element ); 442 | this._on( element, { 443 | mouseenter: function( event ) { 444 | $( event.currentTarget ).addClass( "ui-state-hover" ); 445 | }, 446 | mouseleave: function( event ) { 447 | $( event.currentTarget ).removeClass( "ui-state-hover" ); 448 | } 449 | }); 450 | }, 451 | 452 | _focusable: function( element ) { 453 | this.focusable = this.focusable.add( element ); 454 | this._on( element, { 455 | focusin: function( event ) { 456 | $( event.currentTarget ).addClass( "ui-state-focus" ); 457 | }, 458 | focusout: function( event ) { 459 | $( event.currentTarget ).removeClass( "ui-state-focus" ); 460 | } 461 | }); 462 | }, 463 | 464 | _trigger: function( type, event, data ) { 465 | var prop, orig, 466 | callback = this.options[ type ]; 467 | 468 | data = data || {}; 469 | event = $.Event( event ); 470 | event.type = ( type === this.widgetEventPrefix ? 471 | type : 472 | this.widgetEventPrefix + type ).toLowerCase(); 473 | // the original event may come from any element 474 | // so we need to reset the target on the new event 475 | event.target = this.element[ 0 ]; 476 | 477 | // copy original event properties over to the new event 478 | orig = event.originalEvent; 479 | if ( orig ) { 480 | for ( prop in orig ) { 481 | if ( !( prop in event ) ) { 482 | event[ prop ] = orig[ prop ]; 483 | } 484 | } 485 | } 486 | 487 | this.element.trigger( event, data ); 488 | return !( $.isFunction( callback ) && 489 | callback.apply( this.element[0], [ event ].concat( data ) ) === false || 490 | event.isDefaultPrevented() ); 491 | } 492 | }; 493 | 494 | $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { 495 | $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { 496 | if ( typeof options === "string" ) { 497 | options = { effect: options }; 498 | } 499 | var hasOptions, 500 | effectName = !options ? 501 | method : 502 | options === true || typeof options === "number" ? 503 | defaultEffect : 504 | options.effect || defaultEffect; 505 | options = options || {}; 506 | if ( typeof options === "number" ) { 507 | options = { duration: options }; 508 | } 509 | hasOptions = !$.isEmptyObject( options ); 510 | options.complete = callback; 511 | if ( options.delay ) { 512 | element.delay( options.delay ); 513 | } 514 | if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { 515 | element[ method ]( options ); 516 | } else if ( effectName !== method && element[ effectName ] ) { 517 | element[ effectName ]( options.duration, options.easing, callback ); 518 | } else { 519 | element.queue(function( next ) { 520 | $( this )[ method ](); 521 | if ( callback ) { 522 | callback.call( element[ 0 ] ); 523 | } 524 | next(); 525 | }); 526 | } 527 | }; 528 | }); 529 | 530 | })); 531 | -------------------------------------------------------------------------------- /public/javascripts/less-1.3.3.min.js: -------------------------------------------------------------------------------- 1 | // 2 | // LESS - Leaner CSS v1.3.3 3 | // http://lesscss.org 4 | // 5 | // Copyright (c) 2009-2013, Alexis Sellier 6 | // Licensed under the Apache 2.0 License. 7 | // 8 | (function(e,t){function n(t){return e.less[t.split("/")[1]]}function f(){r.env==="development"?(r.optimization=0,r.watchTimer=setInterval(function(){r.watchMode&&g(function(e,t,n,r,i){t&&S(t.toCSS(),r,i.lastModified)})},r.poll)):r.optimization=3}function m(){var e=document.getElementsByTagName("style");for(var t=0;t0&&(s.splice(o-1,2),o-=2)}return i.hostPart=r[1],i.directories=s,i.path=r[1]+s.join("/"),i.fileUrl=i.path+(r[4]||""),i.url=i.fileUrl+(r[5]||""),i}function w(t,n,i,s){var o=t.contents||{},u=t.files||{},a=b(t.href,e.location.href),f=a.url,c=l&&l.getItem(f),h=l&&l.getItem(f+":timestamp"),p={css:c,timestamp:h},d;r.relativeUrls?r.rootpath?t.entryPath?d=b(r.rootpath+y(a.path,t.entryPath)).path:d=r.rootpath:d=a.path:r.rootpath?d=r.rootpath:t.entryPath?d=t.entryPath:d=a.path,x(f,t.type,function(e,l){v+=e.replace(/@import .+?;/ig,"");if(!i&&p&&l&&(new Date(l)).valueOf()===(new Date(p.timestamp)).valueOf())S(p.css,t),n(null,null,e,t,{local:!0,remaining:s},f);else try{o[f]=e,(new r.Parser({optimization:r.optimization,paths:[a.path],entryPath:t.entryPath||a.path,mime:t.type,filename:f,rootpath:d,relativeUrls:t.relativeUrls,contents:o,files:u,dumpLineNumbers:r.dumpLineNumbers})).parse(e,function(r,i){if(r)return k(r,f);try{n(r,i,e,t,{local:!1,lastModified:l,remaining:s},f),N(document.getElementById("less-error-message:"+E(f)))}catch(r){k(r,f)}})}catch(c){k(c,f)}},function(e,t){throw new Error("Couldn't load "+t+" ("+e+")")})}function E(e){return e.replace(/^[a-z]+:\/\/?[^\/]+/,"").replace(/^\//,"").replace(/\.[a-zA-Z]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function S(e,t,n){var r,i=t.href||"",s="less:"+(t.title||E(i));if((r=document.getElementById(s))===null){r=document.createElement("style"),r.type="text/css",t.media&&(r.media=t.media),r.id=s;var o=t&&t.nextSibling||null;(o||document.getElementsByTagName("head")[0]).parentNode.insertBefore(r,o)}if(r.styleSheet)try{r.styleSheet.cssText=e}catch(u){throw new Error("Couldn't reassign styleSheet.cssText.")}else(function(e){r.childNodes.length>0?r.firstChild.nodeValue!==e.nodeValue&&r.replaceChild(e,r.firstChild):r.appendChild(e)})(document.createTextNode(e));if(n&&l){C("saving "+i+" to cache.");try{l.setItem(i,e),l.setItem(i+":timestamp",n)}catch(u){C("failed to save")}}}function x(e,t,n,i){function a(t,n,r){t.status>=200&&t.status<300?n(t.responseText,t.getResponseHeader("Last-Modified")):typeof r=="function"&&r(t.status,e)}var s=T(),u=o?r.fileAsync:r.async;typeof s.overrideMimeType=="function"&&s.overrideMimeType("text/css"),s.open("GET",e,u),s.setRequestHeader("Accept",t||"text/x-less, text/css; q=0.9, */*; q=0.5"),s.send(null),o&&!r.fileAsync?s.status===0||s.status>=200&&s.status<300?n(s.responseText):i(s.status,e):u?s.onreadystatechange=function(){s.readyState==4&&a(s,n,i)}:a(s,n,i)}function T(){if(e.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(t){return C("browser doesn't support AJAX."),null}}function N(e){return e&&e.parentNode.removeChild(e)}function C(e){r.env=="development"&&typeof console!="undefined"&&console.log("less: "+e)}function k(e,t){var n="less-error-message:"+E(t),i='
  • {content}
  • ',s=document.createElement("div"),o,u,a=[],f=e.filename||t,l=f.match(/([^\/]+(\?.*)?)$/)[1];s.id=n,s.className="less-error-message",u="

    "+(e.message||"There is an error in your .less file")+"

    "+'

    in '+l+" ";var c=function(e,t,n){e.extract[t]&&a.push(i.replace(/\{line\}/,parseInt(e.line)+(t-1)).replace(/\{class\}/,n).replace(/\{content\}/,e.extract[t]))};e.stack?u+="
    "+e.stack.split("\n").slice(1).join("
    "):e.extract&&(c(e,0,""),c(e,1,"line"),c(e,2,""),u+="on line "+e.line+", column "+(e.column+1)+":

    "+"
      "+a.join("")+"
    "),s.innerHTML=u,S([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),s.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),r.env=="development"&&(o=setInterval(function(){document.body&&(document.getElementById(n)?document.body.replaceChild(s,document.getElementById(n)):document.body.insertBefore(s,document.body.firstChild),clearInterval(o))},10))}Array.isArray||(Array.isArray=function(e){return Object.prototype.toString.call(e)==="[object Array]"||e instanceof Array}),Array.prototype.forEach||(Array.prototype.forEach=function(e,t){var n=this.length>>>0;for(var r=0;r>>0,n=new Array(t),r=arguments[1];for(var i=0;i>>0,n=0;if(t===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var r=arguments[1];else do{if(n in this){r=this[n++];break}if(++n>=t)throw new TypeError}while(!0);for(;n=t)return-1;n<0&&(n+=t);for(;nh&&(c[u]=c[u].slice(o-h),h=o)}function w(e){var t=e.charCodeAt(0);return t===32||t===10||t===9}function E(e){var t,n,r,i,a;if(e instanceof Function)return e.call(p.parsers);if(typeof e=="string")t=s.charAt(o)===e?e:null,r=1,b();else{b();if(!(t=e.exec(c[u])))return null;r=t[0].length}if(t)return S(r),typeof t=="string"?t:t.length===1?t[0]:t}function S(e){var t=o,n=u,r=o+c[u].length,i=o+=e;while(o=0&&t.charAt(n)!=="\n";n--)r++;return{line:typeof e=="number"?(t.slice(0,e).match(/\n/g)||"").length:null,column:r}}function L(e){return r.mode==="browser"||r.mode==="rhino"?e.filename:n("path").resolve(e.filename)}function A(e,t,n){return{lineNumber:k(e,t).line+1,fileName:L(n)}}function O(e,t){var n=C(e,t),r=k(e.index,n),i=r.line,s=r.column,o=n.split("\n");this.type=e.type||"Syntax",this.message=e.message,this.filename=e.filename||t.filename,this.index=e.index,this.line=typeof i=="number"?i+1:null,this.callLine=e.call&&k(e.call,n).line+1,this.callExtract=o[k(e.call,n).line],this.stack=e.stack,this.column=s,this.extract=[o[i-1],o[i],o[i+1]]}var s,o,u,a,f,l,c,h,p,d=this,t=t||{};t.contents||(t.contents={}),t.rootpath=t.rootpath||"",t.files||(t.files={});var v=function(){},m=this.imports={paths:t.paths||[],queue:[],files:t.files,contents:t.contents,mime:t.mime,error:null,push:function(e,n){var i=this;this.queue.push(e),r.Parser.importer(e,this.paths,function(t,r,s){i.queue.splice(i.queue.indexOf(e),1);var o=s in i.files;i.files[s]=r,t&&!i.error&&(i.error=t),n(t,r,o),i.queue.length===0&&v(i.error)},t)}};return this.env=t=t||{},this.optimization="optimization"in this.env?this.env.optimization:1,this.env.filename=this.env.filename||null,p={imports:m,parse:function(e,a){var f,d,m,g,y,b,w=[],S,x=null;o=u=h=l=0,s=e.replace(/\r\n/g,"\n"),s=s.replace(/^\uFEFF/,""),c=function(e){var n=0,r=/(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,i=/\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,o=/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,u=0,a,f=e[0],l;for(var c=0,h,p;c0?"missing closing `}`":"missing opening `{`",filename:t.filename},t)),e.map(function(e){return e.join("")})}([[]]);if(x)return a(x,t);try{f=new i.Ruleset([],E(this.parsers.primary)),f.root=!0}catch(T){return a(new O(T,t))}f.toCSS=function(e){var s,o,u;return function(s,o){var u=[],a;s=s||{},typeof o=="object"&&!Array.isArray(o)&&(o=Object.keys(o).map(function(e){var t=o[e];return t instanceof i.Value||(t instanceof i.Expression||(t=new i.Expression([t])),t=new i.Value([t])),new i.Rule("@"+e,t,!1,0)}),u=[new i.Ruleset(null,o)]);try{var f=e.call(this,{frames:u}).toCSS([],{compress:s.compress||!1,dumpLineNumbers:t.dumpLineNumbers})}catch(l){throw new O(l,t)}if(a=p.imports.error)throw a instanceof O?a:new O(a,t);return s.yuicompress&&r.mode==="node"?n("ycssmin").cssmin(f):s.compress?f.replace(/(\s)+/g,"$1"):f}}(f.eval);if(o=0&&s.charAt(N)!=="\n";N--)C++;x={type:"Parse",message:"Syntax Error on line "+y,index:o,filename:t.filename,line:y,column:C,extract:[b[y-2],b[y-1],b[y]]}}this.imports.queue.length>0?v=function(e){e=x||e,e?a(e):a(null,f)}:a(x,f)},parsers:{primary:function(){var e,t=[];while((e=E(this.mixin.definition)||E(this.rule)||E(this.ruleset)||E(this.mixin.call)||E(this.comment)||E(this.directive))||E(/^[\s\n]+/)||E(/^;+/))e&&t.push(e);return t},comment:function(){var e;if(s.charAt(o)!=="/")return;if(s.charAt(o+1)==="/")return new i.Comment(E(/^\/\/.*/),!0);if(e=E(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new i.Comment(e)},entities:{quoted:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=='"'&&s.charAt(t)!=="'")return;n&&E("~");if(e=E(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/))return new i.Quoted(e[0],e[1]||e[2],n)},keyword:function(){var e;if(e=E(/^[_A-Za-z-][_A-Za-z0-9-]*/))return i.colors.hasOwnProperty(e)?new i.Color(i.colors[e].slice(1)):new i.Keyword(e)},call:function(){var e,n,r,s,a=o;if(!(e=/^([\w-]+|%|progid:[\w\.]+)\(/.exec(c[u])))return;e=e[1],n=e.toLowerCase();if(n==="url")return null;o+=e.length;if(n==="alpha"){s=E(this.alpha);if(typeof s!="undefined")return s}E("("),r=E(this.entities.arguments);if(!E(")"))return;if(e)return new i.Call(e,r,a,t.filename)},arguments:function(){var e=[],t;while(t=E(this.entities.assignment)||E(this.expression)){e.push(t);if(!E(","))break}return e},literal:function(){return E(this.entities.ratio)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.quoted)||E(this.entities.unicodeDescriptor)},assignment:function(){var e,t;if((e=E(/^\w+(?=\s?=)/i))&&E("=")&&(t=E(this.entity)))return new i.Assignment(e,t)},url:function(){var e;if(s.charAt(o)!=="u"||!E(/^url\(/))return;return e=E(this.entities.quoted)||E(this.entities.variable)||E(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/)||"",x(")"),new i.URL(e.value!=null||e instanceof i.Variable?e:new i.Anonymous(e),t.rootpath)},variable:function(){var e,n=o;if(s.charAt(o)==="@"&&(e=E(/^@@?[\w-]+/)))return new i.Variable(e,n,t.filename)},variableCurly:function(){var e,n,r=o;if(s.charAt(o)==="@"&&(n=E(/^@\{([\w-]+)\}/)))return new i.Variable("@"+n[1],r,t.filename)},color:function(){var e;if(s.charAt(o)==="#"&&(e=E(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/)))return new i.Color(e[1])},dimension:function(){var e,t=s.charCodeAt(o);if(t>57||t<43||t===47||t==44)return;if(e=E(/^([+-]?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn|dpi|dpcm|dppx|rem|vw|vh|vmin|vm|ch)?/))return new i.Dimension(e[1],e[2])},ratio:function(){var e,t=s.charCodeAt(o);if(t>57||t<48)return;if(e=E(/^(\d+\/\d+)/))return new i.Ratio(e[1])},unicodeDescriptor:function(){var e;if(e=E(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/))return new i.UnicodeDescriptor(e[0])},javascript:function(){var e,t=o,n;s.charAt(t)==="~"&&(t++,n=!0);if(s.charAt(t)!=="`")return;n&&E("~");if(e=E(/^`([^`]*)`/))return new i.JavaScript(e[1],o,n)}},variable:function(){var e;if(s.charAt(o)==="@"&&(e=E(/^(@[\w-]+)\s*:/)))return e[1]},shorthand:function(){var e,t;if(!N(/^[@\w.%-]+\/[@\w.-]+/))return;g();if((e=E(this.entity))&&E("/")&&(t=E(this.entity)))return new i.Shorthand(e,t);y()},mixin:{call:function(){var e=[],n,r,u=[],a=[],f,l,c,h,p,d,v,m=o,b=s.charAt(o),w,S,C=!1;if(b!=="."&&b!=="#")return;g();while(n=E(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/))e.push(new i.Element(r,n,o)),r=E(">");if(E("(")){p=[];while(c=E(this.expression)){h=null,S=c;if(c.value.length==1){var k=c.value[0];k instanceof i.Variable&&E(":")&&(p.length>0&&(d&&T("Cannot mix ; and , as delimiter types"),v=!0),S=x(this.expression),h=w=k.name)}p.push(S),a.push({name:h,value:S});if(E(","))continue;if(E(";")||d)v&&T("Cannot mix ; and , as delimiter types"),d=!0,p.length>1&&(S=new i.Value(p)),u.push({name:w,value:S}),w=null,p=[],v=!1}x(")")}f=d?u:a,E(this.important)&&(C=!0);if(e.length>0&&(E(";")||N("}")))return new i.mixin.Call(e,f,m,t.filename,C);y()},definition:function(){var e,t=[],n,r,u,a,f,c=!1;if(s.charAt(o)!=="."&&s.charAt(o)!=="#"||N(/^[^{]*\}/))return;g();if(n=E(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){e=n[1];do{E(this.comment);if(s.charAt(o)==="."&&E(/^\.{3}/)){c=!0,t.push({variadic:!0});break}if(!(u=E(this.entities.variable)||E(this.entities.literal)||E(this.entities.keyword)))break;if(u instanceof i.Variable)if(E(":"))a=x(this.expression,"expected expression"),t.push({name:u.name,value:a});else{if(E(/^\.{3}/)){t.push({name:u.name,variadic:!0}),c=!0;break}t.push({name:u.name})}else t.push({value:u})}while(E(",")||E(";"));E(")")||(l=o,y()),E(this.comment),E(/^when/)&&(f=x(this.conditions,"expected condition")),r=E(this.block);if(r)return new i.mixin.Definition(e,t,r,f,c);y()}}},entity:function(){return E(this.entities.literal)||E(this.entities.variable)||E(this.entities.url)||E(this.entities.call)||E(this.entities.keyword)||E(this.entities.javascript)||E(this.comment)},end:function(){return E(";")||N("}")},alpha:function(){var e;if(!E(/^\(opacity=/i))return;if(e=E(/^\d+/)||E(this.entities.variable))return x(")"),new i.Alpha(e)},element:function(){var e,t,n,r;n=E(this.combinator),e=E(/^(?:\d+\.\d+|\d+)%/)||E(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||E("*")||E("&")||E(this.attribute)||E(/^\([^()@]+\)/)||E(/^[\.#](?=@)/)||E(this.entities.variableCurly),e||E("(")&&(r=E(this.entities.variableCurly)||E(this.entities.variable)||E(this.selector))&&E(")")&&(e=new i.Paren(r));if(e)return new i.Element(n,e,o)},combinator:function(){var e,t=s.charAt(o);if(t===">"||t==="+"||t==="~"||t==="|"){o++;while(s.charAt(o).match(/\s/))o++;return new i.Combinator(t)}return s.charAt(o-1).match(/\s/)?new i.Combinator(" "):new i.Combinator(null)},selector:function(){var e,t,n=[],r,u;if(E("("))return e=E(this.entity),E(")")?new i.Selector([new i.Element("",e,o)]):null;while(t=E(this.element)){r=s.charAt(o),n.push(t);if(r==="{"||r==="}"||r===";"||r===","||r===")")break}if(n.length>0)return new i.Selector(n)},attribute:function(){var e="",t,n,r;if(!E("["))return;if(t=E(/^(?:[_A-Za-z0-9-]|\\.)+/)||E(this.entities.quoted))(r=E(/^[|~*$^]?=/))&&(n=E(this.entities.quoted)||E(/^[\w-]+/))?e=[t,r,n.toCSS?n.toCSS():n].join(""):e=t;if(!E("]"))return;if(e)return"["+e+"]"},block:function(){var e;if(E("{")&&(e=E(this.primary))&&E("}"))return e},ruleset:function(){var e=[],n,r,u,a;g(),t.dumpLineNumbers&&(a=A(o,s,t));while(n=E(this.selector)){e.push(n),E(this.comment);if(!E(","))break;E(this.comment)}if(e.length>0&&(r=E(this.block))){var f=new i.Ruleset(e,r,t.strictImports);return t.dumpLineNumbers&&(f.debugInfo=a),f}l=o,y()},rule:function(){var e,t,n=s.charAt(o),r,a;g();if(n==="."||n==="#"||n==="&")return;if(e=E(this.variable)||E(this.property)){e.charAt(0)!="@"&&(a=/^([^@+\/'"*`(;{}-]*);/.exec(c[u]))?(o+=a[0].length-1,t=new i.Anonymous(a[1])):e==="font"?t=E(this.font):t=E(this.value),r=E(this.important);if(t&&E(this.end))return new i.Rule(e,t,r,f);l=o,y()}},"import":function(){var e,n,r=o;g();var s=E(/^@import(?:-(once))?\s+/);if(s&&(e=E(this.entities.quoted)||E(this.entities.url))){n=E(this.mediaFeatures);if(E(";"))return new i.Import(e,m,n,s[1]==="once",r,t.rootpath)}y()},mediaFeature:function(){var e,t,n=[];do if(e=E(this.entities.keyword))n.push(e);else if(E("(")){t=E(this.property),e=E(this.entity);if(!E(")"))return null;if(t&&e)n.push(new i.Paren(new i.Rule(t,e,null,o,!0)));else{if(!e)return null;n.push(new i.Paren(e))}}while(e);if(n.length>0)return new i.Expression(n)},mediaFeatures:function(){var e,t=[];do if(e=E(this.mediaFeature)){t.push(e);if(!E(","))break}else if(e=E(this.entities.variable)){t.push(e);if(!E(","))break}while(e);return t.length>0?t:null},media:function(){var e,n,r,u;t.dumpLineNumbers&&(u=A(o,s,t));if(E(/^@media/)){e=E(this.mediaFeatures);if(n=E(this.block))return r=new i.Media(n,e),t.dumpLineNumbers&&(r.debugInfo=u),r}},directive:function(){var e,n,r,u,a,f,l,c,h,p;if(s.charAt(o)!=="@")return;if(n=E(this["import"])||E(this.media))return n;g(),e=E(/^@[a-z-]+/);if(!e)return;l=e,e.charAt(1)=="-"&&e.indexOf("-",2)>0&&(l="@"+e.slice(e.indexOf("-",2)+1));switch(l){case"@font-face":c=!0;break;case"@viewport":case"@top-left":case"@top-left-corner":case"@top-center":case"@top-right":case"@top-right-corner":case"@bottom-left":case"@bottom-left-corner":case"@bottom-center":case"@bottom-right":case"@bottom-right-corner":case"@left-top":case"@left-middle":case"@left-bottom":case"@right-top":case"@right-middle":case"@right-bottom":c=!0;break;case"@page":case"@document":case"@supports":case"@keyframes":c=!0,h=!0;break;case"@namespace":p=!0}h&&(e+=" "+(E(/^[^{]+/)||"").trim());if(c){if(r=E(this.block))return new i.Directive(e,r)}else if((n=p?E(this.expression):E(this.entity))&&E(";")){var d=new i.Directive(e,n);return t.dumpLineNumbers&&(d.debugInfo=A(o,s,t)),d}y()},font:function(){var e=[],t=[],n,r,s,o;while(o=E(this.shorthand)||E(this.entity))t.push(o);e.push(new i.Expression(t));if(E(","))while(o=E(this.expression)){e.push(o);if(!E(","))break}return new i.Value(e)},value:function(){var e,t=[],n;while(e=E(this.expression)){t.push(e);if(!E(","))break}if(t.length>0)return new i.Value(t)},important:function(){if(s.charAt(o)==="!")return E(/^! *important/)},sub:function(){var e;if(E("(")&&(e=E(this.expression))&&E(")"))return e},multiplication:function(){var e,t,n,r;if(e=E(this.operand)){while(!N(/^\/[*\/]/)&&(n=E("/")||E("*"))&&(t=E(this.operand)))r=new i.Operation(n,[r||e,t]);return r||e}},addition:function(){var e,t,n,r;if(e=E(this.multiplication)){while((n=E(/^[-+]\s+/)||!w(s.charAt(o-1))&&(E("+")||E("-")))&&(t=E(this.multiplication)))r=new i.Operation(n,[r||e,t]);return r||e}},conditions:function(){var e,t,n=o,r;if(e=E(this.condition)){while(E(",")&&(t=E(this.condition)))r=new i.Condition("or",r||e,t,n);return r||e}},condition:function(){var e,t,n,r,s=o,u=!1;E(/^not/)&&(u=!0),x("(");if(e=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))return(r=E(/^(?:>=|=<|[<=>])/))?(t=E(this.addition)||E(this.entities.keyword)||E(this.entities.quoted))?n=new i.Condition(r,e,t,s,u):T("expected expression"):n=new i.Condition("=",e,new i.Keyword("true"),s,u),x(")"),E(/^and/)?new i.Condition("and",n,E(this.condition)):n},operand:function(){var e,t=s.charAt(o+1);s.charAt(o)==="-"&&(t==="@"||t==="(")&&(e=E("-"));var n=E(this.sub)||E(this.entities.dimension)||E(this.entities.color)||E(this.entities.variable)||E(this.entities.call);return e?new i.Operation("*",[new i.Dimension(-1),n]):n},expression:function(){var e,t,n=[],r;while(e=E(this.addition)||E(this.entity))n.push(e);if(n.length>0)return new i.Expression(n)},property:function(){var e;if(e=E(/^(\*?-?[_a-z0-9-]+)\s*:/))return e[1]}}}};if(r.mode==="browser"||r.mode==="rhino")r.Parser.importer=function(e,t,n,r){!/^([a-z-]+:)?\//.test(e)&&t.length>0&&(e=t[0]+e),w({href:e,title:e,type:r.mime,contents:r.contents,files:r.files,rootpath:r.rootpath,entryPath:r.entryPath,relativeUrls:r.relativeUrls},function(e,i,s,o,u,a){e&&typeof r.errback=="function"?r.errback.call(null,a,t,n,r):n.call(null,e,i,a)},!0)};(function(e){function t(t){return e.functions.hsla(t.h,t.s,t.l,t.a)}function n(t,n){return t instanceof e.Dimension&&t.unit=="%"?parseFloat(t.value*n/100):r(t)}function r(t){if(t instanceof e.Dimension)return parseFloat(t.unit=="%"?t.value/100:t.value);if(typeof t=="number")return t;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function i(e){return Math.min(1,Math.max(0,e))}e.functions={rgb:function(e,t,n){return this.rgba(e,t,n,1)},rgba:function(t,i,s,o){var u=[t,i,s].map(function(e){return n(e,256)});return o=r(o),new e.Color(u,o)},hsl:function(e,t,n){return this.hsla(e,t,n,1)},hsla:function(e,t,n,i){function u(e){return e=e<0?e+1:e>1?e-1:e,e*6<1?o+(s-o)*e*6:e*2<1?s:e*3<2?o+(s-o)*(2/3-e)*6:o}e=r(e)%360/360,t=r(t),n=r(n),i=r(i);var s=n<=.5?n*(t+1):n+t-n*t,o=n*2-s;return this.rgba(u(e+1/3)*255,u(e)*255,u(e-1/3)*255,i)},hsv:function(e,t,n){return this.hsva(e,t,n,1)},hsva:function(e,t,n,i){e=r(e)%360/360*360,t=r(t),n=r(n),i=r(i);var s,o;s=Math.floor(e/60%6),o=e/60-s;var u=[n,n*(1-t),n*(1-o*t),n*(1-(1-o)*t)],a=[[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]];return this.rgba(u[a[s][0]]*255,u[a[s][1]]*255,u[a[s][2]]*255,i)},hue:function(t){return new e.Dimension(Math.round(t.toHSL().h))},saturation:function(t){return new e.Dimension(Math.round(t.toHSL().s*100),"%")},lightness:function(t){return new e.Dimension(Math.round(t.toHSL().l*100),"%")},red:function(t){return new e.Dimension(t.rgb[0])},green:function(t){return new e.Dimension(t.rgb[1])},blue:function(t){return new e.Dimension(t.rgb[2])},alpha:function(t){return new e.Dimension(t.toHSL().a)},luma:function(t){return new e.Dimension(Math.round((.2126*(t.rgb[0]/255)+.7152*(t.rgb[1]/255)+.0722*(t.rgb[2]/255))*t.alpha*100),"%")},saturate:function(e,n){var r=e.toHSL();return r.s+=n.value/100,r.s=i(r.s),t(r)},desaturate:function(e,n){var r=e.toHSL();return r.s-=n.value/100,r.s=i(r.s),t(r)},lighten:function(e,n){var r=e.toHSL();return r.l+=n.value/100,r.l=i(r.l),t(r)},darken:function(e,n){var r=e.toHSL();return r.l-=n.value/100,r.l=i(r.l),t(r)},fadein:function(e,n){var r=e.toHSL();return r.a+=n.value/100,r.a=i(r.a),t(r)},fadeout:function(e,n){var r=e.toHSL();return r.a-=n.value/100,r.a=i(r.a),t(r)},fade:function(e,n){var r=e.toHSL();return r.a=n.value/100,r.a=i(r.a),t(r)},spin:function(e,n){var r=e.toHSL(),i=(r.h+n.value)%360;return r.h=i<0?360+i:i,t(r)},mix:function(t,n,r){r||(r=new e.Dimension(50));var i=r.value/100,s=i*2-1,o=t.toHSL().a-n.toHSL().a,u=((s*o==-1?s:(s+o)/(1+s*o))+1)/2,a=1-u,f=[t.rgb[0]*u+n.rgb[0]*a,t.rgb[1]*u+n.rgb[1]*a,t.rgb[2]*u+n.rgb[2]*a],l=t.alpha*i+n.alpha*(1-i);return new e.Color(f,l)},greyscale:function(t){return this.desaturate(t,new e.Dimension(100))},contrast:function(e,t,n,r){return e.rgb?(typeof n=="undefined"&&(n=this.rgba(255,255,255,1)),typeof t=="undefined"&&(t=this.rgba(0,0,0,1)),typeof r=="undefined"?r=.43:r=r.value,(.2126*(e.rgb[0]/255)+.7152*(e.rgb[1]/255)+.0722*(e.rgb[2]/255))*e.alpha255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},operate:function(t,n){var r=[];n instanceof e.Color||(n=n.toColor());for(var i=0;i<3;i++)r[i]=e.operate(t,this.rgb[i],n.rgb[i]);return new e.Color(r,this.alpha+n.alpha)},toHSL:function(){var e=this.rgb[0]/255,t=this.rgb[1]/255,n=this.rgb[2]/255,r=this.alpha,i=Math.max(e,t,n),s=Math.min(e,t,n),o,u,a=(i+s)/2,f=i-s;if(i===s)o=u=0;else{u=a>.5?f/(2-i-s):f/(i+s);switch(i){case e:o=(t-n)/f+(t255?255:e<0?0:e).toString(16),e.length===1?"0"+e:e}).join("")},compare:function(e){return e.rgb?e.rgb[0]===this.rgb[0]&&e.rgb[1]===this.rgb[1]&&e.rgb[2]===this.rgb[2]&&e.alpha===this.alpha?0:-1:-1}}}(n("../tree")),function(e){e.Comment=function(e,t){this.value=e,this.silent=!!t},e.Comment.prototype={toCSS:function(e){return e.compress?"":this.value},eval:function(){return this}}}(n("../tree")),function(e){e.Condition=function(e,t,n,r,i){this.op=e.trim(),this.lvalue=t,this.rvalue=n,this.index=r,this.negate=i},e.Condition.prototype.eval=function(e){var t=this.lvalue.eval(e),n=this.rvalue.eval(e),r=this.index,i,i=function(e){switch(e){case"and":return t&&n;case"or":return t||n;default:if(t.compare)i=t.compare(n);else{if(!n.compare)throw{type:"Type",message:"Unable to perform comparison",index:r};i=n.compare(t)}switch(i){case-1:return e==="<"||e==="=<";case 0:return e==="="||e===">="||e==="=<";case 1:return e===">"||e===">="}}}(this.op);return this.negate?!i:i}}(n("../tree")),function(e){e.Dimension=function(e,t){this.value=parseFloat(e),this.unit=t||null},e.Dimension.prototype={eval:function(){return this},toColor:function(){return new e.Color([this.value,this.value,this.value])},toCSS:function(){var e=this.value+this.unit;return e},operate:function(t,n){return new e.Dimension(e.operate(t,this.value,n.value),this.unit||n.unit)},compare:function(t){return t instanceof e.Dimension?t.value>this.value?-1:t.value":e.compress?">":" > ","|":e.compress?"|":" | "}[this.value]}}(n("../tree")),function(e){e.Expression=function(e){this.value=e},e.Expression.prototype={eval:function(t){return this.value.length>1?new e.Expression(this.value.map(function(e){return e.eval(t)})):this.value.length===1?this.value[0].eval(t):this},toCSS:function(e){return this.value.map(function(t){return t.toCSS?t.toCSS(e):""}).join(" ")}}}(n("../tree")),function(e){e.Import=function(t,n,r,i,s,o){var u=this;this.once=i,this.index=s,this._path=t,this.features=r&&new e.Value(r),this.rootpath=o,t instanceof e.Quoted?this.path=/(\.[a-z]*$)|([\?;].*)$/.test(t.value)?t.value:t.value+".less":this.path=t.value.value||t.value,this.css=/css([\?;].*)?$/.test(this.path),this.css||n.push(this.path,function(t,n,r){t&&(t.index=s),r&&u.once&&(u.skip=r),u.root=n||new e.Ruleset([],[])})},e.Import.prototype={toCSS:function(e){var t=this.features?" "+this.features.toCSS(e):"";return this.css?(typeof this._path.value=="string"&&!/^(?:[a-z-]+:|\/)/.test(this._path.value)&&(this._path.value=this.rootpath+this._path.value),"@import "+this._path.toCSS()+t+";\n"):""},eval:function(t){var n,r=this.features&&this.features.eval(t);return this.skip?[]:this.css?this:(n=new e.Ruleset([],this.root.rules.slice(0)),n.evalImports(t),this.features?new e.Media(n.rules,this.features.value):n.rules)}}}(n("../tree")),function(e){e.JavaScript=function(e,t,n){this.escaped=n,this.expression=e,this.index=t},e.JavaScript.prototype={eval:function(t){var n,r=this,i={},s=this.expression.replace(/@\{([\w-]+)\}/g,function(n,i){return e.jsify((new e.Variable("@"+i,r.index)).eval(t))});try{s=new Function("return ("+s+")")}catch(o){throw{message:"JavaScript evaluation error: `"+s+"`",index:this.index}}for(var u in t.frames[0].variables())i[u.slice(1)]={value:t.frames[0].variables()[u].value,toJS:function(){return this.value.eval(t).toCSS()}};try{n=s.call(i)}catch(o){throw{message:"JavaScript evaluation error: '"+o.name+": "+o.message+"'",index:this.index}}return typeof n=="string"?new e.Quoted('"'+n+'"',n,this.escaped,this.index):Array.isArray(n)?new e.Anonymous(n.join(", ")):new e.Anonymous(n)}}}(n("../tree")),function(e){e.Keyword=function(e){this.value=e},e.Keyword.prototype={eval:function(){return this},toCSS:function(){return this.value},compare:function(t){return t instanceof e.Keyword?t.value===this.value?0:1:-1}},e.True=new e.Keyword("true"),e.False=new e.Keyword("false")}(n("../tree")),function(e){e.Media=function(t,n){var r=this.emptySelectors();this.features=new e.Value(n),this.ruleset=new e.Ruleset(r,t),this.ruleset.allowImports=!0},e.Media.prototype={toCSS:function(e,t){var n=this.features.toCSS(t);return this.ruleset.root=e.length===0||e[0].multiMedia,"@media "+n+(t.compress?"{":" {\n ")+this.ruleset.toCSS(e,t).trim().replace(/\n/g,"\n ")+(t.compress?"}":"\n}\n")},eval:function(t){t.mediaBlocks||(t.mediaBlocks=[],t.mediaPath=[]);var n=new e.Media([],[]);return this.debugInfo&&(this.ruleset.debugInfo=this.debugInfo,n.debugInfo=this.debugInfo),n.features=this.features.eval(t),t.mediaPath.push(n),t.mediaBlocks.push(n),t.frames.unshift(this.ruleset),n.ruleset=this.ruleset.eval(t),t.frames.shift(),t.mediaPath.pop(),t.mediaPath.length===0?n.evalTop(t):n.evalNested(t)},variable:function(t){return e.Ruleset.prototype.variable.call(this.ruleset,t)},find:function(){return e.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return e.Ruleset.prototype.rulesets.apply(this.ruleset)},emptySelectors:function(){var t=new e.Element("","&",0);return[new e.Selector([t])]},evalTop:function(t){var n=this;if(t.mediaBlocks.length>1){var r=this.emptySelectors();n=new e.Ruleset(r,t.mediaBlocks),n.multiMedia=!0}return delete t.mediaBlocks,delete t.mediaPath,n},evalNested:function(t){var n,r,i=t.mediaPath.concat([this]);for(n=0;n0;n--)t.splice(n,0,new e.Anonymous("and"));return new e.Expression(t)})),new e.Ruleset([],[])},permute:function(e){if(e.length===0)return[];if(e.length===1)return e[0];var t=[],n=this.permute(e.slice(1));for(var r=0;r0){c=!0;for(a=0;athis.params.length)return!1;if(this.required>0&&n>this.params.length)return!1}r=Math.min(n,this.arity);for(var s=0;si.selectors[o].elements.length?Array.prototype.push.apply(r,i.find(new e.Selector(t.elements.slice(1)),n)):r.push(i);break}}),this._lookups[o]=r)},toCSS:function(t,n){var r=[],i=[],s=[],o=[],u=[],a,f,l;this.root||this.joinSelectors(u,t,this.selectors);for(var c=0;c0){f=e.debugInfo(n,this),a=u.map(function(e){return e.map(function(e){return e.toCSS(n)}).join("").trim()}).join(n.compress?",":",\n");for(var c=i.length-1;c>=0;c--)s.indexOf(i[c])===-1&&s.unshift(i[c]);i=s,r.push(f+a+(n.compress?"{":" {\n ")+i.join(n.compress?"":"\n ")+(n.compress?"}":"\n}\n"))}return r.push(o),r.join("")+(n.compress?"\n":"")},joinSelectors:function(e,t,n){for(var r=0;r0)for(i=0;i0&&this.mergeElementsOnToSelectors(g,a);for(s=0;s0&&(l[0].elements=l[0].elements.slice(0),l[0].elements.push(new e.Element(f.combinator,"",0))),y.push(l);else for(o=0;o0?(h=l.slice(0),m=h.pop(),d=new e.Selector(m.elements.slice(0)),v=!1):d=new e.Selector([]),c.length>1&&(p=p.concat(c.slice(1))),c.length>0&&(v=!1,d.elements.push(new e.Element(f.combinator,c[0].elements[0].value,0)),d.elements=d.elements.concat(c[0].elements.slice(1))),v||h.push(d),h=h.concat(p),y.push(h)}a=y,g=[]}}g.length>0&&this.mergeElementsOnToSelectors(g,a);for(i=0;i0?i[i.length-1]=new e.Selector(i[i.length-1].elements.concat(t)):i.push(new e.Selector(t))}}}(n("../tree")),function(e){e.Selector=function(e){this.elements=e},e.Selector.prototype.match=function(e){var t=this.elements,n=t.length,r,i,s,o;r=e.elements.slice(e.elements.length&&e.elements[0].value==="&"?1:0),i=r.length,s=Math.min(n,i);if(i===0||n1?"["+e.value.map(function(e){return e.toCSS(!1)}).join(", ")+"]":e.toCSS(!1)}}(n("./tree"));var o=/^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);r.env=r.env||(location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname=="localhost"||location.port.length>0||o?"development":"production"),r.async=r.async||!1,r.fileAsync=r.fileAsync||!1,r.poll=r.poll||(o?1e3:1500);if(r.functions)for(var u in r.functions)r.tree.functions[u]=r.functions[u];var a=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);a&&(r.dumpLineNumbers=a[1]),r.watch=function(){return r.watchMode||(r.env="development",f()),this.watchMode=!0},r.unwatch=function(){return clearInterval(r.watchTimer),this.watchMode=!1},/!watch/.test(location.hash)&&r.watch();var l=null;if(r.env!="development")try{l=typeof e.localStorage=="undefined"?null:e.localStorage}catch(c){}var h=document.getElementsByTagName("link"),p=/^text\/(x-)?less$/;r.sheets=[];for(var d=0;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},v=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return u("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=s.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(s.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(s.call(arguments)))};return e}),o.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:u(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},o.svg=function(){return!!b.createElementNS&&!!b.createElementNS(n.svg,"svg").createSVGRect},o.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==n.svg},o.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(l.call(b.createElementNS(n.svg,"clipPath")))};for(var D in o)x(o,D)&&(t=D.toLowerCase(),e[t]=o[D](),r.push((e[t]?"":"no-")+t));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=m,e.mq=v,e.testStyles=u,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+r.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | DropDot - Simple file uploading and sharing 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 37 | 38 | 39 | 40 | 41 |
    42 |
    43 |
    44 |

    DropDot

    45 |
    46 | 47 |

    Drop a file here and share

    48 |

    Any file. 20MB Maximum. No fishy business. ;)
    49 | All files expire in 24 hours from the uploaded time.

    50 | 51 |
    52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
    62 |
    63 | 64 |
    65 | 66 | 75 | 76 | 89 | 90 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 116 | 117 | Fork me on GitHub 118 | 119 | 120 | 121 | 122 | --------------------------------------------------------------------------------